53b4f60da1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
236 lines
11 KiB
Swift
236 lines
11 KiB
Swift
import Foundation
|
|
|
|
@main
|
|
enum ShortcutTests {
|
|
static func main() {
|
|
testDefaultsExhaustive()
|
|
testAllDefaultsHaveModifier()
|
|
testNoDefaultDuplicates()
|
|
testCarbonModifiers()
|
|
testDisplayString()
|
|
testDuplicateDetection()
|
|
testLoadSaveRoundTrip()
|
|
testLegacyKeysForToggleIsland()
|
|
testEnabledByDefault()
|
|
testKeyCodeToString()
|
|
testCardNavigation()
|
|
print("ShortcutLogic: all cases passed")
|
|
}
|
|
|
|
// MARK: - testDefaultsExhaustive
|
|
|
|
static func testDefaultsExhaustive() {
|
|
for action in ShortcutAction.allCases {
|
|
precondition(ShortcutLogic.defaults[action] != nil,
|
|
"missing default for \(action.rawValue)")
|
|
}
|
|
precondition(ShortcutLogic.defaults.count == ShortcutAction.allCases.count,
|
|
"defaults table must cover every ShortcutAction")
|
|
}
|
|
|
|
// MARK: - testAllDefaultsHaveModifier
|
|
|
|
static func testAllDefaultsHaveModifier() {
|
|
for (action, spec) in ShortcutLogic.defaults {
|
|
precondition(spec.hasModifier,
|
|
"default for \(action.rawValue) has no modifier — bare keys not allowed")
|
|
}
|
|
}
|
|
|
|
// MARK: - testNoDefaultDuplicates
|
|
|
|
static func testNoDefaultDuplicates() {
|
|
let dups = ShortcutLogic.duplicates(in: ShortcutLogic.defaults)
|
|
precondition(dups.isEmpty,
|
|
"duplicate default shortcuts: \(dups.map { $0.rawValue })")
|
|
}
|
|
|
|
// MARK: - testCarbonModifiers
|
|
|
|
static func testCarbonModifiers() {
|
|
// cmdShift = ⌘⇧
|
|
let cmdShiftCarbon = ShortcutLogic.carbonModifiers(fromNS: ShortcutSpec.cmdShift)
|
|
// Carbon: cmdKey = 256, shiftKey = 512
|
|
precondition(cmdShiftCarbon & 256 != 0, "⌘ must set cmdKey bit in Carbon")
|
|
precondition(cmdShiftCarbon & 512 != 0, "⇧ must set shiftKey bit in Carbon")
|
|
precondition(cmdShiftCarbon & 2048 == 0, "⌘⇧ must not set optionKey")
|
|
precondition(cmdShiftCarbon & 4096 == 0, "⌘⇧ must not set controlKey")
|
|
|
|
// ctrlOpt = ⌃⌥
|
|
let ctrlOptCarbon = ShortcutLogic.carbonModifiers(fromNS: ShortcutSpec.ctrlOpt)
|
|
precondition(ctrlOptCarbon & 256 == 0, "⌃⌥ must not set cmdKey")
|
|
precondition(ctrlOptCarbon & 512 == 0, "⌃⌥ must not set shiftKey")
|
|
precondition(ctrlOptCarbon & 2048 != 0, "⌥ must set optionKey bit in Carbon")
|
|
precondition(ctrlOptCarbon & 4096 != 0, "⌃ must set controlKey bit in Carbon")
|
|
|
|
// Zero flags → zero Carbon flags
|
|
precondition(ShortcutLogic.carbonModifiers(fromNS: 0) == 0,
|
|
"no modifiers must produce zero Carbon flags")
|
|
|
|
// All four modifiers
|
|
let allNS = ShortcutSpec.cmdBit | ShortcutSpec.shiftBit |
|
|
ShortcutSpec.optBit | ShortcutSpec.ctrlBit
|
|
let allCarbon = ShortcutLogic.carbonModifiers(fromNS: allNS)
|
|
precondition(allCarbon & (256 | 512 | 2048 | 4096) == (256 | 512 | 2048 | 4096),
|
|
"all four NS modifiers must map to all four Carbon bits")
|
|
}
|
|
|
|
// MARK: - testDisplayString
|
|
|
|
static func testDisplayString() {
|
|
// ⌘⇧N
|
|
let cmdShiftN = ShortcutSpec(keyCode: 45, nsFlags: ShortcutSpec.cmdShift)
|
|
let s1 = ShortcutLogic.displayString(for: cmdShiftN)
|
|
precondition(s1.contains("⌘"), "⌘⇧N display must contain ⌘")
|
|
precondition(s1.contains("⇧"), "⌘⇧N display must contain ⇧")
|
|
precondition(s1.contains("N"), "⌘⇧N display must contain N")
|
|
|
|
// ⌃⌥A
|
|
let ctrlOptA = ShortcutSpec(keyCode: 0, nsFlags: ShortcutSpec.ctrlOpt)
|
|
let s2 = ShortcutLogic.displayString(for: ctrlOptA)
|
|
precondition(s2.contains("⌃"), "⌃⌥A display must contain ⌃")
|
|
precondition(s2.contains("⌥"), "⌃⌥A display must contain ⌥")
|
|
precondition(s2.contains("A"), "⌃⌥A display must contain A")
|
|
|
|
// No duplicate modifiers in the string
|
|
precondition(s1.filter { $0 == "⌘" }.count == 1, "⌘ must appear exactly once")
|
|
|
|
// ⌃⌥Space
|
|
let ctrlOptSpace = ShortcutSpec(keyCode: 49, nsFlags: ShortcutSpec.ctrlOpt)
|
|
let s3 = ShortcutLogic.displayString(for: ctrlOptSpace)
|
|
precondition(s3.contains("Space"), "⌃⌥Space display must contain 'Space'")
|
|
}
|
|
|
|
// MARK: - testDuplicateDetection
|
|
|
|
static func testDuplicateDetection() {
|
|
// No duplicates in an empty dict
|
|
precondition(ShortcutLogic.duplicates(in: [:]).isEmpty,
|
|
"empty map must have no duplicates")
|
|
|
|
// Single entry — no duplicate
|
|
let specA = ShortcutSpec(keyCode: 0, nsFlags: ShortcutSpec.ctrlOpt)
|
|
precondition(ShortcutLogic.duplicates(in: [.goToAlert: specA]).isEmpty,
|
|
"single entry must have no duplicate")
|
|
|
|
// Two different specs — no duplicate
|
|
let specB = ShortcutSpec(keyCode: 17, nsFlags: ShortcutSpec.ctrlOpt)
|
|
let twoUnique: [ShortcutAction: ShortcutSpec] = [.goToAlert: specA, .jumpToTerminal: specB]
|
|
precondition(ShortcutLogic.duplicates(in: twoUnique).isEmpty,
|
|
"two distinct specs must have no duplicate")
|
|
|
|
// Same spec for two actions → both flagged
|
|
let twoSame: [ShortcutAction: ShortcutSpec] = [.goToAlert: specA, .jumpToTerminal: specA]
|
|
let dups = ShortcutLogic.duplicates(in: twoSame)
|
|
precondition(dups.contains(.goToAlert), "first duped action must be flagged")
|
|
precondition(dups.contains(.jumpToTerminal), "second duped action must be flagged")
|
|
precondition(dups.count == 2, "exactly 2 actions must be flagged for one duplicate pair")
|
|
|
|
// Same key code but different flags → not a duplicate
|
|
let specC = ShortcutSpec(keyCode: 0, nsFlags: ShortcutSpec.cmdShift)
|
|
let diffFlags: [ShortcutAction: ShortcutSpec] = [.goToAlert: specA, .jumpToTerminal: specC]
|
|
precondition(ShortcutLogic.duplicates(in: diffFlags).isEmpty,
|
|
"same key code with different modifiers must not be a duplicate")
|
|
}
|
|
|
|
// MARK: - testLoadSaveRoundTrip
|
|
|
|
static func testLoadSaveRoundTrip() {
|
|
// Use a test-only suite to avoid polluting the app UserDefaults
|
|
let suiteName = "com.coucou.ShortcutTests.\(Int.random(in: 10000...99999))"
|
|
guard let ud = UserDefaults(suiteName: suiteName) else {
|
|
preconditionFailure("could not create test UserDefaults suite")
|
|
}
|
|
|
|
// Save a custom spec for .openChat
|
|
let custom = ShortcutSpec(keyCode: 5, nsFlags: ShortcutSpec.cmdShift)
|
|
ud.set(Int(custom.keyCode), forKey: ShortcutAction.openChat.udKeyCode)
|
|
ud.set(Int(custom.nsFlags), forKey: ShortcutAction.openChat.udFlags)
|
|
|
|
// Load it back using the raw UserDefaults API (same path ShortcutLogic.hotKey uses)
|
|
let loadedKC = ud.object(forKey: ShortcutAction.openChat.udKeyCode) as? Int
|
|
let loadedFl = ud.object(forKey: ShortcutAction.openChat.udFlags) as? Int
|
|
precondition(loadedKC == Int(custom.keyCode), "saved key code must round-trip")
|
|
precondition(loadedFl == Int(custom.nsFlags), "saved flags must round-trip")
|
|
|
|
// Cleanup
|
|
ud.removePersistentDomain(forName: suiteName)
|
|
}
|
|
|
|
// MARK: - testLegacyKeysForToggleIsland
|
|
|
|
static func testLegacyKeysForToggleIsland() {
|
|
precondition(ShortcutAction.toggleIsland.udKeyCode == "hotkeyCode",
|
|
"toggleIsland must use legacy 'hotkeyCode' key")
|
|
precondition(ShortcutAction.toggleIsland.udFlags == "hotkeyFlags",
|
|
"toggleIsland must use legacy 'hotkeyFlags' key")
|
|
precondition(ShortcutAction.toggleIsland.udEnabled == "hotkeyEnabled",
|
|
"toggleIsland must use legacy 'hotkeyEnabled' key")
|
|
|
|
// All other actions must NOT use the legacy keys
|
|
for action in ShortcutAction.allCases where action != .toggleIsland {
|
|
precondition(action.udKeyCode != "hotkeyCode",
|
|
"\(action.rawValue) must not reuse legacy hotkeyCode key")
|
|
}
|
|
}
|
|
|
|
// MARK: - testEnabledByDefault
|
|
|
|
static func testEnabledByDefault() {
|
|
// toggleIsland is OFF by default (existing behaviour)
|
|
precondition(!ShortcutAction.toggleIsland.enabledByDefault,
|
|
"toggleIsland must be disabled by default")
|
|
|
|
// All other global shortcuts are ON by default
|
|
for action in ShortcutAction.allCases where action != .toggleIsland {
|
|
precondition(action.enabledByDefault,
|
|
"\(action.rawValue) must be enabled by default")
|
|
}
|
|
}
|
|
|
|
// MARK: - testKeyCodeToString
|
|
|
|
static func testKeyCodeToString() {
|
|
precondition(ShortcutLogic.keyCodeToString(0) == "A", "keyCode 0 = A")
|
|
precondition(ShortcutLogic.keyCodeToString(45) == "N", "keyCode 45 = N")
|
|
precondition(ShortcutLogic.keyCodeToString(49) == "Space", "keyCode 49 = Space")
|
|
precondition(ShortcutLogic.keyCodeToString(30) == "]", "keyCode 30 = ]")
|
|
precondition(ShortcutLogic.keyCodeToString(33) == "[", "keyCode 33 = [")
|
|
precondition(ShortcutLogic.keyCodeToString(2) == "D", "keyCode 2 = D")
|
|
// Unknown key code returns a placeholder
|
|
let unknown = ShortcutLogic.keyCodeToString(200)
|
|
precondition(!unknown.isEmpty, "unknown key code must return non-empty string")
|
|
}
|
|
|
|
// MARK: - testCardNavigation
|
|
|
|
static func testCardNavigation() {
|
|
// nil + down → first item
|
|
precondition(ShortcutLogic.navigate(selection: nil, delta: +1, itemCount: 3) == 0,
|
|
"nil+down must select first item")
|
|
// nil + up → last item
|
|
precondition(ShortcutLogic.navigate(selection: nil, delta: -1, itemCount: 3) == 2,
|
|
"nil+up must select last item")
|
|
// clamp at top
|
|
precondition(ShortcutLogic.navigate(selection: 0, delta: -1, itemCount: 3) == 0,
|
|
"selection 0 + up must clamp to 0")
|
|
// clamp at bottom
|
|
precondition(ShortcutLogic.navigate(selection: 2, delta: +1, itemCount: 3) == 2,
|
|
"selection last + down must clamp to last")
|
|
// normal step down
|
|
precondition(ShortcutLogic.navigate(selection: 1, delta: +1, itemCount: 3) == 2,
|
|
"1+down in 3-item list must yield 2")
|
|
// normal step up
|
|
precondition(ShortcutLogic.navigate(selection: 1, delta: -1, itemCount: 3) == 0,
|
|
"1+up in 3-item list must yield 0")
|
|
// delta 0 — no move
|
|
precondition(ShortcutLogic.navigate(selection: 1, delta: 0, itemCount: 3) == 1,
|
|
"delta 0 must not move selection")
|
|
// empty card → nil (navigateCard guards on cardItemCount > 0, but test the pure function)
|
|
precondition(ShortcutLogic.navigate(selection: nil, delta: +1, itemCount: 0) == nil,
|
|
"empty card must return nil")
|
|
precondition(ShortcutLogic.navigate(selection: 0, delta: +1, itemCount: 0) == nil,
|
|
"any selection on empty card must return nil")
|
|
}
|
|
}
|