summaryrefslogtreecommitdiff
path: root/HutchTests
diff options
context:
space:
mode:
Diffstat (limited to 'HutchTests')
-rw-r--r--HutchTests/TicketListViewModelTests.swift116
1 files changed, 116 insertions, 0 deletions
diff --git a/HutchTests/TicketListViewModelTests.swift b/HutchTests/TicketListViewModelTests.swift
new file mode 100644
index 0000000..23be24c
--- /dev/null
+++ b/HutchTests/TicketListViewModelTests.swift
@@ -0,0 +1,116 @@
+import Foundation
+import Testing
+@testable import Hutch
+
+struct TicketListViewModelTests {
+
+ @Test
+ func filteredTicketsReturnsStatusFilteredTicketsWhenSearchTextIsEmpty() {
+ let tickets = [
+ makeTicket(id: 1, title: "Crash on launch", status: .reported, submitter: "~owner", labels: []),
+ makeTicket(id: 2, title: "Already fixed", status: .resolved, submitter: "~owner", labels: [])
+ ]
+
+ let filtered = filterTickets(tickets, filter: .open, query: "")
+
+ #expect(filtered.map(\.id) == [1])
+ }
+
+ @Test
+ func filteredTicketsMatchesTitleAndTicketId() {
+ let tickets = [
+ makeTicket(id: 42, title: "Crash on launch", status: .reported, submitter: "~owner", labels: []),
+ makeTicket(id: 99, title: "Settings polish", status: .reported, submitter: "~owner", labels: [])
+ ]
+
+ let titleMatches = filterTickets(tickets, filter: .all, query: "settings")
+ let idMatches = filterTickets(tickets, filter: .all, query: "42")
+
+ #expect(titleMatches.map(\.id) == [99])
+ #expect(idMatches.map(\.id) == [42])
+ }
+
+ @Test
+ func filteredTicketsMatchesSubmitterAndLabels() {
+ let tickets = [
+ makeTicket(id: 1, title: "Crash on launch", status: .reported, submitter: "~owner", labels: [makeLabel(id: 1, name: "bug")]),
+ makeTicket(id: 2, title: "Needs triage", status: .reported, submitter: "~triage", labels: [makeLabel(id: 2, name: "needs-info")])
+ ]
+
+ let submitterMatches = filterTickets(tickets, filter: .all, query: "~triage")
+ let labelMatches = filterTickets(tickets, filter: .all, query: "bug")
+
+ #expect(submitterMatches.map(\.id) == [2])
+ #expect(labelMatches.map(\.id) == [1])
+ }
+
+ @Test
+ @MainActor
+ func resolveTicketInputHasCorrectStatusAndDefaultResolution() {
+ let input: [String: any Sendable] = [
+ "status": TicketStatus.resolved.rawValue,
+ "resolution": TicketResolution.fixed.rawValue
+ ]
+ #expect(input["status"] as? String == "resolved")
+ #expect(input["resolution"] as? String == "fixed")
+ }
+
+ @Test
+ @MainActor
+ func reopenTicketInputOmitsResolution() {
+ let input: [String: any Sendable] = [
+ "status": TicketStatus.reported.rawValue
+ ]
+ #expect(input["status"] as? String == "reported")
+ #expect(input["resolution"] == nil)
+ }
+
+ private func filterTickets(_ tickets: [TicketSummary], filter: TicketFilter, query: String) -> [TicketSummary] {
+ let statusFiltered: [TicketSummary]
+ switch filter {
+ case .open:
+ statusFiltered = tickets.filter { $0.status.isOpen }
+ case .resolved:
+ statusFiltered = tickets.filter { !$0.status.isOpen }
+ case .all:
+ statusFiltered = tickets
+ }
+
+ let q = query.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
+ guard !q.isEmpty else { return statusFiltered }
+ return statusFiltered.filter {
+ String($0.id).contains(q) ||
+ $0.title.lowercased().contains(q) ||
+ $0.submitter.canonicalName.lowercased().contains(q) ||
+ $0.labels.contains { $0.name.lowercased().contains(q) }
+ }
+ }
+
+ private func makeTicket(
+ id: Int,
+ title: String,
+ status: TicketStatus,
+ submitter: String,
+ labels: [TicketLabel]
+ ) -> TicketSummary {
+ TicketSummary(
+ id: id,
+ title: title,
+ status: status,
+ resolution: status == .resolved ? .fixed : nil,
+ created: Date(),
+ submitter: Entity(canonicalName: submitter),
+ labels: labels,
+ assignees: []
+ )
+ }
+
+ private func makeLabel(id: Int, name: String) -> TicketLabel {
+ TicketLabel(
+ id: id,
+ name: name,
+ backgroundColor: "#000000",
+ foregroundColor: "#ffffff"
+ )
+ }
+}