native macOS codings agent orchestrator prowl.onev.cat
Something went wrong. Try again.
11 kB · 357 lines
Swift
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358import ComposableArchitectureimport DependenciesTestSupportimport Foundationimport Testing
@testable import supacode
private struct SystemNotificationSend: Equatable { let title: String let body: String let worktreeID: Worktree.ID? let surfaceID: UUID?}
@MainActorstruct AppFeatureSystemNotificationTests { @Test(.dependencies) func firstTimeDeniedTurnsSystemNotificationsBackOffWithAlert() async { let storage = SettingsTestStorage() let authorizationRequests = LockIsolated(0) let store = withDependencies { $0.settingsFileStorage = storage.storage } operation: { TestStore(initialState: AppFeature.State()) { AppFeature() } withDependencies: { $0.systemNotificationClient.authorizationStatus = { .notDetermined } $0.systemNotificationClient.requestAuthorization = { authorizationRequests.withValue { $0 += 1 } return SystemNotificationClient.AuthorizationRequestResult( granted: false, errorMessage: "Mock request error" ) } } } store.exhaustivity = .off
await store.send(.settings(.binding(.set(\.systemNotificationsEnabled, true)))) { $0.settings.systemNotificationsEnabled = true } await store.receive(\.systemNotificationsPermissionFailed) await store.receive(\.settings.setSystemNotificationsEnabled) { $0.settings.systemNotificationsEnabled = false } let expectedAlert = AlertState<SettingsFeature.Alert> { TextState("Prowl cannot send system notifications") } actions: { ButtonState(action: .openSystemNotificationSettings) { TextState("Open System Settings") } ButtonState(role: .cancel, action: .dismiss) { TextState("Cancel") } } message: { TextState("Notification permission is turned off. Open System Settings to allow Prowl to send notifications.") } await store.receive(\.settings.showNotificationPermissionAlert) { $0.settings.alert = expectedAlert }
#expect(authorizationRequests.value == 1) #expect(store.state.settings.systemNotificationsEnabled == false) #expect(store.state.settings.alert == expectedAlert) }
@Test(.dependencies) func deniedStatusShowsAlertAndOpensSystemSettings() async { let storage = SettingsTestStorage() let authorizationRequests = LockIsolated(0) let openedSettings = LockIsolated(0) let store = withDependencies { $0.settingsFileStorage = storage.storage } operation: { TestStore(initialState: AppFeature.State()) { AppFeature() } withDependencies: { $0.systemNotificationClient.authorizationStatus = { .denied } $0.systemNotificationClient.requestAuthorization = { authorizationRequests.withValue { $0 += 1 } return SystemNotificationClient.AuthorizationRequestResult( granted: false, errorMessage: "Mock request error" ) } $0.systemNotificationClient.openSettings = { openedSettings.withValue { $0 += 1 } } } } store.exhaustivity = .off
await store.send(.settings(.binding(.set(\.systemNotificationsEnabled, true)))) { $0.settings.systemNotificationsEnabled = true } await store.receive(\.systemNotificationsPermissionFailed) await store.receive(\.settings.setSystemNotificationsEnabled) { $0.settings.systemNotificationsEnabled = false } let expectedAlert = AlertState<SettingsFeature.Alert> { TextState("Prowl cannot send system notifications") } actions: { ButtonState(action: .openSystemNotificationSettings) { TextState("Open System Settings") } ButtonState(role: .cancel, action: .dismiss) { TextState("Cancel") } } message: { TextState("Notification permission is turned off. Open System Settings to allow Prowl to send notifications.") } await store.receive(\.settings.showNotificationPermissionAlert) { $0.settings.alert = expectedAlert }
#expect(authorizationRequests.value == 0) #expect(store.state.settings.systemNotificationsEnabled == false) #expect(store.state.settings.alert == expectedAlert)
await store.send(.settings(.alert(.presented(.openSystemNotificationSettings)))) { $0.settings.alert = nil } await store.finish() #expect(openedSettings.value == 1) }
@Test(.dependencies) func notificationReceivedSendsSystemNotificationWhenEnabled() async { var globalSettings = GlobalSettings.default globalSettings.systemNotificationsEnabled = true let surfaceID = UUID() let sends = LockIsolated<[SystemNotificationSend]>([]) let store = TestStore( initialState: AppFeature.State( settings: SettingsFeature.State(settings: globalSettings) ) ) { AppFeature() } withDependencies: { $0.systemNotificationClient.send = { title, body, worktreeID, targetSurfaceID in sends.withValue { $0.append( SystemNotificationSend( title: title, body: body, worktreeID: worktreeID, surfaceID: targetSurfaceID ) ) } } } store.exhaustivity = .off
await store.send( .terminalEvent( .notificationReceived( worktreeID: "/tmp/repo/wt-1", surfaceID: surfaceID, title: "Done", body: "Build succeeded", isViewed: false ) ) ) await store.finish()
#expect(sends.value.count == 1) #expect(sends.value.first?.title == "Done") #expect(sends.value.first?.body == "Build succeeded") #expect(sends.value.first?.worktreeID == "/tmp/repo/wt-1") #expect(sends.value.first?.surfaceID == surfaceID) }
@Test(.dependencies) func notificationMutedForViewedSurfaceSkipsBannerSoundAndBounce() async { var globalSettings = GlobalSettings.default globalSettings.muteNotificationsForActiveSurface = true globalSettings.systemNotificationsEnabled = true globalSettings.notificationSound = .funk globalSettings.dockBounceMode = .once let sends = LockIsolated(0) let plays = LockIsolated(0) let bounces = LockIsolated(0) let store = TestStore( initialState: AppFeature.State( settings: SettingsFeature.State(settings: globalSettings) ) ) { AppFeature() } withDependencies: { $0.systemNotificationClient.send = { _, _, _, _ in sends.withValue { $0 += 1 } } $0.notificationSoundClient.play = { _ in plays.withValue { $0 += 1 } } $0.dockClient.bounce = { _ in bounces.withValue { $0 += 1 } } } store.exhaustivity = .off
await store.send( .terminalEvent( .notificationReceived( worktreeID: "/tmp/repo/wt-1", surfaceID: UUID(), title: "Done", body: "Build succeeded", isViewed: true ) ) ) await store.finish()
// The user is already looking at the pane, so no attention-grabbing channel fires. #expect(sends.value == 0) #expect(plays.value == 0) #expect(bounces.value == 0) }
@Test(.dependencies) func notificationForViewedSurfaceStillNotifiesWhenMuteDisabled() async { var globalSettings = GlobalSettings.default globalSettings.muteNotificationsForActiveSurface = false globalSettings.systemNotificationsEnabled = true let sends = LockIsolated(0) let store = TestStore( initialState: AppFeature.State( settings: SettingsFeature.State(settings: globalSettings) ) ) { AppFeature() } withDependencies: { $0.systemNotificationClient.send = { _, _, _, _ in sends.withValue { $0 += 1 } } } store.exhaustivity = .off
await store.send( .terminalEvent( .notificationReceived( worktreeID: "/tmp/repo/wt-1", surfaceID: UUID(), title: "Done", body: "Build succeeded", isViewed: true ) ) ) await store.finish()
// Mute is off, so a viewed surface still posts the banner. #expect(sends.value == 1) }
@Test(.dependencies) func notificationReceivedSkipsLocalSoundWhenSystemNotificationsEnabled() async { var globalSettings = GlobalSettings.default globalSettings.systemNotificationsEnabled = true let plays = LockIsolated(0) let store = TestStore( initialState: AppFeature.State( settings: SettingsFeature.State(settings: globalSettings) ) ) { AppFeature() } withDependencies: { $0.notificationSoundClient.play = { _ in plays.withValue { $0 += 1 } } } store.exhaustivity = .off
await store.send( .terminalEvent( .notificationReceived( worktreeID: "/tmp/repo/wt-1", surfaceID: UUID(), title: "Done", body: "Build succeeded", isViewed: false ) ) ) await store.finish()
#expect(plays.value == 0) }
@Test(.dependencies) func notificationReceivedPlaysLocalSoundWhenSystemNotificationsDisabled() async { var globalSettings = GlobalSettings.default globalSettings.systemNotificationsEnabled = false globalSettings.notificationSound = .funk let plays = LockIsolated<[NotificationSound]>([]) let sends = LockIsolated(0) let store = TestStore( initialState: AppFeature.State( settings: SettingsFeature.State(settings: globalSettings) ) ) { AppFeature() } withDependencies: { $0.notificationSoundClient.play = { sound in plays.withValue { $0.append(sound) } } $0.systemNotificationClient.send = { _, _, _, _ in sends.withValue { $0 += 1 } } } store.exhaustivity = .off
await store.send( .terminalEvent( .notificationReceived( worktreeID: "/tmp/repo/wt-1", surfaceID: UUID(), title: "Done", body: "Build succeeded", isViewed: false ) ) ) await store.finish()
// The selected sound reaches the player unchanged. #expect(plays.value == [.funk]) #expect(sends.value == 0) }
@Test(.dependencies) func notificationReceivedSkipsLocalSoundWhenNever() async { var globalSettings = GlobalSettings.default globalSettings.systemNotificationsEnabled = false globalSettings.notificationSound = .never let plays = LockIsolated<[NotificationSound]>([]) let sends = LockIsolated(0) let store = TestStore( initialState: AppFeature.State( settings: SettingsFeature.State(settings: globalSettings) ) ) { AppFeature() } withDependencies: { $0.notificationSoundClient.play = { sound in plays.withValue { $0.append(sound) } } $0.systemNotificationClient.send = { _, _, _, _ in sends.withValue { $0 += 1 } } } store.exhaustivity = .off
await store.send( .terminalEvent( .notificationReceived( worktreeID: "/tmp/repo/wt-1", surfaceID: UUID(), title: "Done", body: "Build succeeded", isViewed: false ) ) ) await store.finish()
#expect(plays.value.isEmpty) #expect(sends.value == 0) }}