From 6bdb0ce247d09c2c1486e691f107ceaddf9d3cb9 Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 18 Jun 2026 18:58:51 -0700 Subject: [PATCH 1/3] test: cover notification focus by surface tab id --- cmuxTests/TabManagerUnitTests.swift | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/cmuxTests/TabManagerUnitTests.swift b/cmuxTests/TabManagerUnitTests.swift index cbfbfe2ae711..07cbe21d284f 100644 --- a/cmuxTests/TabManagerUnitTests.swift +++ b/cmuxTests/TabManagerUnitTests.swift @@ -2219,6 +2219,29 @@ final class TabManagerNotificationFocusTests: XCTestCase { XCTAssertEqual(workspace.focusedPanelId, rightPanel.id, "Expected notification target panel to be focused") } + func testFocusTabFromNotificationAcceptsBonsplitSurfaceIdForNestedTabNotification() throws { + let manager = TabManager() + guard let workspace = manager.selectedWorkspace, + let firstPanelId = workspace.focusedPanelId, + let paneId = workspace.bonsplitController.focusedPaneId else { + XCTFail("Expected selected workspace with focused panel") + return + } + _ = workspace.newTerminalSurface(inPane: paneId, focus: false) + let thirdPanel = try XCTUnwrap(workspace.newTerminalSurface(inPane: paneId, focus: false)) + let thirdSurfaceId = try XCTUnwrap(workspace.surfaceIdFromPanelId(thirdPanel.id)?.uuid) + + workspace.focusPanel(firstPanelId) + XCTAssertEqual(workspace.focusedPanelId, firstPanelId) + + XCTAssertTrue(manager.focusTabFromNotification(workspace.id, surfaceId: thirdSurfaceId)) + drainMainQueue() + drainMainQueue() + + XCTAssertEqual(workspace.focusedPanelId, thirdPanel.id) + XCTAssertEqual(workspace.bonsplitController.selectedTab(inPane: paneId)?.id.uuid, thirdSurfaceId) + } + func testFocusTabFromNotificationReturnsFalseForMissingPanel() { let manager = TabManager() guard let workspace = manager.selectedWorkspace else { From 0ec0faec4112281dacd7ebc658c5ea2068db2163 Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 18 Jun 2026 18:59:41 -0700 Subject: [PATCH 2/3] fix: focus notification surface tab ids --- Sources/TabManager.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index 95a5376dba9d..52037e5209ad 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -3396,16 +3396,17 @@ class TabManager: ObservableObject { #endif return false } - if let surfaceId, tab.panels[surfaceId] == nil { + let requestedPanelId = surfaceId.flatMap { panelId(forSurfaceOrPanelId: $0, in: tab) } + if let requestedSurfaceId = surfaceId, requestedPanelId == nil { #if DEBUG cmuxDebugLog( "notification.focus.fail tab=\(tabId.uuidString.prefix(5)) " + - "panel=\(surfaceId.uuidString.prefix(5)) reason=missingPanel" + "panel=\(requestedSurfaceId.uuidString.prefix(5)) reason=missingPanel" ) #endif return false } - let desiredPanelId = surfaceId ?? tab.focusedPanelId + let desiredPanelId = requestedPanelId ?? tab.focusedPanelId #if DEBUG if let desiredPanelId { AppDelegate.shared?.armJumpUnreadFocusRecord(tabId: tabId, surfaceId: desiredPanelId) From 0d351715f7b9e284cfac9ee271641ae7c8f5729c Mon Sep 17 00:00:00 2001 From: lawrencecchen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 18 Jun 2026 21:19:19 -0700 Subject: [PATCH 3/3] test: move notification focus regression to swift testing --- Sources/TabManager.swift | 7 +--- cmux.xcodeproj/project.pbxproj | 4 ++ ...agerNotificationFocusRegressionTests.swift | 38 +++++++++++++++++++ cmuxTests/TabManagerUnitTests.swift | 23 ----------- 4 files changed, 44 insertions(+), 28 deletions(-) create mode 100644 cmuxTests/TabManagerNotificationFocusRegressionTests.swift diff --git a/Sources/TabManager.swift b/Sources/TabManager.swift index 52037e5209ad..fcf154a0325f 100644 --- a/Sources/TabManager.swift +++ b/Sources/TabManager.swift @@ -3397,12 +3397,9 @@ class TabManager: ObservableObject { return false } let requestedPanelId = surfaceId.flatMap { panelId(forSurfaceOrPanelId: $0, in: tab) } - if let requestedSurfaceId = surfaceId, requestedPanelId == nil { + if let surfaceId, requestedPanelId == nil { #if DEBUG - cmuxDebugLog( - "notification.focus.fail tab=\(tabId.uuidString.prefix(5)) " + - "panel=\(requestedSurfaceId.uuidString.prefix(5)) reason=missingPanel" - ) + cmuxDebugLog("notification.focus.fail tab=\(tabId.uuidString.prefix(5)) panel=\(surfaceId.uuidString.prefix(5)) reason=missingPanel") #endif return false } diff --git a/cmux.xcodeproj/project.pbxproj b/cmux.xcodeproj/project.pbxproj index 1e87c91fb5f1..2cfe1ae1ca0a 100644 --- a/cmux.xcodeproj/project.pbxproj +++ b/cmux.xcodeproj/project.pbxproj @@ -768,6 +768,7 @@ C5B6A10000000000000000B1 /* TabManager+SidebarGitHosting.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5B6A10000000000000000B2 /* TabManager+SidebarGitHosting.swift */; }; 604500100000000000000007 /* TabManager+WindowTitle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 604500100000000000000008 /* TabManager+WindowTitle.swift */; }; A5001003 /* TabManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001013 /* TabManager.swift */; }; + C0DE64160000000000000001 /* TabManagerNotificationFocusRegressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DE64160000000000000002 /* TabManagerNotificationFocusRegressionTests.swift */; }; 2BB56A710BB1FC50367E5BCF /* TabManagerSessionSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10D684CFFB8CDEF89CE2D9E1 /* TabManagerSessionSnapshotTests.swift */; }; B6BF3DC98DB1495E57900199 /* TabManagerUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42092CDB2109E250F7F2A76E /* TabManagerUnitTests.swift */; }; C7A507000000000000000002 /* TaskManagerResourcesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7A507000000000000000001 /* TaskManagerResourcesTests.swift */; }; @@ -1720,6 +1721,7 @@ C5B6A10000000000000000B2 /* TabManager+SidebarGitHosting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TabManager+SidebarGitHosting.swift"; sourceTree = ""; }; 604500100000000000000008 /* TabManager+WindowTitle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TabManager+WindowTitle.swift"; sourceTree = ""; }; A5001013 /* TabManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabManager.swift; sourceTree = ""; }; + C0DE64160000000000000002 /* TabManagerNotificationFocusRegressionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabManagerNotificationFocusRegressionTests.swift; sourceTree = ""; }; 10D684CFFB8CDEF89CE2D9E1 /* TabManagerSessionSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabManagerSessionSnapshotTests.swift; sourceTree = ""; }; 42092CDB2109E250F7F2A76E /* TabManagerUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabManagerUnitTests.swift; sourceTree = ""; }; C7A507000000000000000001 /* TaskManagerResourcesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskManagerResourcesTests.swift; sourceTree = ""; }; @@ -2903,6 +2905,7 @@ 596100000000000000000006 /* TerminalNotificationStore+NativeNotificationDeliveryTesting.swift */, D7AB00000000000000B020 /* NotificationDismissSyncTests.swift */, 4E5F60720000000000000002 /* NotificationSoundSettingsTests.swift */, + C0DE64160000000000000002 /* TabManagerNotificationFocusRegressionTests.swift */, 42092CDB2109E250F7F2A76E /* TabManagerUnitTests.swift */, C0793DC7D7B61CF54886EC36 /* RemoteTmuxControlParserTests.swift */, B0555302B0555302B0555302 /* RemoteTmuxControlStreamParserBudgetTests.swift */, @@ -4255,6 +4258,7 @@ 62270F3DCECB4787D789CCE3 /* SidebarWorkspaceSnapshotRefreshPolicyTests.swift in Sources */, F8000000A1B2C3D4E5F60718 /* SocketControlPasswordStoreTests.swift in Sources */, F6355600A1B2C3D4E5F60718 /* SSHStartupSignalLifecycleTests.swift in Sources */, + C0DE64160000000000000001 /* TabManagerNotificationFocusRegressionTests.swift in Sources */, 2BB56A710BB1FC50367E5BCF /* TabManagerSessionSnapshotTests.swift in Sources */, B6BF3DC98DB1495E57900199 /* TabManagerUnitTests.swift in Sources */, C7A507000000000000000002 /* TaskManagerResourcesTests.swift in Sources */, diff --git a/cmuxTests/TabManagerNotificationFocusRegressionTests.swift b/cmuxTests/TabManagerNotificationFocusRegressionTests.swift new file mode 100644 index 000000000000..730629328007 --- /dev/null +++ b/cmuxTests/TabManagerNotificationFocusRegressionTests.swift @@ -0,0 +1,38 @@ +import Foundation +import Testing + +#if canImport(cmux_DEV) +@testable import cmux_DEV +#elseif canImport(cmux) +@testable import cmux +#endif + +@MainActor +@Suite(.serialized) +struct TabManagerNotificationFocusRegressionTests { + @Test + func focusTabFromNotificationAcceptsBonsplitSurfaceIdForNestedTabNotification() async throws { + let manager = TabManager() + let workspace = try #require(manager.selectedWorkspace) + let firstPanelId = try #require(workspace.focusedPanelId) + let paneId = try #require(workspace.bonsplitController.focusedPaneId) + _ = workspace.newTerminalSurface(inPane: paneId, focus: false) + let thirdPanel = try #require(workspace.newTerminalSurface(inPane: paneId, focus: false)) + let thirdSurfaceId = try #require(workspace.surfaceIdFromPanelId(thirdPanel.id)?.uuid) + + workspace.focusPanel(firstPanelId) + #expect(workspace.focusedPanelId == firstPanelId) + #expect(manager.focusTabFromNotification(workspace.id, surfaceId: thirdSurfaceId)) + await drainMainQueue() + await drainMainQueue() + + #expect(workspace.focusedPanelId == thirdPanel.id) + #expect(workspace.bonsplitController.selectedTab(inPane: paneId)?.id.uuid == thirdSurfaceId) + } + + private func drainMainQueue() async { + await withCheckedContinuation { (continuation: CheckedContinuation) in + DispatchQueue.main.async { continuation.resume() } + } + } +} diff --git a/cmuxTests/TabManagerUnitTests.swift b/cmuxTests/TabManagerUnitTests.swift index 07cbe21d284f..cbfbfe2ae711 100644 --- a/cmuxTests/TabManagerUnitTests.swift +++ b/cmuxTests/TabManagerUnitTests.swift @@ -2219,29 +2219,6 @@ final class TabManagerNotificationFocusTests: XCTestCase { XCTAssertEqual(workspace.focusedPanelId, rightPanel.id, "Expected notification target panel to be focused") } - func testFocusTabFromNotificationAcceptsBonsplitSurfaceIdForNestedTabNotification() throws { - let manager = TabManager() - guard let workspace = manager.selectedWorkspace, - let firstPanelId = workspace.focusedPanelId, - let paneId = workspace.bonsplitController.focusedPaneId else { - XCTFail("Expected selected workspace with focused panel") - return - } - _ = workspace.newTerminalSurface(inPane: paneId, focus: false) - let thirdPanel = try XCTUnwrap(workspace.newTerminalSurface(inPane: paneId, focus: false)) - let thirdSurfaceId = try XCTUnwrap(workspace.surfaceIdFromPanelId(thirdPanel.id)?.uuid) - - workspace.focusPanel(firstPanelId) - XCTAssertEqual(workspace.focusedPanelId, firstPanelId) - - XCTAssertTrue(manager.focusTabFromNotification(workspace.id, surfaceId: thirdSurfaceId)) - drainMainQueue() - drainMainQueue() - - XCTAssertEqual(workspace.focusedPanelId, thirdPanel.id) - XCTAssertEqual(workspace.bonsplitController.selectedTab(inPane: paneId)?.id.uuid, thirdSurfaceId) - } - func testFocusTabFromNotificationReturnsFalseForMissingPanel() { let manager = TabManager() guard let workspace = manager.selectedWorkspace else {