Import Coucou 0.1.9 (Louis-CFM/coucou@83708fe), Mac app only
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user