From 9c11d69002d71556fea1e2a938e02d4f4d503ea5 Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Fri, 7 Aug 2026 03:06:34 -0500 Subject: Expand App Intents: dialogs, search type, mutating intents (#17) Addresses the three open items on the App Intents ticket: - Check Status / Check Builds now return a spoken/visible dialog alongside their value, so they surface a result in Siri instead of appearing to do nothing. Logic consolidated to always produce a message. - Search Hutch gains a Search Type parameter (user, git/hg repo, mailing list, tracker, build job) threaded through the .search route into Lookup, which pre-selects the type. LookupType now conforms to AppEnum. - Adds mutating intents (not just navigation): Clear Recent Activity and Unpin Resource, backed by RecentActivityStore.clear and a new HomePinStore.removePin. Both act on the active account's storage. Tests cover search-type routing, the blank-query fallback, and pin removal. --- HutchTests/DeepLinkTests.swift | 3 ++- HutchTests/HutchIntentsTests.swift | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) (limited to 'HutchTests') diff --git a/HutchTests/DeepLinkTests.swift b/HutchTests/DeepLinkTests.swift index d62cfef..cdc5bab 100644 --- a/HutchTests/DeepLinkTests.swift +++ b/HutchTests/DeepLinkTests.swift @@ -92,7 +92,8 @@ struct DeepLinkTests { func parsesRouteBackedNavigationLinks() { #expect(DeepLink(url: HutchRoute.workQueue(scope: .assigned).url) == .workQueue(scope: .assigned)) #expect(DeepLink(url: HutchRoute.failedBuilds.url) == .failedBuilds) - #expect(DeepLink(url: HutchRoute.search(query: "patch queue").url) == .search(query: "patch queue")) + #expect(DeepLink(url: HutchRoute.search(query: "patch queue", type: nil).url) == .search(query: "patch queue", type: nil)) + #expect(DeepLink(url: HutchRoute.search(query: "~alice", type: .user).url) == .search(query: "~alice", type: .user)) #expect(DeepLink(url: HutchRoute.projectDashboard(id: "project-1", title: "Hutch").url) == .projectDashboard(id: "project-1", title: "Hutch")) } diff --git a/HutchTests/HutchIntentsTests.swift b/HutchTests/HutchIntentsTests.swift index ddbe973..e201f42 100644 --- a/HutchTests/HutchIntentsTests.swift +++ b/HutchTests/HutchIntentsTests.swift @@ -110,4 +110,37 @@ struct HutchIntentsTests { ActiveAccountContextStore.save("account-a", defaults: defaults) #expect(NeedsAttentionSnapshotStore.load(defaults: defaults)?.failedBuilds == 3) } + + @Test + func searchIntentCarriesQueryAndType() { + let intent = SearchHutchIntent() + intent.query = "~alice/hutch" + intent.searchType = .tracker + #expect(intent.route == .search(query: "~alice/hutch", type: .tracker)) + } + + @Test + func searchIntentWithBlankQueryFallsBackToLookup() { + let intent = SearchHutchIntent() + intent.query = " " + intent.searchType = .user + #expect(intent.route == .lookup) + } + + @Test + func removePinDropsMatchingResource() { + let defaultsName = "HutchIntentsTests-unpin-\(UUID().uuidString)" + let defaults = UserDefaults(suiteName: defaultsName)! + defer { defaults.removePersistentDomain(forName: defaultsName) } + + let alice = HomePinRecord(kind: .user, value: "~alice", title: "~alice", subtitle: "User", ownerUsername: "~alice", service: nil) + let bob = HomePinRecord(kind: .user, value: "~bob", title: "~bob", subtitle: "User", ownerUsername: "~bob", service: nil) + HomePinStore.togglePin(alice, for: "~me", defaults: defaults) + HomePinStore.togglePin(bob, for: "~me", defaults: defaults) + + HomePinStore.removePin(id: alice.id, for: "~me", defaults: defaults) + + let remaining = HomePinStore.loadPins(for: "~me", defaults: defaults) + #expect(remaining.map(\.id) == [bob.id]) + } } -- cgit v1.2.3