import XCTest #if canImport(cmux_DEV) @testable import cmux_DEV #elseif canImport(cmux) @testable import cmux #endif private let appDelegateLastSurfaceCloseShortcutDefaultsKey = "closeWorkspaceOnLastSurfaceShortcut" @MainActor final class AppDelegateShortcutRoutingTests: XCTestCase { private var savedShortcutsByAction: [KeyboardShortcutSettings.Action: StoredShortcut] = [:] private var actionsWithPersistedShortcut: Set = [] override func setUp() { super.setUp() // Prevent a single hanging test from consuming the entire CI timeout budget. executionTimeAllowance = 30 actionsWithPersistedShortcut = Set( KeyboardShortcutSettings.Action.allCases.filter { UserDefaults.standard.object(forKey: $0.defaultsKey) != nil } ) savedShortcutsByAction = Dictionary( uniqueKeysWithValues: actionsWithPersistedShortcut.map { action in (action, KeyboardShortcutSettings.shortcut(for: action)) } ) KeyboardShortcutSettings.resetAll() } override func tearDown() { AppDelegate.shared?.shortcutLayoutCharacterProvider = KeyboardLayout.character(forKeyCode:modifierFlags:) AppDelegate.shared?.debugCloseMainWindowConfirmationHandler = nil AppDelegate.shared?.dismissNotificationsPopoverIfShown() RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) for action in KeyboardShortcutSettings.Action.allCases { if actionsWithPersistedShortcut.contains(action), let savedShortcut = savedShortcutsByAction[action] { KeyboardShortcutSettings.setShortcut(savedShortcut, for: action) } else { KeyboardShortcutSettings.resetShortcut(for: action) } } super.tearDown() } func testCmdNUsesEventWindowContextWhenActiveManagerIsStale() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let firstWindowId = appDelegate.createMainWindow() let secondWindowId = appDelegate.createMainWindow() defer { closeWindow(withId: firstWindowId) closeWindow(withId: secondWindowId) } guard let firstManager = appDelegate.tabManagerFor(windowId: firstWindowId), let secondManager = appDelegate.tabManagerFor(windowId: secondWindowId), let secondWindow = window(withId: secondWindowId) else { XCTFail("Expected both window contexts to exist") return } let firstCount = firstManager.tabs.count let secondCount = secondManager.tabs.count XCTAssertTrue(appDelegate.focusMainWindow(windowId: firstWindowId)) guard let event = NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: [.command], timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: secondWindow.windowNumber, context: nil, characters: "n", charactersIgnoringModifiers: "n", isARepeat: false, keyCode: 45 ) else { XCTFail("Failed to construct Cmd+N event") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif XCTAssertEqual(firstManager.tabs.count, firstCount, "Cmd+N should not add workspace to stale active window") XCTAssertEqual(secondManager.tabs.count, secondCount + 1, "Cmd+N should add workspace to the event's window") } func testAddWorkspaceInPreferredMainWindowIgnoresStaleTabManagerPointer() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let firstWindowId = appDelegate.createMainWindow() let secondWindowId = appDelegate.createMainWindow() defer { closeWindow(withId: firstWindowId) closeWindow(withId: secondWindowId) } guard let firstManager = appDelegate.tabManagerFor(windowId: firstWindowId), let secondManager = appDelegate.tabManagerFor(windowId: secondWindowId), let secondWindow = window(withId: secondWindowId) else { XCTFail("Expected both window contexts to exist") return } let firstCount = firstManager.tabs.count let secondCount = secondManager.tabs.count secondWindow.makeKeyAndOrderFront(nil) RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) // Force a stale app-level pointer to a different manager. appDelegate.tabManager = firstManager XCTAssertTrue(appDelegate.tabManager === firstManager) _ = appDelegate.addWorkspaceInPreferredMainWindow() XCTAssertEqual(firstManager.tabs.count, firstCount, "Stale pointer must not receive menu-driven workspace creation") XCTAssertEqual(secondManager.tabs.count, secondCount + 1, "Workspace creation should target key/main window context") } func testCmdNResolvesEventWindowWhenObjectKeyLookupIsMismatched() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let firstWindowId = appDelegate.createMainWindow() let secondWindowId = appDelegate.createMainWindow() defer { closeWindow(withId: firstWindowId) closeWindow(withId: secondWindowId) } guard let firstManager = appDelegate.tabManagerFor(windowId: firstWindowId), let secondManager = appDelegate.tabManagerFor(windowId: secondWindowId), let secondWindow = window(withId: secondWindowId) else { XCTFail("Expected both window contexts to exist") return } secondWindow.makeKeyAndOrderFront(nil) RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) #if DEBUG XCTAssertTrue(appDelegate.debugInjectWindowContextKeyMismatch(windowId: secondWindowId)) #else XCTFail("debugInjectWindowContextKeyMismatch is only available in DEBUG") #endif // Ensure stale active-manager pointer does not mask routing errors. appDelegate.tabManager = firstManager let firstCount = firstManager.tabs.count let secondCount = secondManager.tabs.count guard let event = NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: [.command], timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: secondWindow.windowNumber, context: nil, characters: "n", charactersIgnoringModifiers: "n", isARepeat: false, keyCode: 45 ) else { XCTFail("Failed to construct Cmd+N event") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif XCTAssertEqual(firstManager.tabs.count, firstCount, "Cmd+N should not route to another window when object-key lookup misses") XCTAssertEqual(secondManager.tabs.count, secondCount + 1, "Cmd+N should still route by event window metadata when object-key lookup misses") } func testAddWorkspaceInPreferredMainWindowUsesKeyWindowWhenObjectKeyLookupIsMismatched() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let firstWindowId = appDelegate.createMainWindow() let secondWindowId = appDelegate.createMainWindow() defer { closeWindow(withId: firstWindowId) closeWindow(withId: secondWindowId) } guard let firstManager = appDelegate.tabManagerFor(windowId: firstWindowId), let secondManager = appDelegate.tabManagerFor(windowId: secondWindowId), let secondWindow = window(withId: secondWindowId) else { XCTFail("Expected both window contexts to exist") return } secondWindow.makeKeyAndOrderFront(nil) RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) #if DEBUG XCTAssertTrue(appDelegate.debugInjectWindowContextKeyMismatch(windowId: secondWindowId)) #else XCTFail("debugInjectWindowContextKeyMismatch is only available in DEBUG") #endif // Stale pointer should not receive the new workspace. appDelegate.tabManager = firstManager let firstCount = firstManager.tabs.count let secondCount = secondManager.tabs.count _ = appDelegate.addWorkspaceInPreferredMainWindow() XCTAssertEqual(firstManager.tabs.count, firstCount, "Menu-driven add workspace should not route to stale window") XCTAssertEqual(secondManager.tabs.count, secondCount + 1, "Menu-driven add workspace should still route to key window context when object-key lookup misses") } func testAddWorkspaceInPreferredMainWindowPrunesOrphanedContextWithoutLiveWindow() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let orphanWindowId = UUID() let orphanManager = TabManager() let orphanSidebarState = SidebarState() let orphanSidebarSelectionState = SidebarSelectionState() autoreleasepool { var orphanWindow: NSWindow? = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 320, height: 240), styleMask: [.titled, .closable, .resizable], backing: .buffered, defer: false ) orphanWindow?.identifier = NSUserInterfaceItemIdentifier("cmux.main.\(orphanWindowId.uuidString)") appDelegate.registerMainWindow( orphanWindow!, windowId: orphanWindowId, tabManager: orphanManager, sidebarState: orphanSidebarState, sidebarSelectionState: orphanSidebarSelectionState ) orphanWindow = nil } RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) XCTAssertNil(appDelegate.mainWindow(for: orphanWindowId), "Test precondition: orphaned context should not have a live window") let orphanCount = orphanManager.tabs.count XCTAssertNil( appDelegate.addWorkspaceInPreferredMainWindow(), "Workspace creation should refuse orphaned contexts with no live window" ) XCTAssertEqual(orphanManager.tabs.count, orphanCount, "Orphaned manager must not receive a new workspace") XCTAssertNil(appDelegate.tabManagerFor(windowId: orphanWindowId), "Orphaned context should be pruned after failed resolution") } func testCustomCmdTNewWorkspacePrunesOrphanedContextWithoutLiveWindow() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let existingWindowIds = mainWindowIds() let orphanWindowId = UUID() let orphanManager = TabManager() let orphanSidebarState = SidebarState() let orphanSidebarSelectionState = SidebarSelectionState() autoreleasepool { var orphanWindow: NSWindow? = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 320, height: 240), styleMask: [.titled, .closable, .resizable], backing: .buffered, defer: false ) orphanWindow?.identifier = NSUserInterfaceItemIdentifier("cmux.main.\(orphanWindowId.uuidString)") appDelegate.registerMainWindow( orphanWindow!, windowId: orphanWindowId, tabManager: orphanManager, sidebarState: orphanSidebarState, sidebarSelectionState: orphanSidebarSelectionState ) orphanWindow = nil } RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) XCTAssertNil(appDelegate.mainWindow(for: orphanWindowId), "Test precondition: orphaned context should not have a live window") let orphanCount = orphanManager.tabs.count let remappedCmdT = StoredShortcut(key: "t", command: true, shift: false, option: false, control: false) withTemporaryShortcut(action: .newTab, shortcut: remappedCmdT) { guard let event = makeKeyDownEvent( key: "t", modifiers: [.command], keyCode: 17, // kVK_ANSI_T windowNumber: 0 ) else { XCTFail("Failed to construct remapped Cmd+T event") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) } XCTAssertEqual(orphanManager.tabs.count, orphanCount, "Orphaned manager must not receive a new workspace from remapped Cmd+T") XCTAssertNil(appDelegate.tabManagerFor(windowId: orphanWindowId), "Remapped Cmd+T should prune the orphaned context after failed resolution") let createdWindowIds = mainWindowIds().subtracting(existingWindowIds) for windowId in createdWindowIds { closeWindow(withId: windowId) } } func testCmdDigitRoutesToEventWindowWhenActiveManagerIsStale() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let firstWindowId = appDelegate.createMainWindow() let secondWindowId = appDelegate.createMainWindow() defer { closeWindow(withId: firstWindowId) closeWindow(withId: secondWindowId) } guard let firstManager = appDelegate.tabManagerFor(windowId: firstWindowId), let secondManager = appDelegate.tabManagerFor(windowId: secondWindowId), let secondWindow = window(withId: secondWindowId) else { XCTFail("Expected both window contexts to exist") return } _ = firstManager.addTab(select: true) _ = secondManager.addTab(select: true) guard let firstSelectedBefore = firstManager.selectedTabId, let secondSelectedBefore = secondManager.selectedTabId else { XCTFail("Expected selected tabs in both windows") return } guard let secondFirstTabId = secondManager.tabs.first?.id else { XCTFail("Expected at least one tab in second window") return } appDelegate.tabManager = firstManager XCTAssertTrue(appDelegate.tabManager === firstManager) guard let event = makeKeyDownEvent( key: "1", modifiers: [.command], keyCode: 18, // kVK_ANSI_1 windowNumber: secondWindow.windowNumber ) else { XCTFail("Failed to construct Cmd+1 event") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif XCTAssertEqual(firstManager.selectedTabId, firstSelectedBefore, "Cmd+1 must not select a tab in stale active window") XCTAssertNotEqual(secondManager.selectedTabId, secondSelectedBefore, "Cmd+1 should change tab selection in event window") XCTAssertEqual(secondManager.selectedTabId, secondFirstTabId, "Cmd+1 should select first tab in the event window") XCTAssertTrue(appDelegate.tabManager === secondManager, "Shortcut routing should retarget active manager to event window") } func testCmdTRoutesToEventWindowWhenActiveManagerIsStale() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let firstWindowId = appDelegate.createMainWindow() let secondWindowId = appDelegate.createMainWindow() defer { closeWindow(withId: firstWindowId) closeWindow(withId: secondWindowId) } guard let firstManager = appDelegate.tabManagerFor(windowId: firstWindowId), let secondManager = appDelegate.tabManagerFor(windowId: secondWindowId), let secondWindow = window(withId: secondWindowId), let firstWorkspace = firstManager.selectedWorkspace, let secondWorkspace = secondManager.selectedWorkspace else { XCTFail("Expected both window contexts to exist") return } let firstSurfaceCount = firstWorkspace.panels.count let secondSurfaceCount = secondWorkspace.panels.count appDelegate.tabManager = firstManager XCTAssertTrue(appDelegate.tabManager === firstManager) guard let event = makeKeyDownEvent( key: "t", modifiers: [.command], keyCode: 17, // kVK_ANSI_T windowNumber: secondWindow.windowNumber ) else { XCTFail("Failed to construct Cmd+T event") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) XCTAssertEqual(firstWorkspace.panels.count, firstSurfaceCount, "Cmd+T must not create a surface in stale active window") XCTAssertEqual(secondWorkspace.panels.count, secondSurfaceCount + 1, "Cmd+T should create a surface in the event window") XCTAssertTrue(appDelegate.tabManager === secondManager, "Shortcut routing should retarget active manager to event window") } func testCmdDRoutesSplitToEventWindowWhenKeyWindowIsDifferent() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let firstWindowId = appDelegate.createMainWindow() let secondWindowId = appDelegate.createMainWindow() defer { closeWindow(withId: firstWindowId) closeWindow(withId: secondWindowId) } guard let firstManager = appDelegate.tabManagerFor(windowId: firstWindowId), let secondManager = appDelegate.tabManagerFor(windowId: secondWindowId), let firstWindow = window(withId: firstWindowId), let secondWindow = window(withId: secondWindowId), let firstWorkspace = firstManager.selectedWorkspace, let secondWorkspace = secondManager.selectedWorkspace else { XCTFail("Expected both window contexts to exist") return } firstWindow.makeKeyAndOrderFront(nil) RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) let firstSurfaceCount = firstWorkspace.panels.count let secondSurfaceCount = secondWorkspace.panels.count appDelegate.tabManager = firstManager XCTAssertTrue(appDelegate.tabManager === firstManager) guard let event = makeKeyDownEvent( key: "d", modifiers: [.command], keyCode: 2, // kVK_ANSI_D windowNumber: secondWindow.windowNumber ) else { XCTFail("Failed to construct Cmd+D event") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) XCTAssertEqual(firstWorkspace.panels.count, firstSurfaceCount, "Cmd+D must not create a split in the stale key window") XCTAssertEqual(secondWorkspace.panels.count, secondSurfaceCount + 1, "Cmd+D should create a split in the event window") XCTAssertTrue(appDelegate.tabManager === secondManager, "Split shortcut routing should keep the event window active") } func testPerformSplitShortcutSplitsFocusedTerminalSurfaceWhenSelectedWorkspaceIsStale() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId), let manager = appDelegate.tabManagerFor(windowId: windowId), let workspace = manager.selectedWorkspace, let leftPanelId = workspace.focusedPanelId, let leftPanel = workspace.terminalPanel(for: leftPanelId) else { XCTFail("Expected split terminal panels") return } let originalPanelIds = Set(workspace.panels.keys) guard let rightPanel = workspace.newTerminalSplit(from: leftPanelId, orientation: .horizontal) else { XCTFail("Expected split terminal panels") return } RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) guard let leftPaneBefore = workspace.paneId(forPanelId: leftPanel.id), let rightPaneBefore = workspace.paneId(forPanelId: rightPanel.id) else { XCTFail("Expected split pane IDs") return } let layoutBefore = workspace.bonsplitController.layoutSnapshot() guard let leftPaneBeforeFrame = layoutBefore.panes.first(where: { $0.paneId == leftPaneBefore.id.uuidString })?.frame, let rightPaneBeforeFrame = layoutBefore.panes.first(where: { $0.paneId == rightPaneBefore.id.uuidString })?.frame else { XCTFail("Expected pane frames before shortcut split") return } XCTAssertLessThan(leftPaneBeforeFrame.x, rightPaneBeforeFrame.x, "Expected baseline layout to start left-to-right") guard let leftSurfaceView = surfaceView(in: leftPanel.hostedView) else { XCTFail("Expected left terminal surface view") return } window.makeKeyAndOrderFront(nil) RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) workspace.focusPanel(rightPanel.id) XCTAssertEqual(workspace.focusedPanelId, rightPanel.id, "Expected Bonsplit selection to stay on the right pane") leftPanel.hostedView.suppressReparentFocus() XCTAssertTrue(window.makeFirstResponder(leftSurfaceView)) leftPanel.hostedView.clearSuppressReparentFocus() XCTAssertTrue(window.firstResponder === leftSurfaceView, "Expected left Ghostty surface to stay first responder") XCTAssertEqual(workspace.focusedPanelId, rightPanel.id, "Expected selected pane to stay stale after first-responder change") XCTAssertEqual(leftSurfaceView.tabId, workspace.id, "Expected focused Ghostty view to keep its workspace ID") XCTAssertEqual(leftSurfaceView.terminalSurface?.id, leftPanel.id, "Expected focused Ghostty view to keep its surface ID") XCTAssertTrue( appDelegate.performSplitShortcut(direction: .right, preferredWindow: window), "Split shortcut should use the focused terminal surface even when selectedTabId is stale" ) RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.15)) let newPanelIds = Set(workspace.panels.keys) .subtracting(originalPanelIds) .subtracting([rightPanel.id]) guard newPanelIds.count == 1, let newPanelId = newPanelIds.first else { XCTFail("Expected exactly one shortcut-created split panel") return } guard let newPaneId = workspace.paneId(forPanelId: newPanelId), let rightPaneAfter = workspace.paneId(forPanelId: rightPanel.id) else { XCTFail("Expected pane IDs after shortcut split") return } let layoutAfter = workspace.bonsplitController.layoutSnapshot() guard let newPaneFrame = layoutAfter.panes.first(where: { $0.paneId == newPaneId.id.uuidString })?.frame, let rightPaneAfterFrame = layoutAfter.panes.first(where: { $0.paneId == rightPaneAfter.id.uuidString })?.frame else { XCTFail("Expected pane frames after shortcut split") return } XCTAssertEqual(layoutAfter.panes.count, 3, "Cmd+D should create a third pane") XCTAssertLessThan( newPaneFrame.x, rightPaneAfterFrame.x, "Cmd+D should split the focused left terminal pane, not the stale selected right pane" ) } func testCmdCtrlWPromptsBeforeClosingWindow() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let targetWindow = window(withId: windowId) else { XCTFail("Expected test window") return } var promptedWindow: NSWindow? appDelegate.debugCloseMainWindowConfirmationHandler = { candidate in promptedWindow = candidate return false } guard let event = makeKeyDownEvent( key: "w", modifiers: [.command, .control], keyCode: 13, windowNumber: targetWindow.windowNumber ) else { XCTFail("Failed to construct Cmd+Ctrl+W event") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) XCTAssertTrue(promptedWindow === targetWindow, "Cmd+Ctrl+W should prompt for the target main window") XCTAssertNotNil(self.window(withId: windowId), "Cancelling the confirmation should keep the window open") } func testCmdCtrlWClosesWindowAfterConfirmation() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() guard let targetWindow = window(withId: windowId) else { XCTFail("Expected test window") return } appDelegate.debugCloseMainWindowConfirmationHandler = { _ in true } guard let event = makeKeyDownEvent( key: "w", modifiers: [.command, .control], keyCode: 13, windowNumber: targetWindow.windowNumber ) else { XCTFail("Failed to construct Cmd+Ctrl+W event") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) XCTAssertNil(self.window(withId: windowId), "Confirming Cmd+Ctrl+W should close the window") } // NOTE: This test is skipped in CI via -skip-testing in ci.yml because closing // the last Ghostty surface tears down the PTY/shell, which blocks indefinitely // on headless runners. The xcodebuild test host doesn't inherit CI env vars, // so XCTSkip can't detect CI from inside the test. func testCmdWClosesWindowWhenClosingLastSurfaceInLastWorkspace() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } // Auto-confirm window close to avoid a modal dialog that blocks the RunLoop. appDelegate.debugCloseMainWindowConfirmationHandler = { _ in true } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let targetWindow = window(withId: windowId), let manager = appDelegate.tabManagerFor(windowId: windowId) else { XCTFail("Expected test window and manager") return } XCTAssertEqual(manager.tabs.count, 1) XCTAssertEqual(manager.tabs[0].panels.count, 1) guard let event = makeKeyDownEvent( key: "w", modifiers: [.command], keyCode: 13, windowNumber: targetWindow.windowNumber ) else { XCTFail("Failed to construct Cmd+W event") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) XCTAssertNil( self.window(withId: windowId), "Cmd+W on the last surface in the last workspace should close the window" ) } func testCmdWKeepsLastSurfaceWorkspaceOpenWhenKeepWorkspaceOpenPreferenceIsEnabled() throws { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let defaults = UserDefaults.standard let originalSetting = defaults.object(forKey: appDelegateLastSurfaceCloseShortcutDefaultsKey) defaults.set(false, forKey: appDelegateLastSurfaceCloseShortcutDefaultsKey) defer { if let originalSetting { defaults.set(originalSetting, forKey: appDelegateLastSurfaceCloseShortcutDefaultsKey) } else { defaults.removeObject(forKey: appDelegateLastSurfaceCloseShortcutDefaultsKey) } } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let targetWindow = window(withId: windowId), let manager = appDelegate.tabManagerFor(windowId: windowId), let workspace = manager.selectedWorkspace, let initialPanelId = workspace.focusedPanelId else { XCTFail("Expected test window, manager, workspace, and focused panel") return } guard let event = makeKeyDownEvent( key: "w", modifiers: [.command], keyCode: 13, windowNumber: targetWindow.windowNumber ) else { XCTFail("Failed to construct Cmd+W event") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) XCTAssertNotNil( self.window(withId: windowId), "Cmd+W should keep the window open when the keep-workspace-open preference is enabled" ) XCTAssertEqual(manager.tabs.count, 1) XCTAssertEqual(manager.selectedTabId, workspace.id) XCTAssertNil(workspace.panels[initialPanelId]) XCTAssertEqual(workspace.panels.count, 1) XCTAssertNotEqual(workspace.focusedPanelId, initialPanelId) } func testCmdWClosesAuxiliaryWindowInsteadOfMainTerminalPanel() throws { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } XCTAssertNotNil(window(withId: windowId), "Expected test window") guard let manager = appDelegate.tabManagerFor(windowId: windowId) else { XCTFail("Expected test manager") return } let mainWorkspaceCount = manager.tabs.count let auxiliaryWindow = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 360, height: 240), styleMask: [.titled, .closable, .miniaturizable], backing: .buffered, defer: false ) auxiliaryWindow.isReleasedWhenClosed = false auxiliaryWindow.identifier = NSUserInterfaceItemIdentifier("cmux.about") auxiliaryWindow.makeKeyAndOrderFront(nil) RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) defer { if auxiliaryWindow.isVisible { auxiliaryWindow.performClose(nil) RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) } } guard let event = makeKeyDownEvent( key: "w", modifiers: [.command], keyCode: 13, windowNumber: auxiliaryWindow.windowNumber ) else { XCTFail("Failed to construct Cmd+W event") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) #else throw XCTSkip("debugHandleCustomShortcut is only available in DEBUG builds") #endif RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) XCTAssertFalse(auxiliaryWindow.isVisible, "Cmd+W should close the auxiliary window") XCTAssertNotNil(self.window(withId: windowId), "Cmd+W in auxiliary window should not close the main window") XCTAssertEqual(manager.tabs.count, mainWorkspaceCount, "Cmd+W in auxiliary window should not close a terminal panel") XCTAssertNotEqual(NSApp.keyWindow?.identifier?.rawValue, "cmux.about", "Closed auxiliary window should not remain key") } func testCmdPhysicalIWithDvorakCharactersDoesNotTriggerShowNotifications() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } withTemporaryShortcut(action: .showNotifications) { // Dvorak: physical ANSI "I" key can produce the character "c". // This should behave like Cmd+C (copy), not match the Cmd+I app shortcut. guard let event = NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: [.command], timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: window.windowNumber, context: nil, characters: "c", charactersIgnoringModifiers: "c", isARepeat: false, keyCode: 34 // kVK_ANSI_I ) else { XCTFail("Failed to construct Dvorak Cmd+C event on physical ANSI I key") return } #if DEBUG XCTAssertFalse(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif } } func testMinimalModeUsesZeroTopSafeAreaForMainWindowContentView() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let defaults = UserDefaults.standard let savedMode = defaults.object(forKey: WorkspacePresentationModeSettings.modeKey) let savedLegacyTitlebar = defaults.object(forKey: WorkspaceTitlebarSettings.showTitlebarKey) defaults.set(WorkspacePresentationModeSettings.Mode.minimal.rawValue, forKey: WorkspacePresentationModeSettings.modeKey) defaults.removeObject(forKey: WorkspaceTitlebarSettings.showTitlebarKey) defer { restoreDefaultsValue(savedMode, forKey: WorkspacePresentationModeSettings.modeKey, defaults: defaults) restoreDefaultsValue(savedLegacyTitlebar, forKey: WorkspaceTitlebarSettings.showTitlebarKey, defaults: defaults) } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId), let contentView = window.contentView else { XCTFail("Expected main window content view") return } contentView.layoutSubtreeIfNeeded() RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) XCTAssertEqual( contentView.safeAreaInsets.top, 0, accuracy: 0.5, "Minimal mode should not leave a top safe-area inset in the main window content view" ) } func testAttachUpdateAccessoryRemovesTitlebarAccessoryWhenMinimalModeEnabled() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let defaults = UserDefaults.standard let savedMode = defaults.object(forKey: WorkspacePresentationModeSettings.modeKey) let savedLegacyTitlebar = defaults.object(forKey: WorkspaceTitlebarSettings.showTitlebarKey) defaults.set(WorkspacePresentationModeSettings.Mode.standard.rawValue, forKey: WorkspacePresentationModeSettings.modeKey) defaults.removeObject(forKey: WorkspaceTitlebarSettings.showTitlebarKey) defer { restoreDefaultsValue(savedMode, forKey: WorkspacePresentationModeSettings.modeKey, defaults: defaults) restoreDefaultsValue(savedLegacyTitlebar, forKey: WorkspaceTitlebarSettings.showTitlebarKey, defaults: defaults) } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected main window") return } let hasTitlebarAccessory: () -> Bool = { window.titlebarAccessoryViewControllers.contains { $0.view.identifier?.rawValue == "cmux.titlebarControls" } } XCTAssertTrue(hasTitlebarAccessory(), "Expected visible-titlebar mode to attach the titlebar accessory") defaults.set(WorkspacePresentationModeSettings.Mode.minimal.rawValue, forKey: WorkspacePresentationModeSettings.modeKey) appDelegate.attachUpdateAccessory(to: window) RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) XCTAssertFalse( hasTitlebarAccessory(), "Minimal mode should remove the titlebar accessory instead of keeping a hidden controller attached" ) } func testWorkspaceButtonFadeModeDefaultsOffWhenTitlebarVisible() { let defaults = UserDefaults.standard let savedMode = defaults.object(forKey: WorkspaceButtonFadeSettings.modeKey) let savedTitlebarVisibility = defaults.object(forKey: WorkspaceTitlebarSettings.showTitlebarKey) let savedLegacyTitlebarMode = defaults.object(forKey: WorkspaceButtonFadeSettings.legacyTitlebarControlsVisibilityModeKey) let savedLegacyPaneMode = defaults.object(forKey: WorkspaceButtonFadeSettings.legacyPaneTabBarControlsVisibilityModeKey) defer { restoreDefaultsValue(savedMode, forKey: WorkspaceButtonFadeSettings.modeKey, defaults: defaults) restoreDefaultsValue(savedTitlebarVisibility, forKey: WorkspaceTitlebarSettings.showTitlebarKey, defaults: defaults) restoreDefaultsValue(savedLegacyTitlebarMode, forKey: WorkspaceButtonFadeSettings.legacyTitlebarControlsVisibilityModeKey, defaults: defaults) restoreDefaultsValue(savedLegacyPaneMode, forKey: WorkspaceButtonFadeSettings.legacyPaneTabBarControlsVisibilityModeKey, defaults: defaults) } defaults.removeObject(forKey: WorkspaceButtonFadeSettings.modeKey) defaults.removeObject(forKey: WorkspaceButtonFadeSettings.legacyTitlebarControlsVisibilityModeKey) defaults.removeObject(forKey: WorkspaceButtonFadeSettings.legacyPaneTabBarControlsVisibilityModeKey) defaults.set(true, forKey: WorkspaceTitlebarSettings.showTitlebarKey) WorkspaceButtonFadeSettings.initializeStoredModeIfNeeded(defaults: defaults) XCTAssertEqual( defaults.string(forKey: WorkspaceButtonFadeSettings.modeKey), WorkspaceButtonFadeSettings.Mode.disabled.rawValue ) } func testWorkspaceButtonFadeModeDefaultsOnWhenTitlebarHidden() { let defaults = UserDefaults.standard let savedMode = defaults.object(forKey: WorkspaceButtonFadeSettings.modeKey) let savedTitlebarVisibility = defaults.object(forKey: WorkspaceTitlebarSettings.showTitlebarKey) let savedLegacyTitlebarMode = defaults.object(forKey: WorkspaceButtonFadeSettings.legacyTitlebarControlsVisibilityModeKey) let savedLegacyPaneMode = defaults.object(forKey: WorkspaceButtonFadeSettings.legacyPaneTabBarControlsVisibilityModeKey) defer { restoreDefaultsValue(savedMode, forKey: WorkspaceButtonFadeSettings.modeKey, defaults: defaults) restoreDefaultsValue(savedTitlebarVisibility, forKey: WorkspaceTitlebarSettings.showTitlebarKey, defaults: defaults) restoreDefaultsValue(savedLegacyTitlebarMode, forKey: WorkspaceButtonFadeSettings.legacyTitlebarControlsVisibilityModeKey, defaults: defaults) restoreDefaultsValue(savedLegacyPaneMode, forKey: WorkspaceButtonFadeSettings.legacyPaneTabBarControlsVisibilityModeKey, defaults: defaults) } defaults.removeObject(forKey: WorkspaceButtonFadeSettings.modeKey) defaults.removeObject(forKey: WorkspaceButtonFadeSettings.legacyTitlebarControlsVisibilityModeKey) defaults.removeObject(forKey: WorkspaceButtonFadeSettings.legacyPaneTabBarControlsVisibilityModeKey) defaults.set(false, forKey: WorkspaceTitlebarSettings.showTitlebarKey) WorkspaceButtonFadeSettings.initializeStoredModeIfNeeded(defaults: defaults) XCTAssertEqual( defaults.string(forKey: WorkspaceButtonFadeSettings.modeKey), WorkspaceButtonFadeSettings.Mode.enabled.rawValue ) } func testWorkspaceButtonFadeModeMigratesLegacyHoverVisibilityPreference() { let defaults = UserDefaults.standard let savedMode = defaults.object(forKey: WorkspaceButtonFadeSettings.modeKey) let savedTitlebarVisibility = defaults.object(forKey: WorkspaceTitlebarSettings.showTitlebarKey) let savedLegacyTitlebarMode = defaults.object(forKey: WorkspaceButtonFadeSettings.legacyTitlebarControlsVisibilityModeKey) let savedLegacyPaneMode = defaults.object(forKey: WorkspaceButtonFadeSettings.legacyPaneTabBarControlsVisibilityModeKey) defer { restoreDefaultsValue(savedMode, forKey: WorkspaceButtonFadeSettings.modeKey, defaults: defaults) restoreDefaultsValue(savedTitlebarVisibility, forKey: WorkspaceTitlebarSettings.showTitlebarKey, defaults: defaults) restoreDefaultsValue(savedLegacyTitlebarMode, forKey: WorkspaceButtonFadeSettings.legacyTitlebarControlsVisibilityModeKey, defaults: defaults) restoreDefaultsValue(savedLegacyPaneMode, forKey: WorkspaceButtonFadeSettings.legacyPaneTabBarControlsVisibilityModeKey, defaults: defaults) } defaults.removeObject(forKey: WorkspaceButtonFadeSettings.modeKey) defaults.set(true, forKey: WorkspaceTitlebarSettings.showTitlebarKey) defaults.set("always", forKey: WorkspaceButtonFadeSettings.legacyTitlebarControlsVisibilityModeKey) defaults.set("onHover", forKey: WorkspaceButtonFadeSettings.legacyPaneTabBarControlsVisibilityModeKey) WorkspaceButtonFadeSettings.initializeStoredModeIfNeeded(defaults: defaults) XCTAssertEqual( defaults.string(forKey: WorkspaceButtonFadeSettings.modeKey), WorkspaceButtonFadeSettings.Mode.enabled.rawValue ) } func testWorkspaceButtonFadeModePreservesExistingStoredMode() { let defaults = UserDefaults.standard let savedMode = defaults.object(forKey: WorkspaceButtonFadeSettings.modeKey) let savedTitlebarVisibility = defaults.object(forKey: WorkspaceTitlebarSettings.showTitlebarKey) let savedLegacyTitlebarMode = defaults.object(forKey: WorkspaceButtonFadeSettings.legacyTitlebarControlsVisibilityModeKey) let savedLegacyPaneMode = defaults.object(forKey: WorkspaceButtonFadeSettings.legacyPaneTabBarControlsVisibilityModeKey) defer { restoreDefaultsValue(savedMode, forKey: WorkspaceButtonFadeSettings.modeKey, defaults: defaults) restoreDefaultsValue(savedTitlebarVisibility, forKey: WorkspaceTitlebarSettings.showTitlebarKey, defaults: defaults) restoreDefaultsValue(savedLegacyTitlebarMode, forKey: WorkspaceButtonFadeSettings.legacyTitlebarControlsVisibilityModeKey, defaults: defaults) restoreDefaultsValue(savedLegacyPaneMode, forKey: WorkspaceButtonFadeSettings.legacyPaneTabBarControlsVisibilityModeKey, defaults: defaults) } defaults.set(WorkspaceButtonFadeSettings.Mode.disabled.rawValue, forKey: WorkspaceButtonFadeSettings.modeKey) defaults.set(false, forKey: WorkspaceTitlebarSettings.showTitlebarKey) defaults.set("onHover", forKey: WorkspaceButtonFadeSettings.legacyTitlebarControlsVisibilityModeKey) defaults.set("onHover", forKey: WorkspaceButtonFadeSettings.legacyPaneTabBarControlsVisibilityModeKey) WorkspaceButtonFadeSettings.initializeStoredModeIfNeeded(defaults: defaults) XCTAssertEqual( defaults.string(forKey: WorkspaceButtonFadeSettings.modeKey), WorkspaceButtonFadeSettings.Mode.disabled.rawValue ) } func testWorkspaceMinimalModeDefaultsToStandardPresentation() { let defaults = UserDefaults.standard let savedMode = defaults.object(forKey: WorkspacePresentationModeSettings.modeKey) let savedLegacyTitlebar = defaults.object(forKey: WorkspaceTitlebarSettings.showTitlebarKey) let savedLegacyFade = defaults.object(forKey: WorkspaceButtonFadeSettings.modeKey) defer { restoreDefaultsValue(savedMode, forKey: WorkspacePresentationModeSettings.modeKey, defaults: defaults) restoreDefaultsValue(savedLegacyTitlebar, forKey: WorkspaceTitlebarSettings.showTitlebarKey, defaults: defaults) restoreDefaultsValue(savedLegacyFade, forKey: WorkspaceButtonFadeSettings.modeKey, defaults: defaults) } defaults.removeObject(forKey: WorkspacePresentationModeSettings.modeKey) defaults.set(false, forKey: WorkspaceTitlebarSettings.showTitlebarKey) defaults.set(WorkspaceButtonFadeSettings.Mode.enabled.rawValue, forKey: WorkspaceButtonFadeSettings.modeKey) XCTAssertEqual( WorkspacePresentationModeSettings.mode(defaults: defaults), .standard ) } func testKeyboardShortcutSettingsSetShortcutPostsSpecificChangeNotification() { let notificationName = Notification.Name("cmux.keyboardShortcutSettingsDidChange") let expectedAction = KeyboardShortcutSettings.Action.toggleSidebar.rawValue let expectation = expectation(forNotification: notificationName, object: nil) { notification in notification.userInfo?["action"] as? String == expectedAction } KeyboardShortcutSettings.setShortcut( StoredShortcut(key: "s", command: true, shift: false, option: false, control: true), for: .toggleSidebar ) wait(for: [expectation], timeout: 0.2) } func testCmdPhysicalPWithDvorakCharactersDoesNotTriggerCommandPaletteSwitcher() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } let switcherExpectation = expectation(description: "Cmd+L should not request command palette switcher") switcherExpectation.isInverted = true let token = NotificationCenter.default.addObserver( forName: .commandPaletteSwitcherRequested, object: nil, queue: nil ) { _ in switcherExpectation.fulfill() } defer { NotificationCenter.default.removeObserver(token) } // Dvorak: physical ANSI "P" key can produce "l". // This should behave as Cmd+L, not as physical Cmd+P. guard let event = NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: [.command], timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: window.windowNumber, context: nil, characters: "l", charactersIgnoringModifiers: "l", isARepeat: false, keyCode: 35 // kVK_ANSI_P ) else { XCTFail("Failed to construct Dvorak Cmd+L event on physical ANSI P key") return } #if DEBUG _ = appDelegate.debugHandleCustomShortcut(event: event) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif wait(for: [switcherExpectation], timeout: 0.15) } func testCmdPWithCapsLockStillTriggersCommandPaletteSwitcher() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } let switcherExpectation = expectation(description: "Cmd+P with Caps Lock should request command palette switcher") let token = NotificationCenter.default.addObserver( forName: .commandPaletteSwitcherRequested, object: nil, queue: nil ) { _ in switcherExpectation.fulfill() } defer { NotificationCenter.default.removeObserver(token) } guard let event = NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: [.command, .capsLock], timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: window.windowNumber, context: nil, characters: "p", charactersIgnoringModifiers: "p", isARepeat: false, keyCode: 35 // kVK_ANSI_P ) else { XCTFail("Failed to construct Cmd+P + Caps Lock event") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif wait(for: [switcherExpectation], timeout: 0.15) } func testCmdPFallsBackToANSIKeyCodeWhenCharactersAndLayoutTranslationAreUnavailable() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } appDelegate.shortcutLayoutCharacterProvider = { _, _ in nil } defer { appDelegate.shortcutLayoutCharacterProvider = KeyboardLayout.character(forKeyCode:modifierFlags:) } let switcherExpectation = expectation(description: "Cmd+P with unavailable characters should request command palette switcher") let token = NotificationCenter.default.addObserver( forName: .commandPaletteSwitcherRequested, object: nil, queue: nil ) { _ in switcherExpectation.fulfill() } defer { NotificationCenter.default.removeObserver(token) } guard let event = NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: [.command], timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: window.windowNumber, context: nil, characters: "", charactersIgnoringModifiers: "", isARepeat: false, keyCode: 35 // kVK_ANSI_P ) else { XCTFail("Failed to construct Cmd+P event with unavailable characters") return } XCTAssertTrue(appDelegate.handleBrowserSurfaceKeyEquivalent(event)) wait(for: [switcherExpectation], timeout: 0.15) } func testCmdPDoesNotFallbackToANSIKeyCodeWhenLayoutTranslationProvidesDifferentLetter() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } appDelegate.shortcutLayoutCharacterProvider = { _, _ in "b" } defer { appDelegate.shortcutLayoutCharacterProvider = KeyboardLayout.character(forKeyCode:modifierFlags:) } let switcherExpectation = expectation(description: "Non-P layout translation should not request command palette switcher") switcherExpectation.isInverted = true let token = NotificationCenter.default.addObserver( forName: .commandPaletteSwitcherRequested, object: nil, queue: nil ) { _ in switcherExpectation.fulfill() } defer { NotificationCenter.default.removeObserver(token) } guard let event = NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: [.command], timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: window.windowNumber, context: nil, characters: "", charactersIgnoringModifiers: "", isARepeat: false, keyCode: 35 // kVK_ANSI_P ) else { XCTFail("Failed to construct Cmd+P event with unavailable characters") return } _ = appDelegate.handleBrowserSurfaceKeyEquivalent(event) wait(for: [switcherExpectation], timeout: 0.15) } func testCmdPFallsBackToCommandAwareLayoutTranslationWhenCharactersAreUnavailable() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } appDelegate.shortcutLayoutCharacterProvider = { keyCode, modifierFlags in guard keyCode == 35 else { return nil } // kVK_ANSI_P return modifierFlags.contains(.command) ? "p" : "r" } defer { appDelegate.shortcutLayoutCharacterProvider = KeyboardLayout.character(forKeyCode:modifierFlags:) } let switcherExpectation = expectation(description: "Command-aware layout translation should request command palette switcher") let token = NotificationCenter.default.addObserver( forName: .commandPaletteSwitcherRequested, object: nil, queue: nil ) { _ in switcherExpectation.fulfill() } defer { NotificationCenter.default.removeObserver(token) } guard let event = NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: [.command], timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: window.windowNumber, context: nil, characters: "", charactersIgnoringModifiers: "", isARepeat: false, keyCode: 35 // kVK_ANSI_P ) else { XCTFail("Failed to construct Cmd+P event with unavailable characters") return } XCTAssertTrue(appDelegate.handleBrowserSurfaceKeyEquivalent(event)) wait(for: [switcherExpectation], timeout: 0.15) } func testCmdShiftPhysicalPWithDvorakCharactersDoesNotTriggerCommandPalette() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } let paletteExpectation = expectation(description: "Cmd+Shift+L should not request command palette") paletteExpectation.isInverted = true let token = NotificationCenter.default.addObserver( forName: .commandPaletteRequested, object: nil, queue: nil ) { _ in paletteExpectation.fulfill() } defer { NotificationCenter.default.removeObserver(token) } // Dvorak: physical ANSI "P" key can produce "l". // This should behave as Cmd+Shift+L, not as physical Cmd+Shift+P. guard let event = NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: [.command, .shift], timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: window.windowNumber, context: nil, characters: "l", charactersIgnoringModifiers: "l", isARepeat: false, keyCode: 35 // kVK_ANSI_P ) else { XCTFail("Failed to construct Dvorak Cmd+Shift+L event on physical ANSI P key") return } #if DEBUG _ = appDelegate.debugHandleCustomShortcut(event: event) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif wait(for: [paletteExpectation], timeout: 0.15) } func testCmdOptionPhysicalTWithDvorakCharactersDoesNotTriggerCloseOtherTabsShortcut() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } // Dvorak: physical ANSI "T" key can produce "y". // This should not match the Cmd+Option+T app shortcut. guard let event = NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: [.command, .option], timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: window.windowNumber, context: nil, characters: "y", charactersIgnoringModifiers: "y", isARepeat: false, keyCode: 17 // kVK_ANSI_T ) else { XCTFail("Failed to construct Dvorak Cmd+Option+Y event on physical ANSI T key") return } #if DEBUG XCTAssertFalse(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif } func testCmdShiftPRequestsCommandPaletteCommands() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } let paletteExpectation = expectation(description: "Expected command palette commands request for Cmd+Shift+P") var observedPaletteWindow: NSWindow? let paletteToken = NotificationCenter.default.addObserver( forName: .commandPaletteRequested, object: nil, queue: nil ) { notification in observedPaletteWindow = notification.object as? NSWindow paletteExpectation.fulfill() } defer { NotificationCenter.default.removeObserver(paletteToken) } let switcherExpectation = expectation(description: "Cmd+Shift+P should not request command palette switcher") switcherExpectation.isInverted = true let switcherToken = NotificationCenter.default.addObserver( forName: .commandPaletteSwitcherRequested, object: nil, queue: nil ) { _ in switcherExpectation.fulfill() } defer { NotificationCenter.default.removeObserver(switcherToken) } guard let event = makeKeyDownEvent( key: "P", modifiers: [.command, .shift], keyCode: 35, windowNumber: window.windowNumber ) else { XCTFail("Failed to construct Cmd+Shift+P event") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif wait(for: [paletteExpectation, switcherExpectation], timeout: 1.0) XCTAssertEqual(observedPaletteWindow?.windowNumber, window.windowNumber) } func testCmdPhysicalWWithDvorakCharactersDoesNotTriggerClosePanelShortcut() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId), let manager = appDelegate.tabManagerFor(windowId: windowId), let workspace = manager.selectedWorkspace else { XCTFail("Expected test window and workspace") return } let panelCountBefore = workspace.panels.count // Dvorak: physical ANSI "W" key can produce ",". // This should not match the Cmd+W close-panel shortcut. guard let event = NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: [.command], timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: window.windowNumber, context: nil, characters: ",", charactersIgnoringModifiers: ",", isARepeat: false, keyCode: 13 // kVK_ANSI_W ) else { XCTFail("Failed to construct Dvorak Cmd+, event on physical ANSI W key") return } #if DEBUG XCTAssertFalse(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif XCTAssertEqual(workspace.panels.count, panelCountBefore) } func testCmdIStillTriggersShowNotificationsShortcut() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } withTemporaryShortcut(action: .showNotifications) { guard let event = NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: [.command], timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: window.windowNumber, context: nil, characters: "i", charactersIgnoringModifiers: "i", isARepeat: false, keyCode: 34 // kVK_ANSI_I ) else { XCTFail("Failed to construct Cmd+I event") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif } } func testCmdUnshiftedSymbolDoesNotMatchDigitShortcut() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } withTemporaryShortcut( action: .showNotifications, shortcut: StoredShortcut(key: "8", command: true, shift: false, option: false, control: false) ) { // Some non-US layouts can produce "*" without Shift. // This must not be coerced into "8" for a Cmd+8 shortcut match. guard let event = NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: [.command], timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: window.windowNumber, context: nil, characters: "*", charactersIgnoringModifiers: "*", isARepeat: false, keyCode: 30 // kVK_ANSI_RightBracket ) else { XCTFail("Failed to construct Cmd+* event") return } #if DEBUG XCTAssertFalse(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif } } func testCmdDigitShortcutFallsBackByKeyCodeOnSymbolFirstLayouts() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } withTemporaryShortcut( action: .showNotifications, shortcut: StoredShortcut(key: "1", command: true, shift: false, option: false, control: false) ) { // Symbol-first layouts (for example AZERTY) can report "&" for the ANSI 1 key. // Cmd+1 shortcuts should still match via keyCode fallback in this case. guard let event = NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: [.command], timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: window.windowNumber, context: nil, characters: "&", charactersIgnoringModifiers: "&", isARepeat: false, keyCode: 18 // kVK_ANSI_1 ) else { XCTFail("Failed to construct Cmd+& event on ANSI 1 key") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif } } func testCmdShiftNonDigitKeySymbolDoesNotMatchShiftedDigitShortcut() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } withTemporaryShortcut( action: .showNotifications, shortcut: StoredShortcut(key: "8", command: true, shift: true, option: false, control: false) ) { // Avoid unrelated default Cmd+Shift+] handling for this assertion. withTemporaryShortcut( action: .nextSurface, shortcut: StoredShortcut(key: "x", command: true, shift: true, option: false, control: false) ) { // On some non-US layouts, Shift+RightBracket can produce "*". // This must not be interpreted as Shift+8. guard let event = NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: [.command, .shift], timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: window.windowNumber, context: nil, characters: "*", charactersIgnoringModifiers: "*", isARepeat: false, keyCode: 30 // kVK_ANSI_RightBracket ) else { XCTFail("Failed to construct Cmd+Shift+* event from non-digit key") return } #if DEBUG XCTAssertFalse(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif } } } func testCmdShiftDigitShortcutMatchesShiftedDigitKey() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } withTemporaryShortcut( action: .showNotifications, shortcut: StoredShortcut(key: "8", command: true, shift: true, option: false, control: false) ) { guard let event = NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: [.command, .shift], timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: window.windowNumber, context: nil, characters: "*", charactersIgnoringModifiers: "*", isARepeat: false, keyCode: 28 // kVK_ANSI_8 ) else { XCTFail("Failed to construct Cmd+Shift+8 event") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif } } func testCmdShiftQuestionMarkMatchesSlashShortcut() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } withTemporaryShortcut( action: .triggerFlash, shortcut: StoredShortcut(key: "/", command: true, shift: true, option: false, control: false) ) { guard let event = NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: [.command, .shift], timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: window.windowNumber, context: nil, characters: "?", charactersIgnoringModifiers: "?", isARepeat: false, keyCode: 44 // kVK_ANSI_Slash ) else { XCTFail("Failed to construct Cmd+Shift+/ event") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif } } func testCmdShiftISOAngleBracketDoesNotMatchCommaShortcut() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } withTemporaryShortcut( action: .showNotifications, shortcut: StoredShortcut(key: ",", command: true, shift: true, option: false, control: false) ) { guard let event = NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: [.command, .shift], timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: window.windowNumber, context: nil, characters: "<", charactersIgnoringModifiers: "<", isARepeat: false, keyCode: 10 // kVK_ISO_Section ) else { XCTFail("Failed to construct Cmd+Shift+< event from ISO key") return } #if DEBUG XCTAssertFalse(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif } } func testCmdShiftRightBracketCanFallbackByKeyCodeOnNonUSLayouts() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } withTemporaryShortcut(action: .nextSurface) { // Non-US layouts can report "*" (or other symbols) for kVK_ANSI_RightBracket with Shift. // Shortcut matching should still allow Cmd+Shift+] via keyCode fallback. guard let event = NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: [.command, .shift], timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: window.windowNumber, context: nil, characters: "*", charactersIgnoringModifiers: "*", isARepeat: false, keyCode: 30 // kVK_ANSI_RightBracket ) else { XCTFail("Failed to construct non-US Cmd+Shift+] event") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif } } func testCmdPhysicalOWithDvorakCharactersTriggersRenameTabShortcut() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } let renameTabExpectation = expectation(description: "Expected rename tab request for semantic Cmd+R") var observedRenameTabWindow: NSWindow? let renameTabToken = NotificationCenter.default.addObserver( forName: .commandPaletteRenameTabRequested, object: nil, queue: nil ) { notification in observedRenameTabWindow = notification.object as? NSWindow renameTabExpectation.fulfill() } defer { NotificationCenter.default.removeObserver(renameTabToken) } let switcherExpectation = expectation(description: "Cmd+R should not trigger command palette switcher") switcherExpectation.isInverted = true let switcherToken = NotificationCenter.default.addObserver( forName: .commandPaletteSwitcherRequested, object: nil, queue: nil ) { _ in switcherExpectation.fulfill() } defer { NotificationCenter.default.removeObserver(switcherToken) } withTemporaryShortcut(action: .renameTab) { // Dvorak: physical ANSI "O" key can produce "r". // This should behave as semantic Cmd+R (rename tab), not Cmd+P. guard let event = NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: [.command], timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: window.windowNumber, context: nil, characters: "r", charactersIgnoringModifiers: "r", isARepeat: false, keyCode: 31 // kVK_ANSI_O ) else { XCTFail("Failed to construct Dvorak Cmd+R event on physical ANSI O key") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif } wait(for: [renameTabExpectation, switcherExpectation], timeout: 1.0) XCTAssertEqual(observedRenameTabWindow?.windowNumber, window.windowNumber) } func testCmdPhysicalRWithDvorakCharactersTriggersCommandPaletteSwitcher() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } let switcherExpectation = expectation(description: "Expected command palette switcher request for semantic Cmd+P") var observedSwitcherWindow: NSWindow? let switcherToken = NotificationCenter.default.addObserver( forName: .commandPaletteSwitcherRequested, object: nil, queue: nil ) { notification in observedSwitcherWindow = notification.object as? NSWindow switcherExpectation.fulfill() } defer { NotificationCenter.default.removeObserver(switcherToken) } let renameTabExpectation = expectation(description: "Physical R on Dvorak should not trigger rename tab") renameTabExpectation.isInverted = true let renameTabToken = NotificationCenter.default.addObserver( forName: .commandPaletteRenameTabRequested, object: nil, queue: nil ) { _ in renameTabExpectation.fulfill() } defer { NotificationCenter.default.removeObserver(renameTabToken) } // Dvorak: physical ANSI "R" key can produce "p". // This should behave as semantic Cmd+P (palette switcher), not Cmd+R. guard let event = NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: [.command], timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: window.windowNumber, context: nil, characters: "p", charactersIgnoringModifiers: "p", isARepeat: false, keyCode: 15 // kVK_ANSI_R ) else { XCTFail("Failed to construct Dvorak Cmd+P event on physical ANSI R key") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif wait(for: [switcherExpectation, renameTabExpectation], timeout: 1.0) XCTAssertEqual(observedSwitcherWindow?.windowNumber, window.windowNumber) } func testCmdShiftRRequestsRenameWorkspaceInCommandPalette() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } let workspaceExpectation = expectation(description: "Expected command palette rename workspace notification") var observedWorkspaceWindow: NSWindow? var didObserveWorkspaceNotification = false let workspaceToken = NotificationCenter.default.addObserver( forName: .commandPaletteRenameWorkspaceRequested, object: nil, queue: nil ) { notification in guard !didObserveWorkspaceNotification else { return } didObserveWorkspaceNotification = true observedWorkspaceWindow = notification.object as? NSWindow workspaceExpectation.fulfill() } defer { NotificationCenter.default.removeObserver(workspaceToken) } let renameTabExpectation = expectation(description: "Rename tab notification should not fire for Cmd+Shift+R") renameTabExpectation.isInverted = true let renameTabToken = NotificationCenter.default.addObserver( forName: .commandPaletteRenameTabRequested, object: nil, queue: nil ) { _ in renameTabExpectation.fulfill() } defer { NotificationCenter.default.removeObserver(renameTabToken) } guard let event = makeKeyDownEvent( key: "r", modifiers: [.command, .shift], keyCode: 15, // kVK_ANSI_R windowNumber: window.windowNumber ) else { XCTFail("Failed to construct Cmd+Shift+R event") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif wait(for: [workspaceExpectation, renameTabExpectation], timeout: 1.0) XCTAssertEqual(observedWorkspaceWindow?.windowNumber, window.windowNumber) } func testEscapeDismissesVisibleCommandPaletteAndIsConsumed() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } appDelegate.setCommandPaletteVisible(true, for: window) defer { appDelegate.setCommandPaletteVisible(false, for: window) } let dismissExpectation = expectation(description: "Expected command palette toggle notification for Escape dismiss") var observedDismissWindow: NSWindow? let dismissToken = NotificationCenter.default.addObserver( forName: .commandPaletteToggleRequested, object: nil, queue: nil ) { notification in observedDismissWindow = notification.object as? NSWindow dismissExpectation.fulfill() } defer { NotificationCenter.default.removeObserver(dismissToken) } guard let event = makeKeyDownEvent( key: "\u{1b}", modifiers: [], keyCode: 53, // kVK_Escape windowNumber: window.windowNumber ) else { XCTFail("Failed to construct Escape event") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif wait(for: [dismissExpectation], timeout: 1.0) XCTAssertEqual(observedDismissWindow?.windowNumber, window.windowNumber) } func testEscapeDoesNotDismissCommandPaletteWhenInputHasMarkedText() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } let fieldEditor = CommandPaletteMarkedTextFieldEditor(frame: NSRect(x: 0, y: 0, width: 200, height: 24)) fieldEditor.isFieldEditor = true fieldEditor.hasMarkedTextForTesting = true window.contentView?.addSubview(fieldEditor) XCTAssertTrue(window.makeFirstResponder(fieldEditor)) appDelegate.setCommandPaletteVisible(true, for: window) defer { appDelegate.setCommandPaletteVisible(false, for: window) fieldEditor.removeFromSuperview() } let dismissExpectation = expectation( description: "Escape should not dismiss command palette while IME marked text is active" ) dismissExpectation.isInverted = true let dismissToken = NotificationCenter.default.addObserver( forName: .commandPaletteToggleRequested, object: nil, queue: nil ) { notification in guard let dismissWindow = notification.object as? NSWindow, dismissWindow.windowNumber == window.windowNumber else { return } dismissExpectation.fulfill() } defer { NotificationCenter.default.removeObserver(dismissToken) } guard let escapeEvent = makeKeyDownEvent( key: "\u{1b}", modifiers: [], keyCode: 53, windowNumber: window.windowNumber ) else { XCTFail("Failed to construct Escape event") return } #if DEBUG XCTAssertFalse( appDelegate.debugHandleCustomShortcut(event: escapeEvent), "Escape should pass through to IME composition instead of dismissing command palette" ) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif wait(for: [dismissExpectation], timeout: 0.2) } func testEscapeDismissesCommandPaletteWhenVisibilitySyncLagsAfterOpenRequest() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } let dismissExpectation = expectation(description: "Expected command palette dismiss notification for Escape") var observedDismissWindow: NSWindow? let dismissToken = NotificationCenter.default.addObserver( forName: .commandPaletteToggleRequested, object: nil, queue: nil ) { notification in observedDismissWindow = notification.object as? NSWindow dismissExpectation.fulfill() } defer { NotificationCenter.default.removeObserver(dismissToken) } #if DEBUG appDelegate.debugMarkCommandPaletteOpenPending(window: window) #else XCTFail("debugMarkCommandPaletteOpenPending is only available in DEBUG") #endif // Simulate a visibility sync lag/race where AppDelegate does not yet know the palette is open. appDelegate.setCommandPaletteVisible(false, for: window) guard let escapeEvent = makeKeyDownEvent( key: "\u{1b}", modifiers: [], keyCode: 53, windowNumber: window.windowNumber ) else { XCTFail("Failed to construct Escape event") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: escapeEvent)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif wait(for: [dismissExpectation], timeout: 1.0) XCTAssertEqual(observedDismissWindow?.windowNumber, window.windowNumber) } func testArrowNavigationRoutesWhileCommandPaletteOverlayIsInteractiveBeforeVisibilitySync() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId), let contentView = window.contentView else { XCTFail("Expected test window") return } let overlayContainer = NSView(frame: contentView.bounds) overlayContainer.identifier = commandPaletteOverlayContainerIdentifier overlayContainer.alphaValue = 1 overlayContainer.isHidden = false contentView.addSubview(overlayContainer) let fieldEditor = CommandPaletteMarkedTextFieldEditor(frame: NSRect(x: 0, y: 0, width: 200, height: 24)) fieldEditor.isFieldEditor = true overlayContainer.addSubview(fieldEditor) XCTAssertTrue(window.makeFirstResponder(fieldEditor)) appDelegate.setCommandPaletteVisible(false, for: window) defer { overlayContainer.removeFromSuperview() fieldEditor.removeFromSuperview() } let moveExpectation = expectation( description: "Expected command palette move-selection notification while overlay is interactive" ) var observedDelta: Int? var observedWindow: NSWindow? let moveToken = NotificationCenter.default.addObserver( forName: .commandPaletteMoveSelection, object: nil, queue: nil ) { notification in observedWindow = notification.object as? NSWindow observedDelta = notification.userInfo?["delta"] as? Int moveExpectation.fulfill() } defer { NotificationCenter.default.removeObserver(moveToken) } guard let downArrowEvent = makeKeyDownEvent( key: String(UnicodeScalar(NSDownArrowFunctionKey)!), modifiers: [], keyCode: 125, windowNumber: window.windowNumber ) else { XCTFail("Failed to construct Down Arrow event") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: downArrowEvent)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif wait(for: [moveExpectation], timeout: 1.0) XCTAssertEqual(observedWindow?.windowNumber, window.windowNumber) XCTAssertEqual(observedDelta, 1) } func testEscapeDismissesCommandPaletteWhenVisibilityStateStaysStalePastInitialPendingWindow() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } #if DEBUG XCTAssertTrue( appDelegate.debugSetCommandPalettePendingOpenAge(window: window, age: 1.3), "Expected to backdate pending-open age for stale visibility test" ) #else XCTFail("debugSetCommandPalettePendingOpenAge is only available in DEBUG") #endif // Simulate stale app-level visibility bookkeeping. appDelegate.setCommandPaletteVisible(false, for: window) let dismissExpectation = expectation(description: "Escape should dismiss stale-state command palette after delay") var observedDismissWindow: NSWindow? let dismissToken = NotificationCenter.default.addObserver( forName: .commandPaletteToggleRequested, object: nil, queue: nil ) { notification in observedDismissWindow = notification.object as? NSWindow dismissExpectation.fulfill() } defer { NotificationCenter.default.removeObserver(dismissToken) } guard let escapeEvent = makeKeyDownEvent( key: "\u{1b}", modifiers: [], keyCode: 53, windowNumber: window.windowNumber ) else { XCTFail("Failed to construct Escape event") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: escapeEvent)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif wait(for: [dismissExpectation], timeout: 1.0) XCTAssertEqual(observedDismissWindow?.windowNumber, window.windowNumber) } func testEscapeDismissesCommandPaletteWhenVisibilityStateRemainsStaleForExtendedDelay() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } #if DEBUG XCTAssertTrue( appDelegate.debugSetCommandPalettePendingOpenAge(window: window, age: 6.25), "Expected to backdate pending-open age for extended stale visibility test" ) #else XCTFail("debugSetCommandPalettePendingOpenAge is only available in DEBUG") #endif // Simulate stale app-level visibility bookkeeping for a longer user delay. appDelegate.setCommandPaletteVisible(false, for: window) let dismissExpectation = expectation(description: "Escape should dismiss stale-state command palette after extended delay") var observedDismissWindow: NSWindow? let dismissToken = NotificationCenter.default.addObserver( forName: .commandPaletteToggleRequested, object: nil, queue: nil ) { notification in observedDismissWindow = notification.object as? NSWindow dismissExpectation.fulfill() } defer { NotificationCenter.default.removeObserver(dismissToken) } guard let escapeEvent = makeKeyDownEvent( key: "\u{1b}", modifiers: [], keyCode: 53, windowNumber: window.windowNumber ) else { XCTFail("Failed to construct Escape event") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: escapeEvent)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif wait(for: [dismissExpectation], timeout: 1.0) XCTAssertEqual(observedDismissWindow?.windowNumber, window.windowNumber) } func testEscapeDoesNotConsumeWhenMenuTriggeredPendingOpenStateExpires() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } window.makeKeyAndOrderFront(nil) RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) #if DEBUG XCTAssertTrue( appDelegate.debugSetCommandPalettePendingOpenAge(window: window, age: 20.0), "Expected to seed an expired pending-open request state" ) #else XCTFail("debugSetCommandPalettePendingOpenAge is only available in DEBUG") #endif appDelegate.setCommandPaletteVisible(false, for: window) let dismissExpectation = expectation(description: "No dismiss notification for expired pending-open state") dismissExpectation.isInverted = true let dismissToken = NotificationCenter.default.addObserver( forName: .commandPaletteToggleRequested, object: nil, queue: nil ) { _ in dismissExpectation.fulfill() } defer { NotificationCenter.default.removeObserver(dismissToken) } guard let escapeEvent = makeKeyDownEvent( key: "\u{1b}", modifiers: [], keyCode: 53, windowNumber: window.windowNumber ) else { XCTFail("Failed to construct Escape event") return } #if DEBUG XCTAssertFalse( appDelegate.debugHandleCustomShortcut(event: escapeEvent), "Escape should pass through once pending-open grace has expired" ) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif wait(for: [dismissExpectation], timeout: 0.2) } func testEscapeDismissesMenuTriggeredCommandPaletteWhenVisibilitySyncIsStale() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } // Reproduce the menu-command path (Cmd+Shift+P/Cmd+P) routed via AppDelegate. appDelegate.requestCommandPaletteCommands( preferredWindow: window, source: "test.menuCommandPalette" ) // Simulate delayed/stale visibility sync from SwiftUI overlay state. appDelegate.setCommandPaletteVisible(false, for: window) #if DEBUG XCTAssertTrue( appDelegate.debugSetCommandPalettePendingOpenAge(window: window, age: 0.1), "Expected deterministic pending-open state for menu-triggered stale-visibility path" ) #else XCTFail("debugSetCommandPalettePendingOpenAge is only available in DEBUG") #endif let dismissExpectation = expectation(description: "Expected command palette dismiss notification for menu-triggered stale visibility") var observedDismissWindow: NSWindow? let dismissToken = NotificationCenter.default.addObserver( forName: .commandPaletteToggleRequested, object: nil, queue: nil ) { notification in observedDismissWindow = notification.object as? NSWindow dismissExpectation.fulfill() } defer { NotificationCenter.default.removeObserver(dismissToken) } guard let escapeEvent = makeKeyDownEvent( key: "\u{1b}", modifiers: [], keyCode: 53, windowNumber: window.windowNumber ) else { XCTFail("Failed to construct Escape event") return } #if DEBUG XCTAssertTrue( appDelegate.debugHandleCustomShortcut(event: escapeEvent), "Escape should still be consumed for menu-triggered command palette opens" ) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif wait(for: [dismissExpectation], timeout: 1.0) XCTAssertEqual(observedDismissWindow?.windowNumber, window.windowNumber) } func testEscapeRepeatIsConsumedImmediatelyAfterPaletteDismiss() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } appDelegate.setCommandPaletteVisible(true, for: window) defer { appDelegate.setCommandPaletteVisible(false, for: window) } guard let firstEscape = makeKeyDownEvent( key: "\u{1b}", modifiers: [], keyCode: 53, windowNumber: window.windowNumber ) else { XCTFail("Failed to construct first Escape event") return } guard let repeatedEscape = makeKeyDownEvent( key: "\u{1b}", modifiers: [], keyCode: 53, windowNumber: window.windowNumber, isARepeat: true ) else { XCTFail("Failed to construct repeated Escape event") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: firstEscape)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif // Simulate the palette overlay synchronizing to closed state while the Escape key is still held. appDelegate.setCommandPaletteVisible(false, for: window) #if DEBUG XCTAssertTrue( appDelegate.debugHandleCustomShortcut(event: repeatedEscape), "Repeated Escape immediately after dismiss should be consumed to prevent terminal passthrough" ) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif } func testEscapeKeyUpIsConsumedAfterPaletteDismissToPreventTerminalLeak() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window") return } appDelegate.setCommandPaletteVisible(true, for: window) defer { appDelegate.setCommandPaletteVisible(false, for: window) } guard let escapeKeyDown = makeKeyEvent( type: .keyDown, key: "\u{1b}", modifiers: [], keyCode: 53, windowNumber: window.windowNumber ) else { XCTFail("Failed to construct Escape keyDown event") return } guard let escapeKeyUp = makeKeyEvent( type: .keyUp, key: "\u{1b}", modifiers: [], keyCode: 53, windowNumber: window.windowNumber ) else { XCTFail("Failed to construct Escape keyUp event") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleShortcutMonitorEvent(event: escapeKeyDown)) #else XCTFail("debugHandleShortcutMonitorEvent is only available in DEBUG") #endif // Simulate the palette overlay synchronizing to closed state before Escape key-up arrives. appDelegate.setCommandPaletteVisible(false, for: window) #if DEBUG XCTAssertTrue( appDelegate.debugHandleShortcutMonitorEvent(event: escapeKeyUp), "Escape keyUp after palette dismiss should be consumed to prevent terminal passthrough" ) #else XCTFail("debugHandleShortcutMonitorEvent is only available in DEBUG") #endif } func testEscapeKeyUpIsConsumedAfterCmdPSwitcherDismiss() { assertEscapeKeyUpIsConsumedAfterCommandPaletteOpenRequest { appDelegate, window in appDelegate.requestCommandPaletteSwitcher( preferredWindow: window, source: "test.cmdP" ) } } func testEscapeKeyUpIsConsumedAfterCmdShiftPCommandsDismiss() { assertEscapeKeyUpIsConsumedAfterCommandPaletteOpenRequest { appDelegate, window in appDelegate.requestCommandPaletteCommands( preferredWindow: window, source: "test.cmdShiftP" ) } } func testEscapeDoesNotDismissPaletteInDifferentWindow() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let paletteWindowId = appDelegate.createMainWindow() let eventWindowId = appDelegate.createMainWindow() defer { closeWindow(withId: paletteWindowId) closeWindow(withId: eventWindowId) } guard let paletteWindow = window(withId: paletteWindowId), let eventWindow = window(withId: eventWindowId) else { XCTFail("Expected both test windows") return } appDelegate.setCommandPaletteVisible(true, for: paletteWindow) defer { appDelegate.setCommandPaletteVisible(false, for: paletteWindow) } let dismissExpectation = expectation(description: "Escape in another window should not dismiss palette") dismissExpectation.isInverted = true let dismissToken = NotificationCenter.default.addObserver( forName: .commandPaletteToggleRequested, object: nil, queue: nil ) { _ in dismissExpectation.fulfill() } defer { NotificationCenter.default.removeObserver(dismissToken) } guard let escapeEvent = makeKeyDownEvent( key: "\u{1b}", modifiers: [], keyCode: 53, windowNumber: eventWindow.windowNumber ) else { XCTFail("Failed to construct Escape event") return } #if DEBUG XCTAssertFalse( appDelegate.debugHandleCustomShortcut(event: escapeEvent), "Escape should remain scoped to the event window" ) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif wait(for: [dismissExpectation], timeout: 0.2) } func testCmdDigitDoesNotFallbackToOtherWindowWhenEventWindowContextIsMissing() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let firstWindowId = appDelegate.createMainWindow() let secondWindowId = appDelegate.createMainWindow() defer { closeWindow(withId: firstWindowId) closeWindow(withId: secondWindowId) } guard let firstManager = appDelegate.tabManagerFor(windowId: firstWindowId), let secondManager = appDelegate.tabManagerFor(windowId: secondWindowId), let secondWindow = window(withId: secondWindowId) else { XCTFail("Expected both window contexts to exist") return } _ = firstManager.addTab(select: true) _ = secondManager.addTab(select: true) guard let firstSelectedBefore = firstManager.selectedTabId, let secondSelectedBefore = secondManager.selectedTabId else { XCTFail("Expected selected tabs in both windows") return } secondWindow.makeKeyAndOrderFront(nil) RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) // Force stale app-level manager to first window while keyboard event // references no known window. appDelegate.tabManager = firstManager guard let event = makeKeyDownEvent( key: "1", modifiers: [.command], keyCode: 18, windowNumber: Int.max ) else { XCTFail("Failed to construct Cmd+1 event") return } #if DEBUG XCTAssertFalse(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif XCTAssertEqual(firstManager.selectedTabId, firstSelectedBefore, "Unresolved event window must not route Cmd+1 into stale manager") XCTAssertEqual(secondManager.selectedTabId, secondSelectedBefore, "Unresolved event window must not route Cmd+1 into key/main fallback manager") XCTAssertTrue(appDelegate.tabManager === firstManager, "Unresolved event window should not retarget active manager") } func testCmdNDoesNotFallbackToOtherWindowWhenEventWindowContextIsMissing() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let firstWindowId = appDelegate.createMainWindow() let secondWindowId = appDelegate.createMainWindow() defer { closeWindow(withId: firstWindowId) closeWindow(withId: secondWindowId) } guard let firstManager = appDelegate.tabManagerFor(windowId: firstWindowId), let secondManager = appDelegate.tabManagerFor(windowId: secondWindowId), let secondWindow = window(withId: secondWindowId) else { XCTFail("Expected both window contexts to exist") return } secondWindow.makeKeyAndOrderFront(nil) RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) let firstCount = firstManager.tabs.count let secondCount = secondManager.tabs.count appDelegate.tabManager = firstManager guard let event = makeKeyDownEvent( key: "n", modifiers: [.command], keyCode: 45, windowNumber: Int.max ) else { XCTFail("Failed to construct Cmd+N event") return } #if DEBUG XCTAssertFalse(appDelegate.debugHandleCustomShortcut(event: event)) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif XCTAssertEqual(firstManager.tabs.count, firstCount, "Unresolved event window must not create workspace in stale manager") XCTAssertEqual(secondManager.tabs.count, secondCount, "Unresolved event window must not create workspace in fallback window") XCTAssertTrue(appDelegate.tabManager === firstManager, "Unresolved event window should not retarget active manager") } func testCmdShiftMReturnsFalseWhenNoFocusedTerminalCanHandle() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } // Force unresolved shortcut routing context and no active manager. appDelegate.tabManager = nil guard let event = makeKeyDownEvent( key: "m", modifiers: [.command, .shift], keyCode: 46, // kVK_ANSI_M windowNumber: Int.max ) else { XCTFail("Failed to construct Cmd+Shift+M event") return } #if DEBUG XCTAssertFalse( appDelegate.debugHandleCustomShortcut(event: event), "Cmd+Shift+M should not be consumed when no terminal can toggle copy mode" ) #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif } func testPresentPreferencesWindowShowsCustomSettingsWindowAndActivates() { var showFallbackSettingsWindowCallCount = 0 var activateApplicationCallCount = 0 var receivedNavigationTargets: [SettingsNavigationTarget?] = [] AppDelegate.presentPreferencesWindow( showFallbackSettingsWindow: { navigationTarget in receivedNavigationTargets.append(navigationTarget) showFallbackSettingsWindowCallCount += 1 }, activateApplication: { activateApplicationCallCount += 1 } ) XCTAssertEqual(showFallbackSettingsWindowCallCount, 1) XCTAssertEqual(activateApplicationCallCount, 1) XCTAssertEqual(receivedNavigationTargets, [nil]) } func testPresentPreferencesWindowSupportsRepeatedCalls() { var showFallbackSettingsWindowCallCount = 0 var activateApplicationCallCount = 0 var receivedNavigationTargets: [SettingsNavigationTarget?] = [] AppDelegate.presentPreferencesWindow( showFallbackSettingsWindow: { navigationTarget in receivedNavigationTargets.append(navigationTarget) showFallbackSettingsWindowCallCount += 1 }, activateApplication: { activateApplicationCallCount += 1 } ) AppDelegate.presentPreferencesWindow( showFallbackSettingsWindow: { navigationTarget in receivedNavigationTargets.append(navigationTarget) showFallbackSettingsWindowCallCount += 1 }, activateApplication: { activateApplicationCallCount += 1 } ) XCTAssertEqual(showFallbackSettingsWindowCallCount, 2) XCTAssertEqual(activateApplicationCallCount, 2) XCTAssertEqual(receivedNavigationTargets, [nil, nil]) } func testPresentPreferencesWindowForwardsNavigationTarget() { var receivedNavigationTarget: SettingsNavigationTarget? var activateApplicationCallCount = 0 AppDelegate.presentPreferencesWindow( navigationTarget: .keyboardShortcuts, showFallbackSettingsWindow: { navigationTarget in receivedNavigationTarget = navigationTarget }, activateApplication: { activateApplicationCallCount += 1 } ) XCTAssertEqual(receivedNavigationTarget, .keyboardShortcuts) XCTAssertEqual(activateApplicationCallCount, 1) } func testPresentPreferencesWindowForwardsBrowserImportNavigationTarget() { var receivedNavigationTarget: SettingsNavigationTarget? var activateApplicationCallCount = 0 AppDelegate.presentPreferencesWindow( navigationTarget: .browserImport, showFallbackSettingsWindow: { navigationTarget in receivedNavigationTarget = navigationTarget }, activateApplication: { activateApplicationCallCount += 1 } ) XCTAssertEqual(receivedNavigationTarget, .browserImport) XCTAssertEqual(activateApplicationCallCount, 1) } // MARK: - Non-Latin keyboard layout shortcut tests func testCmdTWorksWithRussianKeyboardLayout() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId), let manager = appDelegate.tabManagerFor(windowId: windowId), let workspace = manager.selectedWorkspace else { XCTFail("Expected test window context") return } let surfaceCountBefore = workspace.panels.count // Simulate Russian keyboard: layout provider returns "t" via ASCII fallback, // but event.charactersIgnoringModifiers returns Cyrillic "е". appDelegate.shortcutLayoutCharacterProvider = { keyCode, _ in keyCode == 17 ? "t" : nil } defer { appDelegate.shortcutLayoutCharacterProvider = KeyboardLayout.character(forKeyCode:modifierFlags:) } guard let event = NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: [.command], timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: window.windowNumber, context: nil, characters: "t", charactersIgnoringModifiers: "е", // Cyrillic е (Russian layout) isARepeat: false, keyCode: 17 // kVK_ANSI_T ) else { XCTFail("Failed to construct Russian-layout Cmd+T event") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event), "Cmd+T should be handled with Russian keyboard layout") #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) XCTAssertEqual(workspace.panels.count, surfaceCountBefore + 1, "Cmd+T should create a new surface with Russian keyboard layout") } func testCmdTFallsBackToKeyCodeWithNonLatinLayoutWhenLayoutTranslationFails() { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared") return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId), let manager = appDelegate.tabManagerFor(windowId: windowId), let workspace = manager.selectedWorkspace else { XCTFail("Expected test window context") return } let surfaceCountBefore = workspace.panels.count // Simulate non-Latin layout where layout translation also fails (returns nil). // The ANSI keyCode fallback should still match the physical T key. appDelegate.shortcutLayoutCharacterProvider = { _, _ in nil } defer { appDelegate.shortcutLayoutCharacterProvider = KeyboardLayout.character(forKeyCode:modifierFlags:) } guard let event = NSEvent.keyEvent( with: .keyDown, location: .zero, modifierFlags: [.command], timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: window.windowNumber, context: nil, characters: "", charactersIgnoringModifiers: "е", // Cyrillic е — non-ASCII isARepeat: false, keyCode: 17 // kVK_ANSI_T ) else { XCTFail("Failed to construct non-Latin Cmd+T event with failed layout translation") return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event), "Cmd+T should fall back to keyCode with non-Latin layout") #else XCTFail("debugHandleCustomShortcut is only available in DEBUG") #endif RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) XCTAssertEqual(workspace.panels.count, surfaceCountBefore + 1, "Cmd+T keyCode fallback should create a new surface") } private func makeKeyDownEvent( key: String, modifiers: NSEvent.ModifierFlags, keyCode: UInt16, windowNumber: Int, isARepeat: Bool = false ) -> NSEvent? { makeKeyEvent( type: .keyDown, key: key, modifiers: modifiers, keyCode: keyCode, windowNumber: windowNumber, isARepeat: isARepeat ) } private func makeKeyEvent( type: NSEvent.EventType, key: String, modifiers: NSEvent.ModifierFlags, keyCode: UInt16, windowNumber: Int, isARepeat: Bool = false ) -> NSEvent? { NSEvent.keyEvent( with: type, location: .zero, modifierFlags: modifiers, timestamp: ProcessInfo.processInfo.systemUptime, windowNumber: windowNumber, context: nil, characters: key, charactersIgnoringModifiers: key, isARepeat: isARepeat, keyCode: keyCode ) } private func withTemporaryShortcut( action: KeyboardShortcutSettings.Action, shortcut: StoredShortcut? = nil, _ body: () -> Void ) { let hadPersistedShortcut = UserDefaults.standard.object(forKey: action.defaultsKey) != nil let originalShortcut = KeyboardShortcutSettings.shortcut(for: action) defer { if hadPersistedShortcut { KeyboardShortcutSettings.setShortcut(originalShortcut, for: action) } else { KeyboardShortcutSettings.resetShortcut(for: action) } } KeyboardShortcutSettings.setShortcut(shortcut ?? action.defaultShortcut, for: action) body() } private func assertEscapeKeyUpIsConsumedAfterCommandPaletteOpenRequest( _ openRequest: (_ appDelegate: AppDelegate, _ window: NSWindow) -> Void, file: StaticString = #filePath, line: UInt = #line ) { guard let appDelegate = AppDelegate.shared else { XCTFail("Expected AppDelegate.shared", file: file, line: line) return } let windowId = appDelegate.createMainWindow() defer { closeWindow(withId: windowId) } guard let window = window(withId: windowId) else { XCTFail("Expected test window", file: file, line: line) return } openRequest(appDelegate, window) appDelegate.setCommandPaletteVisible(true, for: window) guard let escapeKeyDown = makeKeyEvent( type: .keyDown, key: "\u{1b}", modifiers: [], keyCode: 53, windowNumber: window.windowNumber ), let escapeKeyUp = makeKeyEvent( type: .keyUp, key: "\u{1b}", modifiers: [], keyCode: 53, windowNumber: window.windowNumber ) else { XCTFail("Failed to construct Escape key events", file: file, line: line) return } #if DEBUG XCTAssertTrue(appDelegate.debugHandleShortcutMonitorEvent(event: escapeKeyDown), file: file, line: line) #else XCTFail("debugHandleShortcutMonitorEvent is only available in DEBUG", file: file, line: line) #endif appDelegate.setCommandPaletteVisible(false, for: window) #if DEBUG XCTAssertTrue( appDelegate.debugHandleShortcutMonitorEvent(event: escapeKeyUp), "Escape keyUp should be consumed after dismiss for command palette open requests", file: file, line: line ) #else XCTFail("debugHandleShortcutMonitorEvent is only available in DEBUG", file: file, line: line) #endif } private func window(withId windowId: UUID) -> NSWindow? { let identifier = "cmux.main.\(windowId.uuidString)" return NSApp.windows.first(where: { $0.identifier?.rawValue == identifier }) } private func surfaceView(in hostedView: GhosttySurfaceScrollView) -> GhosttyNSView? { var stack: [NSView] = [hostedView] while let current = stack.popLast() { if let surfaceView = current as? GhosttyNSView { return surfaceView } stack.append(contentsOf: current.subviews) } return nil } private func mainWindowIds() -> Set { Set(NSApp.windows.compactMap { window in guard let raw = window.identifier?.rawValue, raw.hasPrefix("cmux.main.") else { return nil } return UUID(uuidString: String(raw.dropFirst("cmux.main.".count))) }) } private func closeWindow(withId windowId: UUID) { guard let window = window(withId: windowId) else { return } window.performClose(nil) RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) } private func restoreDefaultsValue(_ value: Any?, forKey key: String, defaults: UserDefaults) { if let value { defaults.set(value, forKey: key) } else { defaults.removeObject(forKey: key) } } } private final class CommandPaletteMarkedTextFieldEditor: NSTextView { var hasMarkedTextForTesting = false override func hasMarkedText() -> Bool { hasMarkedTextForTesting } }