feat: add destination-specific global shortcuts
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
import AppKit
|
||||
@testable import ConjureKit
|
||||
import Testing
|
||||
|
||||
|
||||
@Test @MainActor
|
||||
func globalShortcutKeepsLegacyStorageAndScopesCustomStorage() throws {
|
||||
let suite = "GlobalShortcutsTests.\(UUID().uuidString)"
|
||||
let defaults = try #require(UserDefaults(suiteName: suite))
|
||||
defer { defaults.removePersistentDomain(forName: suite) }
|
||||
let commandShiftSpace = try #require(KeyboardShortcut(keyCode: 49, modifiers: [.command, .shift]))
|
||||
defaults.set(KeyboardShortcut.commandSpace.storedValue, forKey: "globalActivationShortcut")
|
||||
defaults.set(commandShiftSpace.storedValue, forKey: "customShortcut")
|
||||
|
||||
let legacy = GlobalShortcut(defaults: defaults) {}
|
||||
let custom = GlobalShortcut(defaults: defaults, storageKey: "customShortcut") {}
|
||||
#expect(legacy.shortcut == .commandSpace)
|
||||
#expect(custom.shortcut == commandShiftSpace)
|
||||
try custom.setShortcut(nil)
|
||||
#expect(defaults.object(forKey: "customShortcut") == nil)
|
||||
#expect(KeyboardShortcut(storedValue: defaults.object(forKey: "globalActivationShortcut")) == .commandSpace)
|
||||
#expect(GlobalShortcut(defaults: defaults, action: {}).shortcut == .commandSpace)
|
||||
}
|
||||
|
||||
@Test @MainActor
|
||||
func duplicateGlobalBindingIsRejectedWhileSuspendedWithoutChangingSavedBinding() throws {
|
||||
let suite = "GlobalShortcutsTests.\(UUID().uuidString)"
|
||||
let defaults = try #require(UserDefaults(suiteName: suite))
|
||||
defer { defaults.removePersistentDomain(forName: suite) }
|
||||
let candidate = try #require(KeyboardShortcut(keyCode: 49, modifiers: [.command, .shift]))
|
||||
let previous = try #require(KeyboardShortcut(keyCode: 49, modifiers: [.control, .option]))
|
||||
let emojiIndex = try #require(GlobalShortcuts.destinations.firstIndex(of: .command(.emoji)))
|
||||
defaults.set(candidate.storedValue, forKey: "globalActivationShortcut")
|
||||
defaults.set(previous.storedValue, forKey: "globalCommandShortcut.emoji")
|
||||
let shortcuts = GlobalShortcuts(defaults: defaults) { _ in }
|
||||
shortcuts.suspend()
|
||||
|
||||
do {
|
||||
try shortcuts.setShortcut(candidate, at: emojiIndex)
|
||||
Issue.record("A duplicate shortcut was accepted")
|
||||
} catch {
|
||||
#expect(error.localizedDescription.contains("Applications"))
|
||||
#expect(error.localizedDescription.contains("already assigned"))
|
||||
}
|
||||
let commandReturn = try #require(KeyboardShortcut(keyCode: 36, modifiers: .command))
|
||||
do {
|
||||
try shortcuts.setShortcut(commandReturn, at: emojiIndex)
|
||||
Issue.record("A Conjure action shortcut was accepted")
|
||||
} catch {
|
||||
#expect(error.localizedDescription.contains("already used by Conjure"))
|
||||
#expect(error.localizedDescription.contains("confirm uninstall"))
|
||||
}
|
||||
#expect(shortcuts.bindings[emojiIndex].shortcut == previous)
|
||||
#expect(KeyboardShortcut(storedValue: defaults.object(forKey: "globalCommandShortcut.emoji")) == previous)
|
||||
#expect(shortcuts.bindings.allSatisfy { !$0.isRegistered })
|
||||
try shortcuts.validate(candidate, at: 0)
|
||||
|
||||
try shortcuts.setShortcut(nil, at: emojiIndex)
|
||||
let restored = GlobalShortcuts(defaults: defaults) { _ in }
|
||||
#expect(restored.bindings[0].shortcut == candidate)
|
||||
#expect(restored.bindings[emojiIndex].shortcut == nil)
|
||||
}
|
||||
|
||||
@Test @MainActor
|
||||
func localShortcutConflictsMatchMenusActionsAndPanelNavigation() throws {
|
||||
let commandKey = try #require(KeyboardShortcut(keyCode: 0, modifiers: .command))
|
||||
for key in [",", "h", "q", "w", "x", "c", "v", "a", "m", "k", "Return", "1", "9"] {
|
||||
#expect(GlobalShortcuts.localAction(for: commandKey, keyName: key) != nil)
|
||||
}
|
||||
#expect(GlobalShortcuts.localAction(for: commandKey, keyName: "0") == nil)
|
||||
let hideOthers = try #require(KeyboardShortcut(keyCode: 0, modifiers: [.command, .option]))
|
||||
#expect(GlobalShortcuts.localAction(for: hideOthers, keyName: "H") == "Hide Others")
|
||||
let controlKey = try #require(KeyboardShortcut(keyCode: 0, modifiers: .control))
|
||||
for key in ["h", "j", "k", "l"] {
|
||||
#expect(GlobalShortcuts.localAction(for: controlKey, keyName: key) != nil)
|
||||
}
|
||||
let commandShiftSpace = try #require(KeyboardShortcut(keyCode: 49, modifiers: [.command, .shift]))
|
||||
#expect(GlobalShortcuts.localAction(for: commandShiftSpace, keyName: "Space") == nil)
|
||||
let commandShiftK = try #require(KeyboardShortcut(keyCode: 40, modifiers: [.command, .shift]))
|
||||
#expect(GlobalShortcuts.localAction(for: commandShiftK, keyName: "K") == nil)
|
||||
}
|
||||
|
||||
@Test @MainActor
|
||||
func startupReportsEveryInvalidSavedBindingWithoutDiscardingThem() throws {
|
||||
let suite = "GlobalShortcutsTests.\(UUID().uuidString)"
|
||||
let defaults = try #require(UserDefaults(suiteName: suite))
|
||||
defer { defaults.removePersistentDomain(forName: suite) }
|
||||
let commandReturn = try #require(KeyboardShortcut(keyCode: 36, modifiers: .command))
|
||||
defaults.set("invalid", forKey: "globalActivationShortcut")
|
||||
defaults.set(commandReturn.storedValue, forKey: "globalCommandShortcut.translate")
|
||||
defaults.set(["keyCode": -1, "modifiers": 0], forKey: "globalCommandShortcut.emoji")
|
||||
let shortcuts = GlobalShortcuts(defaults: defaults) { _ in }
|
||||
|
||||
let errors = shortcuts.start()
|
||||
#expect(errors.count == 3)
|
||||
#expect(errors[0].hasPrefix("Applications:"))
|
||||
#expect(errors[1].hasPrefix("Conjure Translate:"))
|
||||
#expect(errors[1].contains("already used by Conjure"))
|
||||
#expect(errors[2].hasPrefix("Conjure Emoji:"))
|
||||
#expect(defaults.string(forKey: "globalActivationShortcut") == "invalid")
|
||||
#expect(KeyboardShortcut(storedValue: defaults.object(forKey: "globalCommandShortcut.translate")) == commandReturn)
|
||||
#expect(defaults.object(forKey: "globalCommandShortcut.emoji") != nil)
|
||||
#expect(shortcuts.bindings.allSatisfy { !$0.isRegistered })
|
||||
}
|
||||
|
||||
@Test @MainActor
|
||||
func startupRejectsEveryDuplicateEvenBeforeNativeRegistration() throws {
|
||||
let suite = "GlobalShortcutsTests.\(UUID().uuidString)"
|
||||
let defaults = try #require(UserDefaults(suiteName: suite))
|
||||
defer { defaults.removePersistentDomain(forName: suite) }
|
||||
let candidate = try #require(KeyboardShortcut(keyCode: 49, modifiers: [.command, .shift]))
|
||||
defaults.set(candidate.storedValue, forKey: "globalActivationShortcut")
|
||||
defaults.set(candidate.storedValue, forKey: "globalCommandShortcut.emoji")
|
||||
let shortcuts = GlobalShortcuts(defaults: defaults) { _ in }
|
||||
|
||||
let errors = shortcuts.start()
|
||||
#expect(errors.count == 2)
|
||||
#expect(errors[0].contains("Conjure Emoji"))
|
||||
#expect(errors[1].contains("Applications"))
|
||||
#expect(shortcuts.bindings.allSatisfy { !$0.isRegistered })
|
||||
}
|
||||
Reference in New Issue
Block a user