cmux/cmuxTests/AppDelegateShortcutRoutingTests.swift
Lawrence Chen 4de975e6a4
Add workspace pages in the titlebar (#1030)
* Add workspace pages in the titlebar

* Add workspace pages UI test target entry

* Relax workspace pages UI test titlebar checks

* Use page close button in workspace pages UI test

* Stabilize workspace pages UI test interruptions

* Skip page close confirms in UI tests

* Clean up superseded workspace handoffs

* Tighten page hint UI assertions

---------

Co-authored-by: cmux <cmux@cmuxs-Mac-mini.local>
2026-03-06 21:23:11 -08:00

2828 lines
101 KiB
Swift

import XCTest
#if canImport(cmux_DEV)
@testable import cmux_DEV
#elseif canImport(cmux)
@testable import cmux
#endif
@MainActor
final class AppDelegateShortcutRoutingTests: XCTestCase {
private var savedShortcutsByAction: [KeyboardShortcutSettings.Action: StoredShortcut] = [:]
private var actionsWithPersistedShortcut: Set<KeyboardShortcutSettings.Action> = []
override func setUp() {
super.setUp()
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?.dismissNotificationsPopoverIfShown()
RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05))
for action in KeyboardShortcutSettings.Action.allCases {
if actionsWithPersistedShortcut.contains(action),
let savedShortcut = savedShortcutsByAction[action] {
KeyboardShortcutSettings.setShortcut(savedShortcut, for: action)
} else {
KeyboardShortcutSettings.resetShortcut(for: action)
}
}
super.tearDown()
}
func testCmdNUsesEventWindowContextWhenActiveManagerIsStale() {
guard let appDelegate = AppDelegate.shared else {
XCTFail("Expected AppDelegate.shared")
return
}
let firstWindowId = appDelegate.createMainWindow()
let secondWindowId = appDelegate.createMainWindow()
defer {
closeWindow(withId: firstWindowId)
closeWindow(withId: secondWindowId)
}
guard let firstManager = appDelegate.tabManagerFor(windowId: firstWindowId),
let secondManager = appDelegate.tabManagerFor(windowId: secondWindowId),
let secondWindow = window(withId: secondWindowId) else {
XCTFail("Expected both window contexts to exist")
return
}
let firstCount = firstManager.tabs.count
let secondCount = secondManager.tabs.count
XCTAssertTrue(appDelegate.focusMainWindow(windowId: firstWindowId))
guard let event = NSEvent.keyEvent(
with: .keyDown,
location: .zero,
modifierFlags: [.command],
timestamp: ProcessInfo.processInfo.systemUptime,
windowNumber: secondWindow.windowNumber,
context: nil,
characters: "n",
charactersIgnoringModifiers: "n",
isARepeat: false,
keyCode: 45
) else {
XCTFail("Failed to construct Cmd+N event")
return
}
#if DEBUG
XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event))
#else
XCTFail("debugHandleCustomShortcut is only available in DEBUG")
#endif
XCTAssertEqual(firstManager.tabs.count, firstCount, "Cmd+N should not add workspace to stale active window")
XCTAssertEqual(secondManager.tabs.count, secondCount + 1, "Cmd+N should add workspace to the event's window")
}
func testAddWorkspaceInPreferredMainWindowIgnoresStaleTabManagerPointer() {
guard let appDelegate = AppDelegate.shared else {
XCTFail("Expected AppDelegate.shared")
return
}
let firstWindowId = appDelegate.createMainWindow()
let secondWindowId = appDelegate.createMainWindow()
defer {
closeWindow(withId: firstWindowId)
closeWindow(withId: secondWindowId)
}
guard let firstManager = appDelegate.tabManagerFor(windowId: firstWindowId),
let secondManager = appDelegate.tabManagerFor(windowId: secondWindowId),
let secondWindow = window(withId: secondWindowId) else {
XCTFail("Expected both window contexts to exist")
return
}
let firstCount = firstManager.tabs.count
let secondCount = secondManager.tabs.count
secondWindow.makeKeyAndOrderFront(nil)
RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05))
// Force a stale app-level pointer to a different manager.
appDelegate.tabManager = firstManager
XCTAssertTrue(appDelegate.tabManager === firstManager)
_ = appDelegate.addWorkspaceInPreferredMainWindow()
XCTAssertEqual(firstManager.tabs.count, firstCount, "Stale pointer must not receive menu-driven workspace creation")
XCTAssertEqual(secondManager.tabs.count, secondCount + 1, "Workspace creation should target key/main window context")
}
func testCmdNResolvesEventWindowWhenObjectKeyLookupIsMismatched() {
guard let appDelegate = AppDelegate.shared else {
XCTFail("Expected AppDelegate.shared")
return
}
let firstWindowId = appDelegate.createMainWindow()
let secondWindowId = appDelegate.createMainWindow()
defer {
closeWindow(withId: firstWindowId)
closeWindow(withId: secondWindowId)
}
guard let firstManager = appDelegate.tabManagerFor(windowId: firstWindowId),
let secondManager = appDelegate.tabManagerFor(windowId: secondWindowId),
let secondWindow = window(withId: secondWindowId) else {
XCTFail("Expected both window contexts to exist")
return
}
secondWindow.makeKeyAndOrderFront(nil)
RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05))
#if DEBUG
XCTAssertTrue(appDelegate.debugInjectWindowContextKeyMismatch(windowId: secondWindowId))
#else
XCTFail("debugInjectWindowContextKeyMismatch is only available in DEBUG")
#endif
// Ensure stale active-manager pointer does not mask routing errors.
appDelegate.tabManager = firstManager
let firstCount = firstManager.tabs.count
let secondCount = secondManager.tabs.count
guard let event = NSEvent.keyEvent(
with: .keyDown,
location: .zero,
modifierFlags: [.command],
timestamp: ProcessInfo.processInfo.systemUptime,
windowNumber: secondWindow.windowNumber,
context: nil,
characters: "n",
charactersIgnoringModifiers: "n",
isARepeat: false,
keyCode: 45
) else {
XCTFail("Failed to construct Cmd+N event")
return
}
#if DEBUG
XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event))
#else
XCTFail("debugHandleCustomShortcut is only available in DEBUG")
#endif
XCTAssertEqual(firstManager.tabs.count, firstCount, "Cmd+N should not route to another window when object-key lookup misses")
XCTAssertEqual(secondManager.tabs.count, secondCount + 1, "Cmd+N should still route by event window metadata when object-key lookup misses")
}
func testAddWorkspaceInPreferredMainWindowUsesKeyWindowWhenObjectKeyLookupIsMismatched() {
guard let appDelegate = AppDelegate.shared else {
XCTFail("Expected AppDelegate.shared")
return
}
let firstWindowId = appDelegate.createMainWindow()
let secondWindowId = appDelegate.createMainWindow()
defer {
closeWindow(withId: firstWindowId)
closeWindow(withId: secondWindowId)
}
guard let firstManager = appDelegate.tabManagerFor(windowId: firstWindowId),
let secondManager = appDelegate.tabManagerFor(windowId: secondWindowId),
let secondWindow = window(withId: secondWindowId) else {
XCTFail("Expected both window contexts to exist")
return
}
secondWindow.makeKeyAndOrderFront(nil)
RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05))
#if DEBUG
XCTAssertTrue(appDelegate.debugInjectWindowContextKeyMismatch(windowId: secondWindowId))
#else
XCTFail("debugInjectWindowContextKeyMismatch is only available in DEBUG")
#endif
// Stale pointer should not receive the new workspace.
appDelegate.tabManager = firstManager
let firstCount = firstManager.tabs.count
let secondCount = secondManager.tabs.count
_ = appDelegate.addWorkspaceInPreferredMainWindow()
XCTAssertEqual(firstManager.tabs.count, firstCount, "Menu-driven add workspace should not route to stale window")
XCTAssertEqual(secondManager.tabs.count, secondCount + 1, "Menu-driven add workspace should still route to key window context when object-key lookup misses")
}
func 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 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 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 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 testOptionDigitPageShortcutFallsBackByKeyCodeOnSymbolFirstLayouts() {
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 firstPageId = workspace.activePage?.id else {
XCTFail("Expected test window and workspace")
return
}
_ = workspace.newPage(select: true)
let selectedBeforeShortcut = workspace.activePage?.id
XCTAssertNotEqual(selectedBeforeShortcut, firstPageId)
withTemporaryShortcut(action: .selectPage1) {
// Symbol-first layouts (for example AZERTY) can report "&" for the ANSI 1 key.
// Option+1 page selection should still match via keyCode fallback.
guard let event = makeKeyDownEvent(
key: "&",
modifiers: [.option],
keyCode: 18, // kVK_ANSI_1
windowNumber: window.windowNumber
) else {
XCTFail("Failed to construct Option+1 event on ANSI 1 key")
return
}
#if DEBUG
XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event))
#else
XCTFail("debugHandleCustomShortcut is only available in DEBUG")
#endif
}
XCTAssertEqual(workspace.activePage?.id, firstPageId)
XCTAssertNotEqual(workspace.activePage?.id, selectedBeforeShortcut)
}
func testOption9SelectsLastPageInEventWindowWhenActiveManagerIsStale() {
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,
let firstWorkspaceFirstPageId = firstWorkspace.activePage?.id,
let secondWorkspaceFirstPageId = secondWorkspace.activePage?.id else {
XCTFail("Expected both window contexts to exist")
return
}
_ = firstWorkspace.newPage(select: true)
firstWorkspace.selectPage(firstWorkspaceFirstPageId)
_ = secondWorkspace.newPage(select: true)
let secondWorkspaceLastPage = secondWorkspace.newPage(select: true)
secondWorkspace.selectPage(secondWorkspaceFirstPageId)
appDelegate.tabManager = firstManager
XCTAssertTrue(appDelegate.tabManager === firstManager)
guard let event = makeKeyDownEvent(
key: "9",
modifiers: [.option],
keyCode: 25, // kVK_ANSI_9
windowNumber: secondWindow.windowNumber
) else {
XCTFail("Failed to construct Option+9 event")
return
}
#if DEBUG
XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event))
#else
XCTFail("debugHandleCustomShortcut is only available in DEBUG")
#endif
XCTAssertEqual(
firstWorkspace.activePage?.id,
firstWorkspaceFirstPageId,
"Option+9 must not select a page in the stale active window"
)
XCTAssertEqual(
secondWorkspace.activePage?.id,
secondWorkspaceLastPage.id,
"Option+9 should select the last page in the event window"
)
XCTAssertTrue(appDelegate.tabManager === secondManager, "Shortcut routing should retarget active manager to event window")
}
func testCustomSelectLastPageShortcutOverrideIsHonored() {
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 firstPageId = workspace.activePage?.id else {
XCTFail("Expected test window and workspace")
return
}
_ = workspace.newPage(select: true)
let lastPage = workspace.newPage(select: true)
workspace.selectPage(firstPageId)
withTemporaryShortcut(
action: .selectLastPage,
shortcut: StoredShortcut(key: "0", command: false, shift: false, option: true, control: false)
) {
guard let event = makeKeyDownEvent(
key: "0",
modifiers: [.option],
keyCode: 29, // kVK_ANSI_0
windowNumber: window.windowNumber
) else {
XCTFail("Failed to construct custom Option+0 event")
return
}
#if DEBUG
XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event))
#else
XCTFail("debugHandleCustomShortcut is only available in DEBUG")
#endif
}
XCTAssertEqual(workspace.activePage?.id, lastPage.id)
XCTAssertNotEqual(workspace.activePage?.id, firstPageId)
}
func testOptionRightBracketPageShortcutFallsBackByKeyCodeOnNonUSLayouts() {
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 firstPageId = workspace.activePage?.id else {
XCTFail("Expected test window and workspace")
return
}
let secondPage = workspace.newPage(select: false)
workspace.selectPage(firstPageId)
withTemporaryShortcut(action: .nextPage) {
// Some non-US layouts can report unrelated symbols for the ANSI ] key.
// Option+] should still work via keyCode fallback.
guard let event = makeKeyDownEvent(
key: "*",
modifiers: [.option],
keyCode: 30, // kVK_ANSI_RightBracket
windowNumber: window.windowNumber
) else {
XCTFail("Failed to construct Option+] event on ANSI ] key")
return
}
#if DEBUG
XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event))
#else
XCTFail("debugHandleCustomShortcut is only available in DEBUG")
#endif
}
XCTAssertEqual(workspace.activePage?.id, secondPage.id)
XCTAssertNotEqual(workspace.activePage?.id, firstPageId)
}
func testCmdOptionNCreatesPageInEventWindowWhenActiveManagerIsStale() {
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 firstPageCount = firstWorkspace.pages.count
let secondPageCount = secondWorkspace.pages.count
appDelegate.tabManager = firstManager
XCTAssertTrue(appDelegate.tabManager === firstManager)
guard let event = makeKeyDownEvent(
key: "n",
modifiers: [.command, .option],
keyCode: 45, // kVK_ANSI_N
windowNumber: secondWindow.windowNumber
) else {
XCTFail("Failed to construct Cmd+Option+N event")
return
}
#if DEBUG
XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event))
#else
XCTFail("debugHandleCustomShortcut is only available in DEBUG")
#endif
XCTAssertEqual(firstWorkspace.pages.count, firstPageCount, "Cmd+Option+N must not create a page in stale active window")
XCTAssertEqual(secondWorkspace.pages.count, secondPageCount + 1, "Cmd+Option+N should create a page in the event window")
XCTAssertTrue(appDelegate.tabManager === secondManager, "Shortcut routing should retarget active manager to event window")
}
func testDuplicatePagePreservesStructureAndRemapsPanelIdentity() {
guard let appDelegate = AppDelegate.shared else {
XCTFail("Expected AppDelegate.shared")
return
}
let windowId = appDelegate.createMainWindow()
defer { closeWindow(withId: windowId) }
guard let manager = appDelegate.tabManagerFor(windowId: windowId),
let workspace = manager.selectedWorkspace,
let sourcePageId = workspace.activePage?.id else {
XCTFail("Expected test window and workspace")
return
}
RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05))
guard let sourceStructure = workspace.pageStructureSummary(pageId: sourcePageId),
let sourceSnapshot = workspace.pageStateSnapshot(pageId: sourcePageId, includeScrollback: false) else {
XCTFail("Expected source page snapshot")
return
}
guard let duplicatePage = workspace.duplicatePage(sourcePageId: sourcePageId, select: false) else {
XCTFail("Expected duplicate page")
return
}
guard let duplicateStructure = workspace.pageStructureSummary(pageId: duplicatePage.id),
let duplicateSnapshot = workspace.pageStateSnapshot(pageId: duplicatePage.id, includeScrollback: false) else {
XCTFail("Expected duplicate page snapshot")
return
}
XCTAssertEqual(duplicateStructure.paneCount, sourceStructure.paneCount)
XCTAssertEqual(duplicateStructure.surfaceCount, sourceStructure.surfaceCount)
XCTAssertEqual(duplicatePage.title, "Page 1 Copy")
XCTAssertEqual(sourceSnapshot.panels.count, duplicateSnapshot.panels.count)
if let sourcePanelId = sourceSnapshot.panels.first?.id,
let duplicatePanelId = duplicateSnapshot.panels.first?.id {
XCTAssertNotEqual(sourcePanelId, duplicatePanelId, "Duplicated pages should remap panel identities")
} else {
XCTFail("Expected duplicated page snapshots to include at least one panel")
}
}
func testClosingActivePageSelectsNearestRightNeighbor() {
guard let appDelegate = AppDelegate.shared else {
XCTFail("Expected AppDelegate.shared")
return
}
let windowId = appDelegate.createMainWindow()
defer { closeWindow(withId: windowId) }
guard let manager = appDelegate.tabManagerFor(windowId: windowId),
let workspace = manager.selectedWorkspace,
let firstPageId = workspace.activePage?.id else {
XCTFail("Expected test window and workspace")
return
}
let middlePage = workspace.newPage(select: false)
let rightPage = workspace.newPage(select: false)
workspace.selectPage(middlePage.id)
workspace.closePage(middlePage.id, skipConfirmation: true)
XCTAssertEqual(workspace.activePage?.id, rightPage.id)
XCTAssertEqual(workspace.pages.map(\.id), [firstPageId, rightPage.id])
}
func testV2PageListIncludesStoredAndActivePageStructureCounts() {
guard let appDelegate = AppDelegate.shared else {
XCTFail("Expected AppDelegate.shared")
return
}
let windowId = appDelegate.createMainWindow()
defer {
TerminalController.shared.setActiveTabManager(nil)
closeWindow(withId: windowId)
}
guard let manager = appDelegate.tabManagerFor(windowId: windowId),
let workspace = manager.selectedWorkspace,
let firstPageId = workspace.activePage?.id,
let firstPaneId = workspace.bonsplitController.allPaneIds.first else {
XCTFail("Expected test window and workspace")
return
}
workspace.setPageTitle(pageId: firstPageId, title: "Agents")
XCTAssertNotNil(workspace.newTerminalSurface(inPane: firstPaneId, focus: false))
let secondPage = workspace.newPage(select: true)
workspace.setPageTitle(pageId: secondPage.id, title: "Editor")
TerminalController.shared.setActiveTabManager(manager)
let result = v2Result(
method: "page.list",
params: ["workspace_id": workspace.id.uuidString]
)
let pages = result["pages"] as? [[String: Any]]
XCTAssertEqual(result["page_id"] as? String, secondPage.id.uuidString)
XCTAssertEqual(result["page_title"] as? String, "Editor")
XCTAssertEqual(pages?.count, 2)
let pagesByTitle = Dictionary(
uniqueKeysWithValues: (pages ?? []).compactMap { page -> (String, [String: Any])? in
guard let title = page["title"] as? String else { return nil }
return (title, page)
}
)
XCTAssertEqual(pagesByTitle["Agents"]?["pane_count"] as? Int, 1)
XCTAssertEqual(pagesByTitle["Agents"]?["surface_count"] as? Int, 2)
XCTAssertEqual(pagesByTitle["Agents"]?["selected"] as? Bool, false)
XCTAssertEqual(pagesByTitle["Editor"]?["pane_count"] as? Int, 1)
XCTAssertEqual(pagesByTitle["Editor"]?["surface_count"] as? Int, 1)
XCTAssertEqual(pagesByTitle["Editor"]?["selected"] as? Bool, true)
}
func testV2PageSelectAndCurrentReturnUpdatedSelection() {
guard let appDelegate = AppDelegate.shared else {
XCTFail("Expected AppDelegate.shared")
return
}
let windowId = appDelegate.createMainWindow()
defer {
TerminalController.shared.setActiveTabManager(nil)
closeWindow(withId: windowId)
}
guard let manager = appDelegate.tabManagerFor(windowId: windowId),
let workspace = manager.selectedWorkspace,
let firstPageId = workspace.activePage?.id else {
XCTFail("Expected test window and workspace")
return
}
workspace.setPageTitle(pageId: firstPageId, title: "Agents")
let secondPage = workspace.newPage(select: false)
workspace.setPageTitle(pageId: secondPage.id, title: "Editor")
workspace.selectPage(firstPageId)
TerminalController.shared.setActiveTabManager(manager)
let selectResult = v2Result(
method: "page.select",
params: [
"workspace_id": workspace.id.uuidString,
"page_id": secondPage.id.uuidString
]
)
XCTAssertEqual(workspace.activePageId, secondPage.id)
XCTAssertEqual(selectResult["page_id"] as? String, secondPage.id.uuidString)
XCTAssertEqual(selectResult["page_title"] as? String, "Editor")
XCTAssertEqual(selectResult["workspace_id"] as? String, workspace.id.uuidString)
let currentResult = v2Result(
method: "page.current",
params: ["workspace_id": workspace.id.uuidString]
)
XCTAssertEqual(currentResult["page_id"] as? String, secondPage.id.uuidString)
XCTAssertEqual(currentResult["page_title"] as? String, "Editor")
}
func testV2SystemTreeIncludesPagesAndSelectedPagePaneMirror() {
guard let appDelegate = AppDelegate.shared else {
XCTFail("Expected AppDelegate.shared")
return
}
let windowId = appDelegate.createMainWindow()
defer {
TerminalController.shared.setActiveTabManager(nil)
closeWindow(withId: windowId)
}
guard let manager = appDelegate.tabManagerFor(windowId: windowId),
let workspace = manager.selectedWorkspace,
let firstPageId = workspace.activePage?.id,
let firstPaneId = workspace.bonsplitController.allPaneIds.first else {
XCTFail("Expected test window and workspace")
return
}
workspace.setPageTitle(pageId: firstPageId, title: "Agents")
XCTAssertNotNil(workspace.newTerminalSurface(inPane: firstPaneId, focus: false))
let secondPage = workspace.newPage(select: true)
workspace.setPageTitle(pageId: secondPage.id, title: "Editor")
TerminalController.shared.setActiveTabManager(manager)
let result = v2Result(
method: "system.tree",
params: ["workspace_id": workspace.id.uuidString]
)
guard let windows = result["windows"] as? [[String: Any]],
let window = windows.first,
let workspaces = window["workspaces"] as? [[String: Any]],
let workspaceNode = workspaces.first,
let pages = workspaceNode["pages"] as? [[String: Any]] else {
XCTFail("Expected system.tree page hierarchy")
return
}
XCTAssertEqual(windows.count, 1)
XCTAssertEqual(workspaceNode["selected_page_id"] as? String, secondPage.id.uuidString)
XCTAssertEqual(pages.count, 2)
let pagesByTitle = Dictionary(
uniqueKeysWithValues: pages.compactMap { page -> (String, [String: Any])? in
guard let title = page["title"] as? String else { return nil }
return (title, page)
}
)
let agentsPage = pagesByTitle["Agents"]
let editorPage = pagesByTitle["Editor"]
XCTAssertEqual(agentsPage?["selected"] as? Bool, false)
XCTAssertEqual(editorPage?["selected"] as? Bool, true)
XCTAssertEqual((agentsPage?["panes"] as? [[String: Any]])?.count, 1)
XCTAssertEqual((editorPage?["panes"] as? [[String: Any]])?.count, 1)
let agentsSurfaceCount = ((agentsPage?["panes"] as? [[String: Any]])?.first?["surfaces"] as? [[String: Any]])?.count
let editorSurfaceCount = ((editorPage?["panes"] as? [[String: Any]])?.first?["surfaces"] as? [[String: Any]])?.count
let mirroredSurfaceCount = ((workspaceNode["panes"] as? [[String: Any]])?.first?["surfaces"] as? [[String: Any]])?.count
XCTAssertEqual(agentsSurfaceCount, 2)
XCTAssertEqual(editorSurfaceCount, 1)
XCTAssertEqual(mirroredSurfaceCount, editorSurfaceCount)
}
func testCmdShiftNonDigitKeySymbolDoesNotMatchShiftedDigitShortcut() {
guard let appDelegate = AppDelegate.shared else {
XCTFail("Expected AppDelegate.shared")
return
}
let windowId = appDelegate.createMainWindow()
defer { closeWindow(withId: windowId) }
guard let window = window(withId: windowId) else {
XCTFail("Expected test window")
return
}
withTemporaryShortcut(
action: .showNotifications,
shortcut: StoredShortcut(key: "8", command: true, shift: true, option: false, control: false)
) {
// Avoid unrelated default Cmd+Shift+] handling for this assertion.
withTemporaryShortcut(
action: .nextSurface,
shortcut: StoredShortcut(key: "x", command: true, shift: true, option: false, control: false)
) {
// On some non-US layouts, Shift+RightBracket can produce "*".
// This must not be interpreted as Shift+8.
guard let event = NSEvent.keyEvent(
with: .keyDown,
location: .zero,
modifierFlags: [.command, .shift],
timestamp: ProcessInfo.processInfo.systemUptime,
windowNumber: window.windowNumber,
context: nil,
characters: "*",
charactersIgnoringModifiers: "*",
isARepeat: false,
keyCode: 30 // kVK_ANSI_RightBracket
) else {
XCTFail("Failed to construct Cmd+Shift+* event from non-digit key")
return
}
#if DEBUG
XCTAssertFalse(appDelegate.debugHandleCustomShortcut(event: event))
#else
XCTFail("debugHandleCustomShortcut is only available in DEBUG")
#endif
}
}
}
func testCmdShiftDigitShortcutMatchesShiftedDigitKey() {
guard let appDelegate = AppDelegate.shared else {
XCTFail("Expected AppDelegate.shared")
return
}
let windowId = appDelegate.createMainWindow()
defer { closeWindow(withId: windowId) }
guard let window = window(withId: windowId) else {
XCTFail("Expected test window")
return
}
withTemporaryShortcut(
action: .showNotifications,
shortcut: StoredShortcut(key: "8", command: true, shift: true, option: false, control: false)
) {
guard let event = NSEvent.keyEvent(
with: .keyDown,
location: .zero,
modifierFlags: [.command, .shift],
timestamp: ProcessInfo.processInfo.systemUptime,
windowNumber: window.windowNumber,
context: nil,
characters: "*",
charactersIgnoringModifiers: "*",
isARepeat: false,
keyCode: 28 // kVK_ANSI_8
) else {
XCTFail("Failed to construct Cmd+Shift+8 event")
return
}
#if DEBUG
XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event))
#else
XCTFail("debugHandleCustomShortcut is only available in DEBUG")
#endif
}
}
func testCmdShiftQuestionMarkMatchesSlashShortcut() {
guard let appDelegate = AppDelegate.shared else {
XCTFail("Expected AppDelegate.shared")
return
}
let windowId = appDelegate.createMainWindow()
defer { closeWindow(withId: windowId) }
guard let window = window(withId: windowId) else {
XCTFail("Expected test window")
return
}
withTemporaryShortcut(
action: .triggerFlash,
shortcut: StoredShortcut(key: "/", command: true, shift: true, option: false, control: false)
) {
guard let event = NSEvent.keyEvent(
with: .keyDown,
location: .zero,
modifierFlags: [.command, .shift],
timestamp: ProcessInfo.processInfo.systemUptime,
windowNumber: window.windowNumber,
context: nil,
characters: "?",
charactersIgnoringModifiers: "?",
isARepeat: false,
keyCode: 44 // kVK_ANSI_Slash
) else {
XCTFail("Failed to construct Cmd+Shift+/ event")
return
}
#if DEBUG
XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event))
#else
XCTFail("debugHandleCustomShortcut is only available in DEBUG")
#endif
}
}
func testCmdShiftISOAngleBracketDoesNotMatchCommaShortcut() {
guard let appDelegate = AppDelegate.shared else {
XCTFail("Expected AppDelegate.shared")
return
}
let windowId = appDelegate.createMainWindow()
defer { closeWindow(withId: windowId) }
guard let window = window(withId: windowId) else {
XCTFail("Expected test window")
return
}
withTemporaryShortcut(
action: .showNotifications,
shortcut: StoredShortcut(key: ",", command: true, shift: true, option: false, control: false)
) {
guard let event = NSEvent.keyEvent(
with: .keyDown,
location: .zero,
modifierFlags: [.command, .shift],
timestamp: ProcessInfo.processInfo.systemUptime,
windowNumber: window.windowNumber,
context: nil,
characters: "<",
charactersIgnoringModifiers: "<",
isARepeat: false,
keyCode: 10 // kVK_ISO_Section
) else {
XCTFail("Failed to construct Cmd+Shift+< event from ISO key")
return
}
#if DEBUG
XCTAssertFalse(appDelegate.debugHandleCustomShortcut(event: event))
#else
XCTFail("debugHandleCustomShortcut is only available in DEBUG")
#endif
}
}
func testCmdShiftRightBracketCanFallbackByKeyCodeOnNonUSLayouts() {
guard let appDelegate = AppDelegate.shared else {
XCTFail("Expected AppDelegate.shared")
return
}
let windowId = appDelegate.createMainWindow()
defer { closeWindow(withId: windowId) }
guard let window = window(withId: windowId) else {
XCTFail("Expected test window")
return
}
withTemporaryShortcut(action: .nextSurface) {
// Non-US layouts can report "*" (or other symbols) for kVK_ANSI_RightBracket with Shift.
// Shortcut matching should still allow Cmd+Shift+] via keyCode fallback.
guard let event = NSEvent.keyEvent(
with: .keyDown,
location: .zero,
modifierFlags: [.command, .shift],
timestamp: ProcessInfo.processInfo.systemUptime,
windowNumber: window.windowNumber,
context: nil,
characters: "*",
charactersIgnoringModifiers: "*",
isARepeat: false,
keyCode: 30 // kVK_ANSI_RightBracket
) else {
XCTFail("Failed to construct non-US Cmd+Shift+] event")
return
}
#if DEBUG
XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event))
#else
XCTFail("debugHandleCustomShortcut is only available in DEBUG")
#endif
}
}
func testCmdPhysicalOWithDvorakCharactersTriggersRenameTabShortcut() {
guard let appDelegate = AppDelegate.shared else {
XCTFail("Expected AppDelegate.shared")
return
}
let windowId = appDelegate.createMainWindow()
defer { closeWindow(withId: windowId) }
guard let window = window(withId: windowId) else {
XCTFail("Expected test window")
return
}
let renameTabExpectation = expectation(description: "Expected rename tab request for semantic Cmd+R")
var observedRenameTabWindow: NSWindow?
let renameTabToken = NotificationCenter.default.addObserver(
forName: .commandPaletteRenameTabRequested,
object: nil,
queue: nil
) { notification in
observedRenameTabWindow = notification.object as? NSWindow
renameTabExpectation.fulfill()
}
defer { NotificationCenter.default.removeObserver(renameTabToken) }
let switcherExpectation = expectation(description: "Cmd+R should not trigger command palette switcher")
switcherExpectation.isInverted = true
let switcherToken = NotificationCenter.default.addObserver(
forName: .commandPaletteSwitcherRequested,
object: nil,
queue: nil
) { _ in
switcherExpectation.fulfill()
}
defer { NotificationCenter.default.removeObserver(switcherToken) }
withTemporaryShortcut(action: .renameTab) {
// Dvorak: physical ANSI "O" key can produce "r".
// This should behave as semantic Cmd+R (rename tab), not Cmd+P.
guard let event = NSEvent.keyEvent(
with: .keyDown,
location: .zero,
modifierFlags: [.command],
timestamp: ProcessInfo.processInfo.systemUptime,
windowNumber: window.windowNumber,
context: nil,
characters: "r",
charactersIgnoringModifiers: "r",
isARepeat: false,
keyCode: 31 // kVK_ANSI_O
) else {
XCTFail("Failed to construct Dvorak Cmd+R event on physical ANSI O key")
return
}
#if DEBUG
XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event))
#else
XCTFail("debugHandleCustomShortcut is only available in DEBUG")
#endif
}
wait(for: [renameTabExpectation, switcherExpectation], timeout: 1.0)
XCTAssertEqual(observedRenameTabWindow?.windowNumber, window.windowNumber)
}
func testCmdPhysicalRWithDvorakCharactersTriggersCommandPaletteSwitcher() {
guard let appDelegate = AppDelegate.shared else {
XCTFail("Expected AppDelegate.shared")
return
}
let windowId = appDelegate.createMainWindow()
defer { closeWindow(withId: windowId) }
guard let window = window(withId: windowId) else {
XCTFail("Expected test window")
return
}
let switcherExpectation = expectation(description: "Expected command palette switcher request for semantic Cmd+P")
var observedSwitcherWindow: NSWindow?
let switcherToken = NotificationCenter.default.addObserver(
forName: .commandPaletteSwitcherRequested,
object: nil,
queue: nil
) { notification in
observedSwitcherWindow = notification.object as? NSWindow
switcherExpectation.fulfill()
}
defer { NotificationCenter.default.removeObserver(switcherToken) }
let renameTabExpectation = expectation(description: "Physical R on Dvorak should not trigger rename tab")
renameTabExpectation.isInverted = true
let renameTabToken = NotificationCenter.default.addObserver(
forName: .commandPaletteRenameTabRequested,
object: nil,
queue: nil
) { _ in
renameTabExpectation.fulfill()
}
defer { NotificationCenter.default.removeObserver(renameTabToken) }
// Dvorak: physical ANSI "R" key can produce "p".
// This should behave as semantic Cmd+P (palette switcher), not Cmd+R.
guard let event = NSEvent.keyEvent(
with: .keyDown,
location: .zero,
modifierFlags: [.command],
timestamp: ProcessInfo.processInfo.systemUptime,
windowNumber: window.windowNumber,
context: nil,
characters: "p",
charactersIgnoringModifiers: "p",
isARepeat: false,
keyCode: 15 // kVK_ANSI_R
) else {
XCTFail("Failed to construct Dvorak Cmd+P event on physical ANSI R key")
return
}
#if DEBUG
XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event))
#else
XCTFail("debugHandleCustomShortcut is only available in DEBUG")
#endif
wait(for: [switcherExpectation, renameTabExpectation], timeout: 1.0)
XCTAssertEqual(observedSwitcherWindow?.windowNumber, window.windowNumber)
}
func testCmdShiftRRequestsRenameWorkspaceInCommandPalette() {
guard let appDelegate = AppDelegate.shared else {
XCTFail("Expected AppDelegate.shared")
return
}
let windowId = appDelegate.createMainWindow()
defer {
closeWindow(withId: windowId)
}
guard let window = window(withId: windowId) else {
XCTFail("Expected test window")
return
}
let workspaceExpectation = expectation(description: "Expected command palette rename workspace notification")
var observedWorkspaceWindow: NSWindow?
var didObserveWorkspaceNotification = false
let workspaceToken = NotificationCenter.default.addObserver(
forName: .commandPaletteRenameWorkspaceRequested,
object: nil,
queue: nil
) { notification in
guard !didObserveWorkspaceNotification else { return }
didObserveWorkspaceNotification = true
observedWorkspaceWindow = notification.object as? NSWindow
workspaceExpectation.fulfill()
}
defer { NotificationCenter.default.removeObserver(workspaceToken) }
let renameTabExpectation = expectation(description: "Rename tab notification should not fire for Cmd+Shift+R")
renameTabExpectation.isInverted = true
let renameTabToken = NotificationCenter.default.addObserver(
forName: .commandPaletteRenameTabRequested,
object: nil,
queue: nil
) { _ in
renameTabExpectation.fulfill()
}
defer { NotificationCenter.default.removeObserver(renameTabToken) }
guard let event = makeKeyDownEvent(
key: "r",
modifiers: [.command, .shift],
keyCode: 15, // kVK_ANSI_R
windowNumber: window.windowNumber
) else {
XCTFail("Failed to construct Cmd+Shift+R event")
return
}
#if DEBUG
XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event))
#else
XCTFail("debugHandleCustomShortcut is only available in DEBUG")
#endif
wait(for: [workspaceExpectation, renameTabExpectation], timeout: 1.0)
XCTAssertEqual(observedWorkspaceWindow?.windowNumber, window.windowNumber)
}
func testEscapeDismissesVisibleCommandPaletteAndIsConsumed() {
guard let appDelegate = AppDelegate.shared else {
XCTFail("Expected AppDelegate.shared")
return
}
let windowId = appDelegate.createMainWindow()
defer {
closeWindow(withId: windowId)
}
guard let window = window(withId: windowId) else {
XCTFail("Expected test window")
return
}
appDelegate.setCommandPaletteVisible(true, for: window)
defer {
appDelegate.setCommandPaletteVisible(false, for: window)
}
let dismissExpectation = expectation(description: "Expected command palette toggle notification for Escape dismiss")
var observedDismissWindow: NSWindow?
let dismissToken = NotificationCenter.default.addObserver(
forName: .commandPaletteToggleRequested,
object: nil,
queue: nil
) { notification in
observedDismissWindow = notification.object as? NSWindow
dismissExpectation.fulfill()
}
defer { NotificationCenter.default.removeObserver(dismissToken) }
guard let event = makeKeyDownEvent(
key: "\u{1b}",
modifiers: [],
keyCode: 53, // kVK_Escape
windowNumber: window.windowNumber
) else {
XCTFail("Failed to construct Escape event")
return
}
#if DEBUG
XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: event))
#else
XCTFail("debugHandleCustomShortcut is only available in DEBUG")
#endif
wait(for: [dismissExpectation], timeout: 1.0)
XCTAssertEqual(observedDismissWindow?.windowNumber, window.windowNumber)
}
func testEscapeDoesNotDismissCommandPaletteWhenInputHasMarkedText() {
guard let appDelegate = AppDelegate.shared else {
XCTFail("Expected AppDelegate.shared")
return
}
let windowId = appDelegate.createMainWindow()
defer {
closeWindow(withId: windowId)
}
guard let window = window(withId: windowId) else {
XCTFail("Expected test window")
return
}
let fieldEditor = CommandPaletteMarkedTextFieldEditor(frame: NSRect(x: 0, y: 0, width: 200, height: 24))
fieldEditor.isFieldEditor = true
fieldEditor.hasMarkedTextForTesting = true
window.contentView?.addSubview(fieldEditor)
XCTAssertTrue(window.makeFirstResponder(fieldEditor))
appDelegate.setCommandPaletteVisible(true, for: window)
defer {
appDelegate.setCommandPaletteVisible(false, for: window)
fieldEditor.removeFromSuperview()
}
let dismissExpectation = expectation(
description: "Escape should not dismiss command palette while IME marked text is active"
)
dismissExpectation.isInverted = true
let dismissToken = NotificationCenter.default.addObserver(
forName: .commandPaletteToggleRequested,
object: nil,
queue: nil
) { notification in
guard let dismissWindow = notification.object as? NSWindow,
dismissWindow.windowNumber == window.windowNumber else { return }
dismissExpectation.fulfill()
}
defer { NotificationCenter.default.removeObserver(dismissToken) }
guard let escapeEvent = makeKeyDownEvent(
key: "\u{1b}",
modifiers: [],
keyCode: 53,
windowNumber: window.windowNumber
) else {
XCTFail("Failed to construct Escape event")
return
}
#if DEBUG
XCTAssertFalse(
appDelegate.debugHandleCustomShortcut(event: escapeEvent),
"Escape should pass through to IME composition instead of dismissing command palette"
)
#else
XCTFail("debugHandleCustomShortcut is only available in DEBUG")
#endif
wait(for: [dismissExpectation], timeout: 0.2)
}
func testEscapeDismissesCommandPaletteWhenVisibilitySyncLagsAfterOpenRequest() {
guard let appDelegate = AppDelegate.shared else {
XCTFail("Expected AppDelegate.shared")
return
}
let windowId = appDelegate.createMainWindow()
defer {
closeWindow(withId: windowId)
}
guard let window = window(withId: windowId) else {
XCTFail("Expected test window")
return
}
let dismissExpectation = expectation(description: "Expected command palette dismiss notification for Escape")
var observedDismissWindow: NSWindow?
let dismissToken = NotificationCenter.default.addObserver(
forName: .commandPaletteToggleRequested,
object: nil,
queue: nil
) { notification in
observedDismissWindow = notification.object as? NSWindow
dismissExpectation.fulfill()
}
defer { NotificationCenter.default.removeObserver(dismissToken) }
#if DEBUG
appDelegate.debugMarkCommandPaletteOpenPending(window: window)
#else
XCTFail("debugMarkCommandPaletteOpenPending is only available in DEBUG")
#endif
// Simulate a visibility sync lag/race where AppDelegate does not yet know the palette is open.
appDelegate.setCommandPaletteVisible(false, for: window)
guard let escapeEvent = makeKeyDownEvent(
key: "\u{1b}",
modifiers: [],
keyCode: 53,
windowNumber: window.windowNumber
) else {
XCTFail("Failed to construct Escape event")
return
}
#if DEBUG
XCTAssertTrue(appDelegate.debugHandleCustomShortcut(event: escapeEvent))
#else
XCTFail("debugHandleCustomShortcut is only available in DEBUG")
#endif
wait(for: [dismissExpectation], timeout: 1.0)
XCTAssertEqual(observedDismissWindow?.windowNumber, window.windowNumber)
}
func 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)
}
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 v2Result(
method: String,
params: [String: Any] = [:],
file: StaticString = #filePath,
line: UInt = #line
) -> [String: Any] {
let request: [String: Any] = [
"id": "test",
"method": method,
"params": params
]
guard JSONSerialization.isValidJSONObject(request) else {
XCTFail("Expected valid JSON request", file: file, line: line)
return [:]
}
let requestData: Data
do {
requestData = try JSONSerialization.data(withJSONObject: request, options: [])
} catch {
XCTFail("Failed to encode request JSON: \(error)", file: file, line: line)
return [:]
}
guard let requestString = String(data: requestData, encoding: .utf8) else {
XCTFail("Failed to encode UTF-8 request", file: file, line: line)
return [:]
}
let responseString: String
#if DEBUG
responseString = TerminalController.shared.debugProcessV2Command(requestString)
#else
XCTFail("debugProcessV2Command is only available in DEBUG", file: file, line: line)
return [:]
#endif
guard let responseData = responseString.data(using: .utf8) else {
XCTFail("Failed to decode UTF-8 response", file: file, line: line)
return [:]
}
let responseObject: Any
do {
responseObject = try JSONSerialization.jsonObject(with: responseData, options: [])
} catch {
XCTFail("Failed to decode response JSON: \(error)", file: file, line: line)
return [:]
}
guard let response = responseObject as? [String: Any] else {
XCTFail("Expected JSON object response", file: file, line: line)
return [:]
}
let isOK = (response["ok"] as? Bool) == true
XCTAssertTrue(isOK, "Expected successful v2 response: \(responseString)", file: file, line: line)
guard let result = response["result"] as? [String: Any] else {
XCTFail("Expected result payload in response: \(responseString)", file: file, line: line)
return [:]
}
return result
}
private func window(withId windowId: UUID) -> NSWindow? {
let identifier = "cmux.main.\(windowId.uuidString)"
return NSApp.windows.first(where: { $0.identifier?.rawValue == identifier })
}
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 final class CommandPaletteMarkedTextFieldEditor: NSTextView {
var hasMarkedTextForTesting = false
override func hasMarkedText() -> Bool {
hasMarkedTextForTesting
}
}