summaryrefslogtreecommitdiff
path: root/HutchTests
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-04-12 22:52:33 -0500
committerChristian Cleberg <[email protected]>2026-04-12 22:52:33 -0500
commit72f860e0e171f6aac4e7d896e7a571147dbcb5c5 (patch)
treec62c9c9325292f65e6f72372a38b35760cc937b1 /HutchTests
parent576bb6c2432a15f5138771c06019b55fd2244e15 (diff)
downloadhutch-72f860e0e171f6aac4e7d896e7a571147dbcb5c5.tar.gz
hutch-72f860e0e171f6aac4e7d896e7a571147dbcb5c5.tar.bz2
hutch-72f860e0e171f6aac4e7d896e7a571147dbcb5c5.zip
feat: add search and recent queries
Implements: https://todo.sr.ht/~ccleberg/hutch/47 Implements: https://todo.sr.ht/~ccleberg/hutch/48
Diffstat (limited to 'HutchTests')
-rw-r--r--HutchTests/BuildListViewModelTests.swift21
-rw-r--r--HutchTests/RepositoryListViewModelTests.swift23
-rw-r--r--HutchTests/ScopedSearchHistoryStoreTests.swift54
-rw-r--r--HutchTests/TicketListViewModelTests.swift41
4 files changed, 126 insertions, 13 deletions
diff --git a/HutchTests/BuildListViewModelTests.swift b/HutchTests/BuildListViewModelTests.swift
index a8cdc30..fccd7cf 100644
--- a/HutchTests/BuildListViewModelTests.swift
+++ b/HutchTests/BuildListViewModelTests.swift
@@ -29,6 +29,18 @@ struct BuildListViewModelTests {
}
@Test
+ func filteredJobsMatchesByStatus() {
+ let jobs = [
+ makeJob(id: 1, status: .running, tags: []),
+ makeJob(id: 2, status: .success, tags: [])
+ ]
+
+ let filtered = filterJobs(jobs, query: "running")
+
+ #expect(filtered.map(\.id) == [1])
+ }
+
+ @Test
func buildFilterPrioritizesActionableStates() {
let jobs = [
makeJob(id: 1, status: .success, tags: []),
@@ -45,14 +57,7 @@ struct BuildListViewModelTests {
}
private func filterJobs(_ jobs: [JobSummary], query: String) -> [JobSummary] {
- let q = query.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
- guard !q.isEmpty else { return jobs }
- return jobs.filter {
- String($0.id).contains(q) ||
- $0.tags.contains { $0.lowercased().contains(q) } ||
- ($0.note?.lowercased().contains(q) == true) ||
- ($0.image?.lowercased().contains(q) == true)
- }
+ BuildListViewModel.searchJobs(jobs, matching: query)
}
private func makeJob(id: Int, status: JobStatus = .success, tags: [String]) -> JobSummary {
diff --git a/HutchTests/RepositoryListViewModelTests.swift b/HutchTests/RepositoryListViewModelTests.swift
index 7bd9126..07f66aa 100644
--- a/HutchTests/RepositoryListViewModelTests.swift
+++ b/HutchTests/RepositoryListViewModelTests.swift
@@ -29,6 +29,21 @@ struct RepositoryListViewModelTests {
}
@Test
+ @MainActor
+ func filterRepositoriesMatchesOwnerAndDefaultBranchLocally() {
+ let repositories = [
+ makeRepository(id: 1, service: .git, name: "Hutch", description: nil, owner: "~alice", branch: "main"),
+ makeRepository(id: 2, service: .hg, name: "Mail", description: nil, owner: "~bob", branch: "stable")
+ ]
+
+ let ownerMatches = RepositoryListViewModel.filterRepositories(repositories, matching: "~bob")
+ let branchMatches = RepositoryListViewModel.filterRepositories(repositories, matching: "main")
+
+ #expect(ownerMatches.map(\.id) == [2])
+ #expect(branchMatches.map(\.id) == [1])
+ }
+
+ @Test
func buildStatusKeysParsesSourceHutRepositoryURLsFromManifest() {
let manifest = """
image: alpine/latest
@@ -59,7 +74,9 @@ struct RepositoryListViewModelTests {
id: Int,
service: SRHTService,
name: String,
- description: String?
+ description: String?,
+ owner: String = "~owner",
+ branch: String = "main"
) -> RepositorySummary {
RepositorySummary(
id: id,
@@ -69,8 +86,8 @@ struct RepositoryListViewModelTests {
description: description,
visibility: .public,
updated: Date(timeIntervalSince1970: TimeInterval(id)),
- owner: Entity(canonicalName: "~owner"),
- head: Reference(name: "main", target: nil)
+ owner: Entity(canonicalName: owner),
+ head: Reference(name: branch, target: nil)
)
}
}
diff --git a/HutchTests/ScopedSearchHistoryStoreTests.swift b/HutchTests/ScopedSearchHistoryStoreTests.swift
new file mode 100644
index 0000000..856d69f
--- /dev/null
+++ b/HutchTests/ScopedSearchHistoryStoreTests.swift
@@ -0,0 +1,54 @@
+import Foundation
+import Testing
+@testable import Hutch
+
+struct ScopedSearchHistoryStoreTests {
+
+ @Test
+ func recordsMostRecentUniqueSearchPerScopeFirst() {
+ let suiteName = "ScopedSearchHistoryStoreTests-\(UUID().uuidString)"
+ let defaults = UserDefaults(suiteName: suiteName)!
+ defer { defaults.removePersistentDomain(forName: suiteName) }
+
+ ScopedSearchHistoryStore.record(
+ query: "hutch",
+ scopeID: "repositories",
+ defaults: defaults,
+ now: Date(timeIntervalSince1970: 100)
+ )
+ ScopedSearchHistoryStore.record(
+ query: "running",
+ scopeID: "builds",
+ defaults: defaults,
+ now: Date(timeIntervalSince1970: 200)
+ )
+ ScopedSearchHistoryStore.record(
+ query: "Hutch",
+ scopeID: "repositories",
+ defaults: defaults,
+ now: Date(timeIntervalSince1970: 300)
+ )
+
+ let repositoryHistory = ScopedSearchHistoryStore.load(scopeID: "repositories", defaults: defaults)
+ let buildHistory = ScopedSearchHistoryStore.load(scopeID: "builds", defaults: defaults)
+
+ #expect(repositoryHistory.map(\.query) == ["Hutch"])
+ #expect(repositoryHistory.first?.createdAt == Date(timeIntervalSince1970: 300))
+ #expect(buildHistory.map(\.query) == ["running"])
+ }
+
+ @Test
+ func clearsOnlyRequestedScope() {
+ let suiteName = "ScopedSearchHistoryStoreTests-\(UUID().uuidString)"
+ let defaults = UserDefaults(suiteName: suiteName)!
+ defer { defaults.removePersistentDomain(forName: suiteName) }
+
+ ScopedSearchHistoryStore.record(query: "repo", scopeID: "repositories", defaults: defaults)
+ ScopedSearchHistoryStore.record(query: "ticket", scopeID: "tickets.rid", defaults: defaults)
+
+ ScopedSearchHistoryStore.clear(scopeID: "repositories", defaults: defaults)
+
+ #expect(ScopedSearchHistoryStore.load(scopeID: "repositories", defaults: defaults).isEmpty)
+ #expect(ScopedSearchHistoryStore.load(scopeID: "tickets.rid", defaults: defaults).map(\.query) == ["ticket"])
+ }
+}
diff --git a/HutchTests/TicketListViewModelTests.swift b/HutchTests/TicketListViewModelTests.swift
index a237071..7fa5449 100644
--- a/HutchTests/TicketListViewModelTests.swift
+++ b/HutchTests/TicketListViewModelTests.swift
@@ -66,6 +66,42 @@ struct TicketListViewModelTests {
}
@Test
+ func filteredTicketsMatchesAssigneeAndStatus() {
+ let tickets = [
+ makeTicket(
+ id: 1,
+ title: "Crash on launch",
+ status: .inProgress,
+ submitter: "~owner",
+ labels: [],
+ assignees: [Entity(canonicalName: "~alice")]
+ ),
+ makeTicket(
+ id: 2,
+ title: "Settings polish",
+ status: .resolved,
+ submitter: "~owner",
+ labels: [],
+ assignees: []
+ )
+ ]
+
+ let assigneeMatches = filterTickets(
+ tickets,
+ state: TicketListFilterState(status: .all),
+ query: "~alice"
+ )
+ let statusMatches = filterTickets(
+ tickets,
+ state: TicketListFilterState(status: .all),
+ query: "resolved"
+ )
+
+ #expect(assigneeMatches.map(\.id) == [1])
+ #expect(statusMatches.map(\.id) == [2])
+ }
+
+ @Test
func filteredTicketsMatchesAnySelectedLabel() {
let tickets = [
makeTicket(id: 1, title: "Crash on launch", status: .reported, submitter: "~owner", labels: [makeLabel(id: 1, name: "bug")]),
@@ -153,7 +189,8 @@ struct TicketListViewModelTests {
title: String,
status: TicketStatus,
submitter: String,
- labels: [TicketLabel]
+ labels: [TicketLabel],
+ assignees: [Entity] = []
) -> TicketSummary {
TicketSummary(
id: id,
@@ -163,7 +200,7 @@ struct TicketListViewModelTests {
created: Date(),
submitter: Entity(canonicalName: submitter),
labels: labels,
- assignees: []
+ assignees: assignees
)
}