diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index 0920d588..7afd48ae 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -1121,6 +1121,16 @@ class TabManager: ObservableObject { tabs.insert(tab, at: insertIndex) } + func moveTabToTopForNotification(_ tabId: UUID) { + guard let index = tabs.firstIndex(where: { $0.id == tabId }) else { return } + let pinnedCount = tabs.filter { $0.isPinned }.count + guard index != pinnedCount else { return } + let tab = tabs[index] + guard !tab.isPinned else { return } + tabs.remove(at: index) + tabs.insert(tab, at: pinnedCount) + } + func moveTabsToTop(_ tabIds: Set) { guard !tabIds.isEmpty else { return } let selectedTabs = tabs.filter { tabIds.contains($0.id) } diff --git a/Sources/TerminalNotificationStore.swift b/Sources/TerminalNotificationStore.swift index 5bb768cb..9cdcf1f9 100644 --- a/Sources/TerminalNotificationStore.swift +++ b/Sources/TerminalNotificationStore.swift @@ -671,6 +671,11 @@ final class TerminalNotificationStore: ObservableObject { private var notificationSettingsURLOpener: (URL) -> Void = { url in NSWorkspace.shared.open(url) } + private var notificationDeliveryHandler: (TerminalNotificationStore, TerminalNotification) -> Void = { + store, + notification in + store.scheduleUserNotification(notification) + } private var indexes = NotificationIndexes() private init() { @@ -843,7 +848,7 @@ final class TerminalNotificationStore: ObservableObject { } if WorkspaceAutoReorderSettings.isEnabled() { - AppDelegate.shared?.tabManager?.moveTabToTop(tabId) + AppDelegate.shared?.tabManager?.moveTabToTopForNotification(tabId) } let notification = TerminalNotification( @@ -862,7 +867,7 @@ final class TerminalNotificationStore: ObservableObject { center.removeDeliveredNotificationsOffMain(withIdentifiers: idsToClear) center.removePendingNotificationRequestsOffMain(withIdentifiers: idsToClear) } - scheduleUserNotification(notification) + notificationDeliveryHandler(self, notification) } func markRead(id: UUID) { @@ -1233,6 +1238,18 @@ final class TerminalNotificationStore: ObservableObject { hasPromptedForSettings = false } + func configureNotificationDeliveryHandlerForTesting( + _ handler: @escaping (TerminalNotificationStore, TerminalNotification) -> Void + ) { + notificationDeliveryHandler = handler + } + + func resetNotificationDeliveryHandlerForTesting() { + notificationDeliveryHandler = { store, notification in + store.scheduleUserNotification(notification) + } + } + func promptToEnableNotificationsForTesting() { promptToEnableNotifications() } diff --git a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift index 1f8981b6..724d1fa0 100644 --- a/cmuxTests/CmuxWebViewKeyEquivalentTests.swift +++ b/cmuxTests/CmuxWebViewKeyEquivalentTests.swift @@ -4233,6 +4233,58 @@ final class WorkspaceReorderTests: XCTestCase { } } +@MainActor +final class WorkspaceNotificationReorderTests: XCTestCase { + func testNotificationAutoReorderDoesNotMovePinnedWorkspace() { + let appDelegate = AppDelegate.shared ?? AppDelegate() + let manager = TabManager() + let notificationStore = TerminalNotificationStore.shared + + let originalTabManager = appDelegate.tabManager + let originalNotificationStore = appDelegate.notificationStore + let defaults = UserDefaults.standard + let originalAutoReorderSetting = defaults.object(forKey: WorkspaceAutoReorderSettings.key) + let originalAppFocusOverride = AppFocusState.overrideIsFocused + + notificationStore.replaceNotificationsForTesting([]) + notificationStore.configureNotificationDeliveryHandlerForTesting { _, _ in } + appDelegate.tabManager = manager + appDelegate.notificationStore = notificationStore + defaults.set(true, forKey: WorkspaceAutoReorderSettings.key) + AppFocusState.overrideIsFocused = false + + defer { + notificationStore.replaceNotificationsForTesting([]) + notificationStore.resetNotificationDeliveryHandlerForTesting() + appDelegate.tabManager = originalTabManager + appDelegate.notificationStore = originalNotificationStore + AppFocusState.overrideIsFocused = originalAppFocusOverride + if let originalAutoReorderSetting { + defaults.set(originalAutoReorderSetting, forKey: WorkspaceAutoReorderSettings.key) + } else { + defaults.removeObject(forKey: WorkspaceAutoReorderSettings.key) + } + } + + let firstPinned = manager.tabs[0] + manager.setPinned(firstPinned, pinned: true) + let secondPinned = manager.addWorkspace() + manager.setPinned(secondPinned, pinned: true) + let unpinned = manager.addWorkspace() + let expectedOrder = [firstPinned.id, secondPinned.id, unpinned.id] + + notificationStore.addNotification( + tabId: secondPinned.id, + surfaceId: nil, + title: "Build finished", + subtitle: "", + body: "Pinned workspaces should stay put" + ) + + XCTAssertEqual(manager.tabs.map(\.id), expectedOrder) + } +} + @MainActor final class TabManagerChildExitCloseTests: XCTestCase { func testChildExitOnLastPanelClosesSelectedWorkspaceAndKeepsIndexStable() {