cmux/cmuxTests/AppDelegateShortcutRoutingTests.swift
2026-03-30 20:58:01 -07:00

3557 lines
132 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import XCTest
#if canImport(cmux_DEV)
@testable import cmux_DEV
#elseif canImport(cmux)
@testable import cmux
#endif
private let appDelegateLastSurfaceCloseShortcutDefaultsKey = "closeWorkspaceOnLastSurfaceShortcut"
private final class FakeWKInspectorContainerView: NSView {}
@MainActor
final class AppDelegateShortcutRoutingTests: XCTestCase {
private var savedShortcutsByAction: [KeyboardShortcutSettings.Action: StoredShortcut] = [:]
private var actionsWithPersistedShortcut: Set<KeyboardShortcutSettings.Action> = []
private func makeKeyEvent(
modifierFlags: NSEvent.ModifierFlags,
characters: String,
charactersIgnoringModifiers: String,
keyCode: UInt16
) -> NSEvent {
guard let event = NSEvent.keyEvent(
with: .keyDown,
location: .zero,
modifierFlags: modifierFlags,
timestamp: ProcessInfo.processInfo.systemUptime,
windowNumber: 0,
context: nil,
characters: characters,
charactersIgnoringModifiers: charactersIgnoringModifiers,
isARepeat: false,
keyCode: keyCode
) else {
fatalError("Failed to construct key event")
}
return event
}
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?.debugCreateMainWindowSourceIsNativeFullScreenOverride = 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 testCreateMainWindowDoesNotDisallowFullScreenTilingByDefault() {
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
}
XCTAssertFalse(
window.collectionBehavior.contains(.fullScreenDisallowsTiling),
"Main windows should still support standard macOS Split View when not created from a fullscreen source"
)
}
func testCreateMainWindowTemporarilyDisallowsFullScreenTilingFromFullscreenSource() {
guard let appDelegate = AppDelegate.shared else {
XCTFail("Expected AppDelegate.shared")
return
}
appDelegate.debugCreateMainWindowSourceIsNativeFullScreenOverride = true
let newWindowId = appDelegate.createMainWindow()
defer {
closeWindow(withId: newWindowId)
}
guard let newWindow = window(withId: newWindowId) else {
XCTFail("Expected new window")
return
}
XCTAssertTrue(
newWindow.collectionBehavior.contains(.fullScreenDisallowsTiling),
"New windows should temporarily opt out of fullscreen tiling while opening from a fullscreen source"
)
appDelegate.debugCreateMainWindowSourceIsNativeFullScreenOverride = nil
RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05))
XCTAssertFalse(
newWindow.collectionBehavior.contains(.fullScreenDisallowsTiling),
"The fullscreen tiling opt-out should be cleared after initial presentation so Split View keeps working"
)
}
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 testDockMenuNewWindowItemCreatesMainWindow() {
guard let appDelegate = AppDelegate.shared else {
XCTFail("Expected AppDelegate.shared")
return
}
let existingWindowId = appDelegate.createMainWindow()
var createdWindowId: UUID?
defer {
if let createdWindowId {
closeWindow(withId: createdWindowId)
}
closeWindow(withId: existingWindowId)
}
let existingWindowIds = mainWindowIds()
let delegate: NSApplicationDelegate = appDelegate
guard let dockMenu = delegate.applicationDockMenu?(NSApp) else {
XCTFail("Expected Dock menu")
return
}
let expectedTitle = String(localized: "menu.file.newWindow", defaultValue: "New Window")
guard let item = dockMenu.items.first(where: { $0.action == #selector(AppDelegate.openNewMainWindow(_:)) }) else {
XCTFail("Expected New Window item in Dock menu")
return
}
XCTAssertEqual(item.title, expectedTitle)
XCTAssertTrue(NSApp.sendAction(#selector(AppDelegate.openNewMainWindow(_:)), to: item.target, from: item))
RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05))
let newWindowIds = mainWindowIds().subtracting(existingWindowIds)
XCTAssertEqual(newWindowIds.count, 1, "Dock menu New Window should create one main window")
createdWindowId = newWindowIds.first
}
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
// Model the normal open-palette state so the test reads like the user-facing scenario.
appDelegate.setCommandPaletteVisible(true, 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 testControlKDoesNotRoutePaletteMoveSelectionWhenSearchFieldIsFocused() {
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: "Ctrl+K should not be rerouted as command palette move-selection"
)
moveExpectation.isInverted = true
let moveToken = NotificationCenter.default.addObserver(
forName: .commandPaletteMoveSelection,
object: nil,
queue: nil
) { _ in
moveExpectation.fulfill()
}
defer { NotificationCenter.default.removeObserver(moveToken) }
guard let controlKEvent = makeKeyDownEvent(
key: "\u{0b}",
modifiers: [.control],
keyCode: 40,
windowNumber: window.windowNumber
) else {
XCTFail("Failed to construct Ctrl+K event")
return
}
#if DEBUG
XCTAssertFalse(appDelegate.debugHandleCustomShortcut(event: controlKEvent))
#else
XCTFail("debugHandleCustomShortcut is only available in DEBUG")
#endif
wait(for: [moveExpectation], timeout: 0.2)
}
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 testBrowserFirstFindShortcutRoutingRecognizesFindCommandFamily() {
let cases: [(name: String, modifiers: NSEvent.ModifierFlags, chars: String, keyCode: UInt16)] = [
("cmd-f", [.command], "f", 3),
("cmd-g", [.command], "g", 5),
("cmd-shift-g", [.command, .shift], "g", 5),
("cmd-shift-f", [.command, .shift], "f", 3),
("cmd-e", [.command], "e", 14),
]
for testCase in cases {
let event = makeKeyEvent(
modifierFlags: testCase.modifiers,
characters: testCase.chars,
charactersIgnoringModifiers: testCase.chars,
keyCode: testCase.keyCode
)
XCTAssertTrue(
shouldRouteBrowserFindCommandEquivalentThroughWebContentFirst(event),
"Expected browser-first routing for \(testCase.name)"
)
}
}
func testBrowserFirstFindShortcutRoutingFallsBackToKeyCodeForNonLatinInput() {
let event = makeKeyEvent(
modifierFlags: [.command],
characters: "",
charactersIgnoringModifiers: "а", // Cyrillic a from a non-Latin input source
keyCode: 3 // kVK_ANSI_F
)
XCTAssertTrue(
shouldRouteBrowserFindCommandEquivalentThroughWebContentFirst(event),
"Expected browser-first routing to keep Cmd+F eligible under non-Latin input"
)
}
func testBrowserFirstFindShortcutRoutingDoesNotUseANSIPositionsForMismatchedASCIICharacters() {
let cases: [(name: String, modifiers: NSEvent.ModifierFlags, chars: String, keyCode: UInt16)] = [
("cmd-u-on-ansi-f", [.command], "u", 3),
("cmd-o-on-ansi-g", [.command], "o", 5),
("cmd-period-on-ansi-e", [.command], ".", 14),
("cmd-shift-u-on-ansi-f", [.command, .shift], "u", 3),
("cmd-shift-o-on-ansi-g", [.command, .shift], "o", 5),
]
for testCase in cases {
let event = makeKeyEvent(
modifierFlags: testCase.modifiers,
characters: testCase.chars,
charactersIgnoringModifiers: testCase.chars,
keyCode: testCase.keyCode
)
XCTAssertFalse(
shouldRouteBrowserFindCommandEquivalentThroughWebContentFirst(event),
"Did not expect browser-first routing for mismatched ASCII shortcut \(testCase.name)"
)
}
}
func testBrowserFirstFindShortcutRoutingExcludesWebInspectorResponders() {
let inspectorContainer = FakeWKInspectorContainerView(frame: .zero)
let inspectorChild = NSView(frame: .zero)
inspectorContainer.addSubview(inspectorChild)
let event = makeKeyEvent(
modifierFlags: [.command],
characters: "f",
charactersIgnoringModifiers: "f",
keyCode: 3
)
XCTAssertFalse(
shouldRouteBrowserFindCommandEquivalentThroughWebContentFirst(
event,
responder: inspectorChild
),
"Did not expect browser-first routing while a Web Inspector responder is focused"
)
}
func testBrowserFirstFindShortcutRoutingExcludesNonFindCommands() {
let cases: [(name: String, modifiers: NSEvent.ModifierFlags, chars: String, keyCode: UInt16)] = [
("cmd-n", [.command], "n", 45),
("cmd-w", [.command], "w", 13),
("cmd-l", [.command], "l", 37),
("cmd-option-f", [.command, .option], "f", 3),
]
for testCase in cases {
let event = makeKeyEvent(
modifierFlags: testCase.modifiers,
characters: testCase.chars,
charactersIgnoringModifiers: testCase.chars,
keyCode: testCase.keyCode
)
XCTAssertFalse(
shouldRouteBrowserFindCommandEquivalentThroughWebContentFirst(event),
"Did not expect browser-first routing for \(testCase.name)"
)
}
}
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<UUID> {
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
}
}