diff --git a/Sources/TerminalNotificationStore.swift b/Sources/TerminalNotificationStore.swift index bd622967..b7ce2883 100644 --- a/Sources/TerminalNotificationStore.swift +++ b/Sources/TerminalNotificationStore.swift @@ -693,6 +693,10 @@ final class TerminalNotificationStore: ObservableObject { notification in store.scheduleUserNotification(notification) } + private var suppressedNotificationFeedbackHandler: (TerminalNotificationStore, TerminalNotification) -> Void = { + _, + _ in + } private var indexes = NotificationIndexes() private init() { @@ -1262,6 +1266,16 @@ final class TerminalNotificationStore: ObservableObject { } } + func configureSuppressedNotificationFeedbackHandlerForTesting( + _ handler: @escaping (TerminalNotificationStore, TerminalNotification) -> Void + ) { + suppressedNotificationFeedbackHandler = handler + } + + func resetSuppressedNotificationFeedbackHandlerForTesting() { + suppressedNotificationFeedbackHandler = { _, _ in } + } + func promptToEnableNotificationsForTesting() { promptToEnableNotifications() } diff --git a/cmuxTests/NotificationAndMenuBarTests.swift b/cmuxTests/NotificationAndMenuBarTests.swift index c519e3ab..5251fd50 100644 --- a/cmuxTests/NotificationAndMenuBarTests.swift +++ b/cmuxTests/NotificationAndMenuBarTests.swift @@ -37,6 +37,8 @@ final class NotificationDockBadgeTests: XCTestCase { override func tearDown() { TerminalNotificationStore.shared.resetNotificationSettingsPromptHooksForTesting() TerminalNotificationStore.shared.replaceNotificationsForTesting([]) + TerminalNotificationStore.shared.resetNotificationDeliveryHandlerForTesting() + TerminalNotificationStore.shared.resetSuppressedNotificationFeedbackHandlerForTesting() super.tearDown() } @@ -399,6 +401,55 @@ final class NotificationDockBadgeTests: XCTestCase { } } + func testFocusedTerminalNotificationStillRunsLocalSoundFeedbackWhenExternalDeliveryIsSuppressed() { + let appDelegate = AppDelegate.shared ?? AppDelegate() + let manager = TabManager() + let store = TerminalNotificationStore.shared + + let originalTabManager = appDelegate.tabManager + let originalNotificationStore = appDelegate.notificationStore + let originalAppFocusOverride = AppFocusState.overrideIsFocused + + var deliveredNotificationIDs: [UUID] = [] + var localFeedbackNotificationIDs: [UUID] = [] + + store.replaceNotificationsForTesting([]) + store.configureNotificationDeliveryHandlerForTesting { _, notification in + deliveredNotificationIDs.append(notification.id) + } + store.configureSuppressedNotificationFeedbackHandlerForTesting { _, notification in + localFeedbackNotificationIDs.append(notification.id) + } + appDelegate.tabManager = manager + appDelegate.notificationStore = store + AppFocusState.overrideIsFocused = true + + defer { + store.replaceNotificationsForTesting([]) + appDelegate.tabManager = originalTabManager + appDelegate.notificationStore = originalNotificationStore + AppFocusState.overrideIsFocused = originalAppFocusOverride + } + + guard let workspace = manager.selectedWorkspace, + let terminalPanel = workspace.focusedTerminalPanel else { + XCTFail("Expected selected workspace with a focused terminal panel") + return + } + + store.addNotification( + tabId: workspace.id, + surfaceId: terminalPanel.id, + title: "Unread", + subtitle: "", + body: "" + ) + + XCTAssertTrue(store.hasUnreadNotification(forTabId: workspace.id, surfaceId: terminalPanel.id)) + XCTAssertTrue(deliveredNotificationIDs.isEmpty) + XCTAssertEqual(localFeedbackNotificationIDs.count, 1) + } + func testNotificationAuthorizationStateMappingCoversKnownUNAuthorizationStatuses() { XCTAssertEqual(TerminalNotificationStore.authorizationState(from: .notDetermined), .notDetermined) XCTAssertEqual(TerminalNotificationStore.authorizationState(from: .denied), .denied)