From d3d0d5a426bd6de2142ad51e152662c7acfaf213 Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Fri, 14 Aug 2026 00:41:01 +0200 Subject: [PATCH 1/6] refactor(transformer): replace keycode special cases with general rules --- .../Keyboard/KeyboardGlyphCatalog.swift | 1 - .../Keyty/Services/Display/UnicodeToken.swift | 1 - .../EventPipeline/EventTransformer.swift | 101 ++---- .../EventTransformerKeystrokeTests.swift | 317 +++++++----------- .../Support/TestKeyboardCharacters.swift | 2 - .../Support/TestKeyboardLayouts.swift | 14 +- .../KeytyTests/Support/TestKeystrokes.swift | 7 +- 7 files changed, 172 insertions(+), 271 deletions(-) diff --git a/Apps/Keyty/Sources/Keyty/Domain/Keyboard/KeyboardGlyphCatalog.swift b/Apps/Keyty/Sources/Keyty/Domain/Keyboard/KeyboardGlyphCatalog.swift index 0b2629c0..1deb1cff 100644 --- a/Apps/Keyty/Sources/Keyty/Domain/Keyboard/KeyboardGlyphCatalog.swift +++ b/Apps/Keyty/Sources/Keyty/Domain/Keyboard/KeyboardGlyphCatalog.swift @@ -22,7 +22,6 @@ enum KeyboardGlyphCatalog { static let control = KeyboardModifierKey.Kind.control.glyph static let tab = UnicodeToken.tab.string - static let backTab = UnicodeToken.backTab.string /// Glyphs that can prefix a chord in display strings. static let modifierSymbols: [String] = KeyboardModifierKey.Kind.allCases.map(\.glyph) diff --git a/Apps/Keyty/Sources/Keyty/Services/Display/UnicodeToken.swift b/Apps/Keyty/Sources/Keyty/Services/Display/UnicodeToken.swift index 32532de8..7af0d495 100644 --- a/Apps/Keyty/Sources/Keyty/Services/Display/UnicodeToken.swift +++ b/Apps/Keyty/Sources/Keyty/Services/Display/UnicodeToken.swift @@ -19,7 +19,6 @@ enum UnicodeToken { static let control: Unicode.Scalar = "\u{2303}" static let tab: Unicode.Scalar = "\u{21E5}" - static let backTab: Unicode.Scalar = "\u{21E4}" static let escape: Unicode.Scalar = "\u{238B}" static let delete: Unicode.Scalar = "\u{232B}" static let keypadClear: Unicode.Scalar = "\u{2327}" diff --git a/Apps/Keyty/Sources/Keyty/Services/EventPipeline/EventTransformer.swift b/Apps/Keyty/Sources/Keyty/Services/EventPipeline/EventTransformer.swift index 826714e7..68a9619f 100644 --- a/Apps/Keyty/Sources/Keyty/Services/EventPipeline/EventTransformer.swift +++ b/Apps/Keyty/Sources/Keyty/Services/EventPipeline/EventTransformer.swift @@ -26,97 +26,32 @@ public final class EventTransformer { public func transform(_ event: InputEvent) -> String { switch event { case .keystroke(let keystroke): - return transform(keystroke) + return self.transform(keystroke) case .mouse(let mouseEvent): - return transform(mouseEvent) + return self.transform(mouseEvent) case .mediaKey(let mediaKey): - return transform(mediaKey) + return self.transform(mediaKey) } } - - private func shouldReturnOriginalCharacters(keyCode: UInt16, characters: String?) -> Bool { - keyCode == KeyboardKeyCode.minus.rawValue && characters == "ß" - } - - private func modifierPrefix( - for modifiers: NSEvent.ModifierFlags, - includesDeferredShift: Bool = true - ) -> String { - let hasOptionModifier = modifiers.contains(.option) - let hasShiftModifier = modifiers.contains(.shift) - let usesShortcutStyle = !modifiers.intersection([.control, .command]).isEmpty - var needsShiftGlyph = false - var response = "" - - if modifiers.contains(.control) { - response += KeyboardGlyphCatalog.control - } - - if hasOptionModifier { - response += KeyboardGlyphCatalog.option - } - - if hasShiftModifier { - if usesShortcutStyle || hasOptionModifier { - response += KeyboardGlyphCatalog.shift - } else { - needsShiftGlyph = true - } - } - - if modifiers.contains(.command) { - if needsShiftGlyph { - response += KeyboardGlyphCatalog.shift - needsShiftGlyph = false - } - response += KeyboardGlyphCatalog.command - } - - if needsShiftGlyph && includesDeferredShift { - response += KeyboardGlyphCatalog.shift - } - - return response - } } +// MARK: - Event Transforms private extension EventTransformer { func transform(_ keystroke: StandardKeyEvent) -> String { if let glyph = InputEventGlyphMapper.glyph(for: keystroke.inputEvent) { return glyph } - let modifiers = keystroke.modifierFlags - let hasOptionModifier = modifiers.contains(.option) - let hasShiftModifier = modifiers.contains(.shift) - let isCommand = !modifiers.intersection([.control, .command]).isEmpty - var response = self.modifierPrefix(for: modifiers, includesDeferredShift: false) - - if hasShiftModifier && !keystroke.isCommand && !hasOptionModifier && keystroke.keyCode == KeyboardKeyCode.tab.rawValue { - response += KeyboardGlyphCatalog.backTab - return response - } - - if hasShiftModifier && !isCommand && !hasOptionModifier { - response += KeyboardGlyphCatalog.shift - } + var response = self.modifierPrefix(for: keystroke.modifierFlags) if let specialKeyString = KeyboardSpecialKeyResolver.displayText(for: keystroke) { - response += specialKeyString - return response + return response + specialKeyString } - if isCommand, - shouldReturnOriginalCharacters(keyCode: keystroke.keyCode, characters: keystroke.characters) { - response += keystroke.characters ?? "" - } else { - response += uchrData.translatedKeyCode(keystroke.keyCode) - } + response += self.uchrData.translatedKeyCode(keystroke.keyCode) - if isCommand || hasShiftModifier || hasOptionModifier { - if keystroke.keyCode != KeyboardKeyCode.minus.rawValue { - response = response.uppercased() - } + if keystroke.isModified { + response = self.legendCased(response) } return response @@ -132,3 +67,21 @@ private extension EventTransformer { InputEventGlyphMapper.mediaKeyGlyph(for: mediaKey) } } + +// MARK: - Display Formatting +private extension EventTransformer { + // Uppercases a legend only when the result keeps its length. + private func legendCased(_ text: String) -> String { + let uppercased = text.uppercased() + return uppercased.count == text.count ? uppercased : text + } + + + // The glyphs for the held modifiers, in Apple's canonical display order. + private func modifierPrefix(for modifiers: NSEvent.ModifierFlags) -> String { + KeyboardModifierKey.Kind.canonicalDisplayOrder + .filter { modifiers.contains($0.flag) } + .map(\.glyph) + .joined() + } +} diff --git a/Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventTransformerKeystrokeTests.swift b/Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventTransformerKeystrokeTests.swift index ee0a4532..3f96f186 100644 --- a/Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventTransformerKeystrokeTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventTransformerKeystrokeTests.swift @@ -23,177 +23,109 @@ final class EventTransformerKeystrokeTests: XCTestCase { } } -// MARK: - Numbers +// MARK: - Modifier Glyphs extension EventTransformerKeystrokeTests { - func test_convertsCtrlNumberToNumber() { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.digit7.rawValue, modifiers: TestModifierFlags.control, characters: "7", charactersIgnoringModifiers: "7") - XCTAssertEqual(self.transform(keystroke), KeyboardGlyphCatalog.control + "7") - } - - func test_convertsShiftNumberToShiftNumber() { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.digit7.rawValue, modifiers: TestModifierFlags.shift, characters: "&", charactersIgnoringModifiers: "&") - XCTAssertEqual(self.transform(keystroke), KeyboardGlyphCatalog.shift + "7") - } - - func test_convertsCtrlShiftNumberToNumber() { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.digit7.rawValue, modifiers: TestModifierFlags.controlShift, characters: "7", charactersIgnoringModifiers: "&") - XCTAssertEqual(self.transform(keystroke), KeyboardGlyphCatalog.control + KeyboardGlyphCatalog.shift + "7") - } - - func test_convertsCmdNumberToNumber() { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.digit7.rawValue, modifiers: TestModifierFlags.command, characters: "7", charactersIgnoringModifiers: "7") - XCTAssertEqual(self.transform(keystroke), KeyboardGlyphCatalog.command + "7") - } - - func test_convertsCmdShiftNumberToNumber() { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.digit7.rawValue, modifiers: TestModifierFlags.commandShift, characters: "7", charactersIgnoringModifiers: "&") - XCTAssertEqual(self.transform(keystroke), KeyboardGlyphCatalog.shift + KeyboardGlyphCatalog.command + "7") - } - - func test_convertsCmdOptNumberToNumber() { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.digit7.rawValue, modifiers: TestModifierFlags.commandOption, characters: "¶", charactersIgnoringModifiers: "7") - XCTAssertEqual(self.transform(keystroke), KeyboardGlyphCatalog.option + KeyboardGlyphCatalog.command + "7") + /// The order the glyphs are expected in, written out independently of the + /// `canonicalDisplayOrder` the transformer reads, so a reordering there fails here. + private static let expectedGlyphOrder: [(flag: NSEvent.ModifierFlags, glyph: String)] = [ + (.control, KeyboardGlyphCatalog.control), + (.option, KeyboardGlyphCatalog.option), + (.shift, KeyboardGlyphCatalog.shift), + (.command, KeyboardGlyphCatalog.command) + ] + + /// Every combination of the four modifiers, as flags plus a readable name. + private static var allModifierCombinations: [(modifiers: NSEvent.ModifierFlags, name: String)] { + (0..<16).map { combination in + var modifiers: NSEvent.ModifierFlags = [] + var names: [String] = [] + for (index, entry) in Self.expectedGlyphOrder.enumerated() where combination & (1 << index) != 0 { + modifiers.insert(entry.flag) + names.append(entry.glyph) + } + return (modifiers, names.isEmpty ? "no modifiers" : names.joined()) + } } - func test_convertsShiftOptionNumberToNumber() { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.digit7.rawValue, modifiers: TestModifierFlags.optionShift, characters: "»", charactersIgnoringModifiers: "7") - XCTAssertEqual(self.transform(keystroke), KeyboardGlyphCatalog.option + KeyboardGlyphCatalog.shift + "7") + private func expectedPrefix(for modifiers: NSEvent.ModifierFlags) -> String { + Self.expectedGlyphOrder + .filter { modifiers.contains($0.flag) } + .map(\.glyph) + .joined() } - func test_convertsCmdOptShiftNumberToShiftedNumber() { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.digit7.rawValue, modifiers: TestModifierFlags.commandOptionShift, characters: "‡", charactersIgnoringModifiers: "&") - XCTAssertEqual(self.transform(keystroke), KeyboardGlyphCatalog.option + KeyboardGlyphCatalog.shift + KeyboardGlyphCatalog.command + "7") - } -} + func test_modifierGlyphsPrecedeTheLegendInCanonicalOrder() { + for (modifiers, name) in Self.allModifierCombinations { + let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.digit7.rawValue, modifiers: modifiers) -// MARK: - Letters - -extension EventTransformerKeystrokeTests { - func test_convertsCtrlLetterToUppercaseLetter() { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.a.rawValue, modifiers: TestModifierFlags.control, characters: "^A", charactersIgnoringModifiers: "a") - XCTAssertEqual(self.transform(keystroke), KeyboardGlyphCatalog.control + "A") + XCTAssertEqual( + self.transform(keystroke), + self.expectedPrefix(for: modifiers) + "7", + "for \(name)" + ) + } } - func test_convertsCtrlShiftLetterToLetter() { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.a.rawValue, modifiers: TestModifierFlags.controlShift, characters: "^A", charactersIgnoringModifiers: "a") - XCTAssertEqual(self.transform(keystroke), KeyboardGlyphCatalog.control + KeyboardGlyphCatalog.shift + "A") - } + /// A digit has no distinct uppercase form, so only letters show the casing rule. + func test_letterLegendIsUppercasedOnlyWhenModified() { + for (modifiers, name) in Self.allModifierCombinations { + let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.a.rawValue, modifiers: modifiers) + let expectedLegend = modifiers.isEmpty ? "a" : "A" - func test_convertsCtrlShiftCmdLetterToLetter() { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.a.rawValue, modifiers: TestModifierFlags.controlCommandShift, characters: "^A", charactersIgnoringModifiers: "A") - XCTAssertEqual(self.transform(keystroke), KeyboardGlyphCatalog.control + KeyboardGlyphCatalog.shift + KeyboardGlyphCatalog.command + "A") + XCTAssertEqual( + self.transform(keystroke), + self.expectedPrefix(for: modifiers) + expectedLegend, + "for \(name)" + ) + } } - func test_convertsCtrlOptLetterToUppercaseLetter() { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.a.rawValue, modifiers: TestModifierFlags.controlOption, characters: "^A", charactersIgnoringModifiers: "a") - XCTAssertEqual(self.transform(keystroke), KeyboardGlyphCatalog.control + KeyboardGlyphCatalog.option + "A") - } + /// Option produces a dead key or an alternate character, but the legend stays + /// the one printed on the key. + func test_optionLegendIgnoresTheAlternateCharacter() { + let cases: [(KeyboardKeyCode, String)] = [(.u, "U"), (.e, "E"), (.grave, "`")] - func test_convertsCtrlOptShiftLetterToLetter() { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.a.rawValue, modifiers: TestModifierFlags.controlOptionShift, characters: "^A", charactersIgnoringModifiers: "A") - XCTAssertEqual(self.transform(keystroke), KeyboardGlyphCatalog.control + KeyboardGlyphCatalog.option + KeyboardGlyphCatalog.shift + "A") - } + for (keyCode, expectedLegend) in cases { + let keystroke = TestKeystrokes.make(keyCode: keyCode.rawValue, modifiers: TestModifierFlags.option) - func test_displaysOptLetterByDefault() { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.u.rawValue, modifiers: TestModifierFlags.option, characters: "", charactersIgnoringModifiers: "u") - XCTAssertEqual(self.transform(keystroke), KeyboardGlyphCatalog.option + "U") + XCTAssertEqual( + self.transform(keystroke), + KeyboardGlyphCatalog.option + expectedLegend, + "for \(keyCode)" + ) + } } } -// MARK: - Function Row -extension EventTransformerKeystrokeTests { - func test_convertsFnF1ToBrightnessDecrease() { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.brightnessDown.rawValue, modifiers: TestModifierFlags.function, characters: "", charactersIgnoringModifiers: "") - XCTAssertEqual(self.transform(keystroke), "dimmer") - } +// MARK: - Legend Casing - func test_convertsFnF2ToBrightnessIncrease() { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.brightnessUp.rawValue, modifiers: TestModifierFlags.function, characters: "", charactersIgnoringModifiers: "") - XCTAssertEqual(self.transform(keystroke), "brighter") - } - - func test_convertsFnF3ToMissionControl() { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.missionControl.rawValue, modifiers: TestModifierFlags.function, characters: "", charactersIgnoringModifiers: "") - XCTAssertEqual(self.transform(keystroke), "mission") - } - - func test_convertsFnF4ToLauncher() { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.launchpad.rawValue, modifiers: TestModifierFlags.function, characters: "", charactersIgnoringModifiers: "") - XCTAssertEqual(self.transform(keystroke), "launchpad") - } -} - -// MARK: - JIS layout extension EventTransformerKeystrokeTests { - func test_convertsEisuKey() { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.eisu.rawValue, modifiers: [], characters: "", charactersIgnoringModifiers: "") - XCTAssertEqual(self.transform(keystroke), KeyboardSpecialKey.eisu.displayText) - } - - func test_convertsKanaKey() { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.kana.rawValue, modifiers: [], characters: "", charactersIgnoringModifiers: "") - XCTAssertEqual(self.transform(keystroke), KeyboardSpecialKey.kana.displayText) - } -} - -// MARK: - Option-modified characters + /// German ß uppercases to "SS", which would misreport the key's legend. + func test_legendThatExpandsWhenUppercasedIsLeftAlone() throws { + let german = EventTransformer(keyboardLayout: try TestKeyboardLayouts.requireGerman()) + let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.minus.rawValue, modifiers: TestModifierFlags.command) -extension EventTransformerKeystrokeTests { - func test_optionShiftNumberDisplaysExplicitModifiers() { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.digit7.rawValue, modifiers: TestModifierFlags.optionShift, characters: "»", charactersIgnoringModifiers: "7") - XCTAssertEqual(self.transform(keystroke), KeyboardGlyphCatalog.option + KeyboardGlyphCatalog.shift + "7") + XCTAssertEqual(german.transform(.keystroke(keystroke)), KeyboardGlyphCatalog.command + "ß") } } -// MARK: - Special Cases +// MARK: - Keys Named by the Layout extension EventTransformerKeystrokeTests { - func test_tabKey() { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.tab.rawValue, modifiers: TestModifierFlags.none, characters: "\t", charactersIgnoringModifiers: "\t") - XCTAssertEqual(self.transform(keystroke), KeyboardGlyphCatalog.tab) - } - - func test_returnAndKeypadEnterUseDifferentSymbols() { - let returnKey = TestKeystrokes.make( - keyCode: KeyboardKeyCode.returnKey.rawValue, - modifiers: TestModifierFlags.none, - characters: "\r", - charactersIgnoringModifiers: "\r" - ) - XCTAssertEqual(self.transform(returnKey), UnicodeToken.returnKey.string) - - let keypadEnter = TestKeystrokes.make( - keyCode: KeyboardKeyCode.keypadEnter.rawValue, - modifiers: TestModifierFlags.none, - characters: "\r", - charactersIgnoringModifiers: "\r" - ) - XCTAssertEqual(self.transform(keypadEnter), UnicodeToken.keypadEnter.string) - } - - func test_deleteAndForwardDeleteUseDifferentSymbols() { - let deleteKey = TestKeystrokes.make( - keyCode: KeyboardKeyCode.delete.rawValue, - modifiers: TestModifierFlags.none, - characters: UnicodeToken.delete.string, - charactersIgnoringModifiers: UnicodeToken.delete.string - ) - XCTAssertEqual(self.transform(deleteKey), UnicodeToken.delete.string) - - let forwardDelete = TestKeystrokes.make( - keyCode: KeyboardKeyCode.forwardDelete.rawValue, - modifiers: TestModifierFlags.none, - characters: UnicodeToken.forwardDelete.string, - charactersIgnoringModifiers: UnicodeToken.forwardDelete.string - ) - XCTAssertEqual(self.transform(forwardDelete), UnicodeToken.forwardDelete.string) - } + func test_editingKeysUseDistinctSymbols() { + let cases: [(KeyboardKeyCode, String)] = [ + (.tab, KeyboardGlyphCatalog.tab), + (.returnKey, UnicodeToken.returnKey.string), + (.keypadEnter, UnicodeToken.keypadEnter.string), + (.delete, UnicodeToken.delete.string), + (.forwardDelete, UnicodeToken.forwardDelete.string) + ] - func test_shiftTab() { - let ch = TestKeyboardCharacters.backTab - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.tab.rawValue, modifiers: TestModifierFlags.shift, characters: ch, charactersIgnoringModifiers: ch) - XCTAssertEqual(self.transform(keystroke), KeyboardGlyphCatalog.backTab) + for (keyCode, expected) in cases { + let keystroke = TestKeystrokes.make(keyCode: keyCode.rawValue) + XCTAssertEqual(self.transform(keystroke), expected, "for \(keyCode)") + } } func test_arrowKeysUseFilledTriangleSymbols() { @@ -205,40 +137,19 @@ extension EventTransformerKeystrokeTests { ] for (keyCode, expected) in cases { - let keystroke = TestKeystrokes.make( - keyCode: keyCode.rawValue, - modifiers: TestModifierFlags.none, - characters: expected, - charactersIgnoringModifiers: expected - ) - XCTAssertEqual(self.transform(keystroke), expected) + let keystroke = TestKeystrokes.make(keyCode: keyCode.rawValue) + XCTAssertEqual(self.transform(keystroke), expected, "for \(keyCode)") } } - func test_insertFunctionKeyDisplaysInsertForHelpKeyCode() { - let ch = TestKeyboardCharacters.functionKeyCharacter(NSInsertFunctionKey) - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.help.rawValue, modifiers: [], characters: ch, charactersIgnoringModifiers: ch) - XCTAssertEqual(self.transform(keystroke), KeyboardSpecialKey.insert.displayText) - } - - func test_helpFunctionKeyDisplaysHelpForHelpKeyCode() { - let ch = TestKeyboardCharacters.functionKeyCharacter(NSHelpFunctionKey) - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.help.rawValue, modifiers: [], characters: ch, charactersIgnoringModifiers: ch) - XCTAssertEqual(self.transform(keystroke), "help") - } - - func test_helpKeyCodeWithoutSemanticCharactersDefaultsToInsert() { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.help.rawValue, modifiers: [], characters: "", charactersIgnoringModifiers: "") - XCTAssertEqual(self.transform(keystroke), KeyboardSpecialKey.insert.displayText) - } -} - -// MARK: - US English - Special Cases with Modifiers - -extension EventTransformerKeystrokeTests { - func test_optionShiftUp() { - let ch = TestKeyboardCharacters.functionKeyCharacter(NSUpArrowFunctionKey) - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.upArrow.rawValue, modifiers: TestModifierFlags.functionOptionShiftNumericPad, characters: ch, charactersIgnoringModifiers: ch) + func test_modifiersPrefixASpecialKeySymbol() { + let character = TestKeyboardCharacters.functionKeyCharacter(NSUpArrowFunctionKey) + let keystroke = TestKeystrokes.make( + keyCode: KeyboardKeyCode.upArrow.rawValue, + modifiers: TestModifierFlags.functionOptionShiftNumericPad, + characters: character, + charactersIgnoringModifiers: character + ) XCTAssertEqual( self.transform(keystroke), @@ -246,27 +157,53 @@ extension EventTransformerKeystrokeTests { ) } - func test_optionUSpecialCase() { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.u.rawValue, modifiers: TestModifierFlags.option, characters: "", charactersIgnoringModifiers: "u") - XCTAssertEqual(self.transform(keystroke), KeyboardGlyphCatalog.option + "U") - } + func test_systemKeysUseTheirOwnNames() { + let cases: [(KeyboardKeyCode, String)] = [ + (.brightnessDown, "dimmer"), + (.brightnessUp, "brighter"), + (.missionControl, "mission"), + (.launchpad, "launchpad") + ] - func test_optionESpecialCase() { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.e.rawValue, modifiers: TestModifierFlags.option, characters: "", charactersIgnoringModifiers: "e") - XCTAssertEqual(self.transform(keystroke), KeyboardGlyphCatalog.option + "E") + for (keyCode, expected) in cases { + let keystroke = TestKeystrokes.make(keyCode: keyCode.rawValue, modifiers: TestModifierFlags.function) + XCTAssertEqual(self.transform(keystroke), expected, "for \(keyCode)") + } } - func test_optionBacktickSpecialCase() { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.grave.rawValue, modifiers: TestModifierFlags.option, characters: "", charactersIgnoringModifiers: "`") - XCTAssertEqual(self.transform(keystroke), KeyboardGlyphCatalog.option + "`") + func test_japaneseInputKeysUseTheirOwnLabels() { + let cases: [(KeyboardKeyCode, KeyboardSpecialKey)] = [(.eisu, .eisu), (.kana, .kana)] + + for (keyCode, specialKey) in cases { + let keystroke = TestKeystrokes.make(keyCode: keyCode.rawValue) + XCTAssertEqual(self.transform(keystroke), specialKey.displayText, "for \(keyCode)") + } } } -// MARK: - German - Special Case +// MARK: - Keys Named by the Event extension EventTransformerKeystrokeTests { - func test_commandßDisplaysCommandß() { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.minus.rawValue, modifiers: TestModifierFlags.command, characters: "ß", charactersIgnoringModifiers: "ß") - XCTAssertEqual(self.transform(keystroke), KeyboardGlyphCatalog.command + "ß") + /// macOS reuses the Help key code for Insert on many external keyboards, so + /// these three keys are told apart by the event's characters, not the key code. + func test_helpKeyCodeResolvesFromTheEventCharacters() { + let insertCharacter = TestKeyboardCharacters.functionKeyCharacter(NSInsertFunctionKey) + let insert = TestKeystrokes.make( + keyCode: KeyboardKeyCode.help.rawValue, + characters: insertCharacter, + charactersIgnoringModifiers: insertCharacter + ) + XCTAssertEqual(self.transform(insert), KeyboardSpecialKey.insert.displayText) + + let helpCharacter = TestKeyboardCharacters.functionKeyCharacter(NSHelpFunctionKey) + let help = TestKeystrokes.make( + keyCode: KeyboardKeyCode.help.rawValue, + characters: helpCharacter, + charactersIgnoringModifiers: helpCharacter + ) + XCTAssertEqual(self.transform(help), "help") + + let withoutCharacters = TestKeystrokes.make(keyCode: KeyboardKeyCode.help.rawValue) + XCTAssertEqual(self.transform(withoutCharacters), KeyboardSpecialKey.insert.displayText) } } diff --git a/Apps/Keyty/Tests/KeytyTests/Support/TestKeyboardCharacters.swift b/Apps/Keyty/Tests/KeytyTests/Support/TestKeyboardCharacters.swift index 333e832d..eb8340db 100644 --- a/Apps/Keyty/Tests/KeytyTests/Support/TestKeyboardCharacters.swift +++ b/Apps/Keyty/Tests/KeytyTests/Support/TestKeyboardCharacters.swift @@ -12,6 +12,4 @@ enum TestKeyboardCharacters { static func functionKeyCharacter(_ key: Int) -> String { String(UnicodeScalar(key)!) } - - static let backTab = String(UnicodeScalar(NSBackTabCharacter)!) } diff --git a/Apps/Keyty/Tests/KeytyTests/Support/TestKeyboardLayouts.swift b/Apps/Keyty/Tests/KeytyTests/Support/TestKeyboardLayouts.swift index 75493183..54bf3c6c 100644 --- a/Apps/Keyty/Tests/KeytyTests/Support/TestKeyboardLayouts.swift +++ b/Apps/Keyty/Tests/KeytyTests/Support/TestKeyboardLayouts.swift @@ -12,8 +12,20 @@ import XCTest enum TestKeyboardLayouts { static func requireUSEnglish(file: StaticString = #filePath, line: UInt = #line) throws -> TISInputSource { + try self.require(id: "com.apple.keylayout.US", file: file, line: line) + } + + /// The German layout, whose ß key exercises legends that expand when uppercased. + static func requireGerman(file: StaticString = #filePath, + line: UInt = #line) throws -> TISInputSource { + try self.require(id: "com.apple.keylayout.German", file: file, line: line) + } + + private static func require(id: String, + file: StaticString = #filePath, + line: UInt = #line) throws -> TISInputSource { let properties: [String: Any] = [ - kTISPropertyInputSourceID as String: "com.apple.keylayout.US", + kTISPropertyInputSourceID as String: id, kTISPropertyInputSourceType as String: kTISTypeKeyboardLayout as String ] let inputSources = try XCTUnwrap( diff --git a/Apps/Keyty/Tests/KeytyTests/Support/TestKeystrokes.swift b/Apps/Keyty/Tests/KeytyTests/Support/TestKeystrokes.swift index f52a3f3a..0a44d99a 100644 --- a/Apps/Keyty/Tests/KeytyTests/Support/TestKeystrokes.swift +++ b/Apps/Keyty/Tests/KeytyTests/Support/TestKeystrokes.swift @@ -10,11 +10,14 @@ import AppKit @testable import Keyty enum TestKeystrokes { + /// The transformer resolves a legend from the key code and the layout, so + /// `characters` only matters for keys whose meaning comes from the event + /// itself — Help versus Insert, and the arrow function keys. static func make( keyCode: UInt16, modifiers: NSEvent.ModifierFlags = [], - characters: String, - charactersIgnoringModifiers: String + characters: String = "", + charactersIgnoringModifiers: String = "" ) -> StandardKeyEvent { let event = NSEvent.keyEvent( with: .keyDown, From c266d8949424686b545af07fc16fc25c84c1d2ff Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Fri, 14 Aug 2026 00:56:05 +0200 Subject: [PATCH 2/6] test: replace Test* helper namespaces with fixture extensions --- .../Domain/Events/MouseEventTests.swift | 23 +++---- ...rdVisualizerSpecialKeyFilteringTests.swift | 10 +-- .../Keycaps/KeyboardVisualizerTests.swift | 4 +- .../Keycaps/KeycapItemFactoryTests.swift | 4 +- .../PointerIconVisualizerTests.swift | 16 +---- .../EventPipeline/EventProcessorTests.swift | 4 +- .../EventTransformerKeystrokeTests.swift | 42 ++++++------- .../EventTransformerMouseTests.swift | 16 ++--- .../MouseEvent+Stub.swift} | 18 +++++- .../NSEventModifierFlags+DeviceMasks.swift} | 0 .../NSEventModifierFlags+Recorded.swift | 36 +++++++++++ .../StandardKeyEvent+Stub.swift} | 16 ++--- .../Support/Fixtures/String+FunctionKey.swift | 16 +++++ .../Fixtures/TISInputSource+Layouts.swift | 43 +++++++++++++ .../Support/TestKeyboardCharacters.swift | 15 ----- .../Support/TestKeyboardLayouts.swift | 45 ------------- .../Support/TestModifierFlags.swift | 63 ------------------- 17 files changed, 168 insertions(+), 203 deletions(-) rename Apps/Keyty/Tests/KeytyTests/Support/{TestMouseEvents.swift => Fixtures/MouseEvent+Stub.swift} (81%) rename Apps/Keyty/Tests/KeytyTests/Support/{Extensions/AppKit/NSEventModifierFlagsDeviceMasks.swift => Fixtures/NSEventModifierFlags+DeviceMasks.swift} (100%) create mode 100644 Apps/Keyty/Tests/KeytyTests/Support/Fixtures/NSEventModifierFlags+Recorded.swift rename Apps/Keyty/Tests/KeytyTests/Support/{TestKeystrokes.swift => Fixtures/StandardKeyEvent+Stub.swift} (66%) create mode 100644 Apps/Keyty/Tests/KeytyTests/Support/Fixtures/String+FunctionKey.swift create mode 100644 Apps/Keyty/Tests/KeytyTests/Support/Fixtures/TISInputSource+Layouts.swift delete mode 100644 Apps/Keyty/Tests/KeytyTests/Support/TestKeyboardCharacters.swift delete mode 100644 Apps/Keyty/Tests/KeytyTests/Support/TestKeyboardLayouts.swift delete mode 100644 Apps/Keyty/Tests/KeytyTests/Support/TestModifierFlags.swift diff --git a/Apps/Keyty/Tests/KeytyTests/Domain/Events/MouseEventTests.swift b/Apps/Keyty/Tests/KeytyTests/Domain/Events/MouseEventTests.swift index c7c65f74..3c1aa2c9 100644 --- a/Apps/Keyty/Tests/KeytyTests/Domain/Events/MouseEventTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Domain/Events/MouseEventTests.swift @@ -11,18 +11,18 @@ import XCTest final class MouseEventTests: XCTestCase { func testKindClassifiesButtonsAndScrollDirections() { - XCTAssertEqual(TestMouseEvents.make(type: .leftMouseDown, buttonNumber: 0).kind, .leftButton) - XCTAssertEqual(TestMouseEvents.make(type: .rightMouseDown, buttonNumber: 1).kind, .rightButton) - XCTAssertEqual(TestMouseEvents.make(type: .otherMouseDown, buttonNumber: 2).kind, .middleButton) - XCTAssertEqual(TestMouseEvents.make(type: .otherMouseDown, buttonNumber: 3).kind, .otherButton(4)) - XCTAssertEqual(makeScrollEvent(deltaX: 0, deltaY: 1).kind, .wheelUp) - XCTAssertEqual(makeScrollEvent(deltaX: 0, deltaY: -1).kind, .wheelDown) - XCTAssertEqual(makeScrollEvent(deltaX: -1, deltaY: 0).kind, .wheelLeft) - XCTAssertEqual(makeScrollEvent(deltaX: 1, deltaY: 0).kind, .wheelRight) + XCTAssertEqual(MouseEvent.stub(type: .leftMouseDown, buttonNumber: 0).kind, .leftButton) + XCTAssertEqual(MouseEvent.stub(type: .rightMouseDown, buttonNumber: 1).kind, .rightButton) + XCTAssertEqual(MouseEvent.stub(type: .otherMouseDown, buttonNumber: 2).kind, .middleButton) + XCTAssertEqual(MouseEvent.stub(type: .otherMouseDown, buttonNumber: 3).kind, .otherButton(4)) + XCTAssertEqual(MouseEvent.scrollStub(deltaX: 0, deltaY: 1).kind, .wheelUp) + XCTAssertEqual(MouseEvent.scrollStub(deltaX: 0, deltaY: -1).kind, .wheelDown) + XCTAssertEqual(MouseEvent.scrollStub(deltaX: -1, deltaY: 0).kind, .wheelLeft) + XCTAssertEqual(MouseEvent.scrollStub(deltaX: 1, deltaY: 0).kind, .wheelRight) } func testKindTreatsZeroDeltaScrollEventAsGeneric() { - XCTAssertEqual(makeScrollEvent(deltaX: 0, deltaY: 0).kind, .generic) + XCTAssertEqual(MouseEvent.scrollStub(deltaX: 0, deltaY: 0).kind, .generic) } func testKindIsScrollRecognizesOnlyWheelCases() { @@ -67,9 +67,4 @@ final class MouseEventTests: XCTestCase { XCTAssertEqual(location.x, 1600) XCTAssertEqual(location.y, 980) } - - private func makeScrollEvent(deltaX: CGFloat, deltaY: CGFloat) -> MouseEvent { - let cgEvent = CGEvent(scrollWheelEvent2Source: nil, units: .pixel, wheelCount: 2, wheel1: Int32(deltaY), wheel2: Int32(deltaX), wheel3: 0)! - return MouseEvent(nsEvent: NSEvent(cgEvent: cgEvent)!) - } } diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSpecialKeyFilteringTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSpecialKeyFilteringTests.swift index e476b31c..da3d5c75 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSpecialKeyFilteringTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerSpecialKeyFilteringTests.swift @@ -33,9 +33,9 @@ final class KeyboardVisualizerSpecialKeyFilteringTests: XCTestCase { } func testResolverClassifiesInsertFunctionKeyAsSpecial() { - let ch = TestKeyboardCharacters.functionKeyCharacter(NSInsertFunctionKey) - let event = TestKeystrokes.make( - keyCode: KeyboardKeyCode.help.rawValue, + let ch = String.functionKey(NSInsertFunctionKey) + let event = StandardKeyEvent.stub( + keyCode: .help, characters: ch, charactersIgnoringModifiers: ch ) @@ -44,8 +44,8 @@ final class KeyboardVisualizerSpecialKeyFilteringTests: XCTestCase { } func testResolverClassifiesPrintableLetterAsNonSpecial() { - let event = TestKeystrokes.make( - keyCode: KeyboardKeyCode.a.rawValue, + let event = StandardKeyEvent.stub( + keyCode: .a, characters: "a", charactersIgnoringModifiers: "a" ) diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerTests.swift index 1d3e9f34..43a9f35e 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerTests.swift @@ -44,8 +44,8 @@ final class KeyboardVisualizerTests: XCTestCase { self.settings.isEnabled = true self.visualizer.isPresentationActive = true - let keystroke = TestKeystrokes.make( - keyCode: KeyboardKeyCode.k.rawValue, + let keystroke = StandardKeyEvent.stub( + keyCode: .k, characters: "k", charactersIgnoringModifiers: "k" ) diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift index 7b416d2e..78c8b8d0 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeycapItemFactoryTests.swift @@ -148,11 +148,11 @@ final class KeycapItemFactoryTests: XCTestCase { let palette = Self.makePalette() let downItem = KeycapItemFactory.mouseItem( - for: TestMouseEvents.make(type: .leftMouseDown), + for: MouseEvent.stub(type: .leftMouseDown), palette: palette ) let upItem = KeycapItemFactory.mouseItem( - for: TestMouseEvents.make(type: .leftMouseUp), + for: MouseEvent.stub(type: .leftMouseUp), palette: palette ) diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Mouse/PointerIcon/PointerIconVisualizerTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Mouse/PointerIcon/PointerIconVisualizerTests.swift index cd72db3a..7c57eee9 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Mouse/PointerIcon/PointerIconVisualizerTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Mouse/PointerIcon/PointerIconVisualizerTests.swift @@ -125,7 +125,7 @@ final class PointerIconVisualizerTests: XCTestCase { XCTAssertEqual(view.displayedKind, .rightButton) XCTAssertTrue(view.isTransientlyVisible) - view.handle(mouseEvent: try makeScrollEvent(deltaX: 0, deltaY: 0)) + view.handle(mouseEvent: MouseEvent.scrollStub()) XCTAssertEqual(view.displayedKind, .rightButton) XCTAssertTrue(view.isTransientlyVisible) @@ -189,20 +189,6 @@ final class PointerIconVisualizerTests: XCTestCase { return MouseEvent(nsEvent: nsEvent) } - private func makeScrollEvent(deltaX: Int32 = 0, deltaY: Int32) throws -> MouseEvent { - guard let cgEvent = CGEvent( - scrollWheelEvent2Source: nil, - units: .pixel, - wheelCount: 2, - wheel1: deltaY, - wheel2: deltaX, - wheel3: 0 - ), let nsEvent = NSEvent(cgEvent: cgEvent) else { - throw TestError.eventCreationFailed - } - return MouseEvent(nsEvent: nsEvent) - } - private enum TestError: Error { case eventCreationFailed } diff --git a/Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventProcessorTests.swift b/Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventProcessorTests.swift index 1224ea2f..43871981 100644 --- a/Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventProcessorTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventProcessorTests.swift @@ -15,7 +15,7 @@ final class EventProcessorTests: XCTestCase { var items: [DisplayEvent] = [] processor.onItemProduced = { items.append($0) } - processor.processMouseEvent(TestMouseEvents.make(type: .leftMouseUp, buttonNumber: 0, modifiers: [.command])) + processor.processMouseEvent(MouseEvent.stub(type: .leftMouseUp, buttonNumber: 0, modifiers: [.command])) XCTAssertEqual(items.count, 2) switch items[0] { @@ -38,7 +38,7 @@ final class EventProcessorTests: XCTestCase { var items: [DisplayEvent] = [] processor.onItemProduced = { items.append($0) } - processor.processMouseEvent(TestMouseEvents.make(type: .leftMouseUp, buttonNumber: 0, modifiers: [])) + processor.processMouseEvent(MouseEvent.stub(type: .leftMouseUp, buttonNumber: 0, modifiers: [])) XCTAssertEqual(items.count, 2) switch items[0] { diff --git a/Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventTransformerKeystrokeTests.swift b/Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventTransformerKeystrokeTests.swift index 3f96f186..c7823bfa 100644 --- a/Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventTransformerKeystrokeTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventTransformerKeystrokeTests.swift @@ -19,7 +19,7 @@ final class EventTransformerKeystrokeTests: XCTestCase { override func setUpWithError() throws { try super.setUpWithError() - self.transformer = EventTransformer(keyboardLayout: try TestKeyboardLayouts.requireUSEnglish()) + self.transformer = EventTransformer(keyboardLayout: try TISInputSource.usEnglish()) } } @@ -57,7 +57,7 @@ extension EventTransformerKeystrokeTests { func test_modifierGlyphsPrecedeTheLegendInCanonicalOrder() { for (modifiers, name) in Self.allModifierCombinations { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.digit7.rawValue, modifiers: modifiers) + let keystroke = StandardKeyEvent.stub(keyCode: .digit7, modifiers: modifiers) XCTAssertEqual( self.transform(keystroke), @@ -70,7 +70,7 @@ extension EventTransformerKeystrokeTests { /// A digit has no distinct uppercase form, so only letters show the casing rule. func test_letterLegendIsUppercasedOnlyWhenModified() { for (modifiers, name) in Self.allModifierCombinations { - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.a.rawValue, modifiers: modifiers) + let keystroke = StandardKeyEvent.stub(keyCode: .a, modifiers: modifiers) let expectedLegend = modifiers.isEmpty ? "a" : "A" XCTAssertEqual( @@ -87,7 +87,7 @@ extension EventTransformerKeystrokeTests { let cases: [(KeyboardKeyCode, String)] = [(.u, "U"), (.e, "E"), (.grave, "`")] for (keyCode, expectedLegend) in cases { - let keystroke = TestKeystrokes.make(keyCode: keyCode.rawValue, modifiers: TestModifierFlags.option) + let keystroke = StandardKeyEvent.stub(keyCode: keyCode, modifiers: .recorded(.option)) XCTAssertEqual( self.transform(keystroke), @@ -103,8 +103,8 @@ extension EventTransformerKeystrokeTests { extension EventTransformerKeystrokeTests { /// German ß uppercases to "SS", which would misreport the key's legend. func test_legendThatExpandsWhenUppercasedIsLeftAlone() throws { - let german = EventTransformer(keyboardLayout: try TestKeyboardLayouts.requireGerman()) - let keystroke = TestKeystrokes.make(keyCode: KeyboardKeyCode.minus.rawValue, modifiers: TestModifierFlags.command) + let german = EventTransformer(keyboardLayout: try TISInputSource.german()) + let keystroke = StandardKeyEvent.stub(keyCode: .minus, modifiers: .recorded(.command)) XCTAssertEqual(german.transform(.keystroke(keystroke)), KeyboardGlyphCatalog.command + "ß") } @@ -123,7 +123,7 @@ extension EventTransformerKeystrokeTests { ] for (keyCode, expected) in cases { - let keystroke = TestKeystrokes.make(keyCode: keyCode.rawValue) + let keystroke = StandardKeyEvent.stub(keyCode: keyCode) XCTAssertEqual(self.transform(keystroke), expected, "for \(keyCode)") } } @@ -137,16 +137,16 @@ extension EventTransformerKeystrokeTests { ] for (keyCode, expected) in cases { - let keystroke = TestKeystrokes.make(keyCode: keyCode.rawValue) + let keystroke = StandardKeyEvent.stub(keyCode: keyCode) XCTAssertEqual(self.transform(keystroke), expected, "for \(keyCode)") } } func test_modifiersPrefixASpecialKeySymbol() { - let character = TestKeyboardCharacters.functionKeyCharacter(NSUpArrowFunctionKey) - let keystroke = TestKeystrokes.make( - keyCode: KeyboardKeyCode.upArrow.rawValue, - modifiers: TestModifierFlags.functionOptionShiftNumericPad, + let character = String.functionKey(NSUpArrowFunctionKey) + let keystroke = StandardKeyEvent.stub( + keyCode: .upArrow, + modifiers: .recorded([.function, .option, .shift, .numericPad]), characters: character, charactersIgnoringModifiers: character ) @@ -166,7 +166,7 @@ extension EventTransformerKeystrokeTests { ] for (keyCode, expected) in cases { - let keystroke = TestKeystrokes.make(keyCode: keyCode.rawValue, modifiers: TestModifierFlags.function) + let keystroke = StandardKeyEvent.stub(keyCode: keyCode, modifiers: .recorded(.function)) XCTAssertEqual(self.transform(keystroke), expected, "for \(keyCode)") } } @@ -175,7 +175,7 @@ extension EventTransformerKeystrokeTests { let cases: [(KeyboardKeyCode, KeyboardSpecialKey)] = [(.eisu, .eisu), (.kana, .kana)] for (keyCode, specialKey) in cases { - let keystroke = TestKeystrokes.make(keyCode: keyCode.rawValue) + let keystroke = StandardKeyEvent.stub(keyCode: keyCode) XCTAssertEqual(self.transform(keystroke), specialKey.displayText, "for \(keyCode)") } } @@ -187,23 +187,23 @@ extension EventTransformerKeystrokeTests { /// macOS reuses the Help key code for Insert on many external keyboards, so /// these three keys are told apart by the event's characters, not the key code. func test_helpKeyCodeResolvesFromTheEventCharacters() { - let insertCharacter = TestKeyboardCharacters.functionKeyCharacter(NSInsertFunctionKey) - let insert = TestKeystrokes.make( - keyCode: KeyboardKeyCode.help.rawValue, + let insertCharacter = String.functionKey(NSInsertFunctionKey) + let insert = StandardKeyEvent.stub( + keyCode: .help, characters: insertCharacter, charactersIgnoringModifiers: insertCharacter ) XCTAssertEqual(self.transform(insert), KeyboardSpecialKey.insert.displayText) - let helpCharacter = TestKeyboardCharacters.functionKeyCharacter(NSHelpFunctionKey) - let help = TestKeystrokes.make( - keyCode: KeyboardKeyCode.help.rawValue, + let helpCharacter = String.functionKey(NSHelpFunctionKey) + let help = StandardKeyEvent.stub( + keyCode: .help, characters: helpCharacter, charactersIgnoringModifiers: helpCharacter ) XCTAssertEqual(self.transform(help), "help") - let withoutCharacters = TestKeystrokes.make(keyCode: KeyboardKeyCode.help.rawValue) + let withoutCharacters = StandardKeyEvent.stub(keyCode: .help) XCTAssertEqual(self.transform(withoutCharacters), KeyboardSpecialKey.insert.displayText) } } diff --git a/Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventTransformerMouseTests.swift b/Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventTransformerMouseTests.swift index 730421fc..4654139e 100644 --- a/Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventTransformerMouseTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventTransformerMouseTests.swift @@ -19,43 +19,43 @@ final class EventTransformerMouseTests: XCTestCase { override func setUpWithError() throws { try super.setUpWithError() - self.keyboardLayout = try TestKeyboardLayouts.requireUSEnglish() + self.keyboardLayout = try TISInputSource.usEnglish() } // MARK: - Mouse buttons func test_MouseEvent_leftMouseDownIsLMB() { - let event = TestMouseEvents.make(type: .leftMouseDown, buttonNumber: 0, modifiers: []) + let event = MouseEvent.stub(type: .leftMouseDown, buttonNumber: 0, modifiers: []) XCTAssertEqual(transform(event), "LMB") } func test_MouseEvent_rightMouseDownIsRMB() { - let event = TestMouseEvents.make(type: .rightMouseDown, buttonNumber: 1, modifiers: []) + let event = MouseEvent.stub(type: .rightMouseDown, buttonNumber: 1, modifiers: []) XCTAssertEqual(transform(event), "RMB") } func test_MouseEvent_middleMouseDownIsMMB() { - let event = TestMouseEvents.make(type: .otherMouseDown, buttonNumber: 2, modifiers: []) + let event = MouseEvent.stub(type: .otherMouseDown, buttonNumber: 2, modifiers: []) XCTAssertEqual(transform(event), "MMB") } func test_MouseEvent_fourthButtonIsMB4() { - let event = TestMouseEvents.make(type: .otherMouseDown, buttonNumber: 3, modifiers: []) + let event = MouseEvent.stub(type: .otherMouseDown, buttonNumber: 3, modifiers: []) XCTAssertEqual(transform(event), "MB4") } func test_MouseEvent_fifthButtonIsMB5() { - let event = TestMouseEvents.make(type: .otherMouseDown, buttonNumber: 4, modifiers: []) + let event = MouseEvent.stub(type: .otherMouseDown, buttonNumber: 4, modifiers: []) XCTAssertEqual(transform(event), "MB5") } func test_MouseEvent_commandLeftClickShowsCommandLMB() { - let event = TestMouseEvents.make(type: .leftMouseDown, buttonNumber: 0, modifiers: .command) + let event = MouseEvent.stub(type: .leftMouseDown, buttonNumber: 0, modifiers: .command) XCTAssertEqual(transform(event), KeyboardGlyphCatalog.command + "LMB") } func test_MouseEvent_optionShiftRightClickShowsModifiersWithRMB() { - let event = TestMouseEvents.make(type: .rightMouseDown, buttonNumber: 1, modifiers: [.option, .shift]) + let event = MouseEvent.stub(type: .rightMouseDown, buttonNumber: 1, modifiers: [.option, .shift]) XCTAssertEqual(transform(event), KeyboardGlyphCatalog.option + KeyboardGlyphCatalog.shift + "RMB") } } diff --git a/Apps/Keyty/Tests/KeytyTests/Support/TestMouseEvents.swift b/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/MouseEvent+Stub.swift similarity index 81% rename from Apps/Keyty/Tests/KeytyTests/Support/TestMouseEvents.swift rename to Apps/Keyty/Tests/KeytyTests/Support/Fixtures/MouseEvent+Stub.swift index 5323f11b..e303dbe2 100644 --- a/Apps/Keyty/Tests/KeytyTests/Support/TestMouseEvents.swift +++ b/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/MouseEvent+Stub.swift @@ -1,5 +1,5 @@ // -// TestMouseEvents.swift +// MouseEvent+Stub.swift // KeytyTests // // SPDX-FileCopyrightText: 2026 Serhii Bykov @@ -9,8 +9,8 @@ import AppKit @testable import Keyty -enum TestMouseEvents { - static func make( +extension MouseEvent { + static func stub( type: NSEvent.EventType, buttonNumber: Int = 0, modifiers: NSEvent.ModifierFlags = [] @@ -26,6 +26,18 @@ enum TestMouseEvents { return MouseEvent(nsEvent: NSEvent(cgEvent: cgEvent)!) } + static func scrollStub(deltaX: Int32 = 0, deltaY: Int32 = 0) -> MouseEvent { + let cgEvent = CGEvent( + scrollWheelEvent2Source: nil, + units: .pixel, + wheelCount: 2, + wheel1: deltaY, + wheel2: deltaX, + wheel3: 0 + )! + return MouseEvent(nsEvent: NSEvent(cgEvent: cgEvent)!) + } + private static func cgMouseEventType(for nsEventType: NSEvent.EventType) -> CGEventType { switch nsEventType { case .leftMouseDown: return .leftMouseDown diff --git a/Apps/Keyty/Tests/KeytyTests/Support/Extensions/AppKit/NSEventModifierFlagsDeviceMasks.swift b/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/NSEventModifierFlags+DeviceMasks.swift similarity index 100% rename from Apps/Keyty/Tests/KeytyTests/Support/Extensions/AppKit/NSEventModifierFlagsDeviceMasks.swift rename to Apps/Keyty/Tests/KeytyTests/Support/Fixtures/NSEventModifierFlags+DeviceMasks.swift diff --git a/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/NSEventModifierFlags+Recorded.swift b/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/NSEventModifierFlags+Recorded.swift new file mode 100644 index 00000000..dba477ca --- /dev/null +++ b/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/NSEventModifierFlags+Recorded.swift @@ -0,0 +1,36 @@ +// +// NSEventModifierFlags+Recorded.swift +// KeytyTests +// +// SPDX-FileCopyrightText: 2026 Serhii Bykov +// SPDX-License-Identifier: BSD-3-Clause +// + +import AppKit +import IOKit.hidsystem +@testable import Keyty + +extension NSEvent.ModifierFlags { + private static let recordedEventStateMask: UInt = 0x100 + + // The flags a real key event carries: the modifiers themselves, the + // device-dependent bit for each side-specific key, and the recorded-event bit. + static func recorded(_ flags: NSEvent.ModifierFlags) -> NSEvent.ModifierFlags { + let deviceMasks = flags.elements().compactMap(Self.deviceMask(for:)) + return flags.addingRawMasks([Self.recordedEventStateMask] + deviceMasks) + } + + private static func deviceMask(for flag: NSEvent.ModifierFlags) -> UInt? { + switch flag { + case .control: return UInt(NX_DEVICELCTLKEYMASK) + case .shift: return UInt(NX_DEVICELSHIFTKEYMASK) + case .command: return UInt(NX_DEVICELCMDKEYMASK) + case .option: return UInt(NX_DEVICELALTKEYMASK) + default: return nil + } + } + + private func elements() -> [NSEvent.ModifierFlags] { + [.control, .shift, .command, .option, .function, .numericPad].filter(self.contains) + } +} diff --git a/Apps/Keyty/Tests/KeytyTests/Support/TestKeystrokes.swift b/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/StandardKeyEvent+Stub.swift similarity index 66% rename from Apps/Keyty/Tests/KeytyTests/Support/TestKeystrokes.swift rename to Apps/Keyty/Tests/KeytyTests/Support/Fixtures/StandardKeyEvent+Stub.swift index 0a44d99a..44c48bac 100644 --- a/Apps/Keyty/Tests/KeytyTests/Support/TestKeystrokes.swift +++ b/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/StandardKeyEvent+Stub.swift @@ -1,5 +1,5 @@ // -// TestKeystrokes.swift +// StandardKeyEvent+Stub.swift // KeytyTests // // SPDX-FileCopyrightText: 2026 Serhii Bykov @@ -9,12 +9,12 @@ import AppKit @testable import Keyty -enum TestKeystrokes { - /// The transformer resolves a legend from the key code and the layout, so - /// `characters` only matters for keys whose meaning comes from the event - /// itself — Help versus Insert, and the arrow function keys. - static func make( - keyCode: UInt16, +extension StandardKeyEvent { + // The transformer resolves a legend from the key code and the layout, so + // `characters` only matters for keys whose meaning comes from the event + // itself — Help versus Insert, and the arrow function keys. + static func stub( + keyCode: KeyboardKeyCode, modifiers: NSEvent.ModifierFlags = [], characters: String = "", charactersIgnoringModifiers: String = "" @@ -29,7 +29,7 @@ enum TestKeystrokes { characters: characters, charactersIgnoringModifiers: charactersIgnoringModifiers, isARepeat: false, - keyCode: keyCode + keyCode: keyCode.rawValue )! return StandardKeyEvent(nsEvent: event) } diff --git a/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/String+FunctionKey.swift b/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/String+FunctionKey.swift new file mode 100644 index 00000000..70b8e721 --- /dev/null +++ b/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/String+FunctionKey.swift @@ -0,0 +1,16 @@ +// +// String+FunctionKey.swift +// KeytyTests +// +// SPDX-FileCopyrightText: 2026 Serhii Bykov +// SPDX-License-Identifier: BSD-3-Clause +// + +import AppKit + +extension String { + // The character AppKit reports for a function key such as `NSHelpFunctionKey`. + static func functionKey(_ key: Int) -> String { + String(UnicodeScalar(key)!) + } +} diff --git a/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/TISInputSource+Layouts.swift b/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/TISInputSource+Layouts.swift new file mode 100644 index 00000000..ee69eb44 --- /dev/null +++ b/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/TISInputSource+Layouts.swift @@ -0,0 +1,43 @@ +// +// TISInputSource+Layouts.swift +// KeytyTests +// +// SPDX-FileCopyrightText: 2026 Serhii Bykov +// SPDX-License-Identifier: BSD-3-Clause +// + +import Carbon +import XCTest + +extension TISInputSource { + static func usEnglish(file: StaticString = #filePath, line: UInt = #line) throws -> TISInputSource { + try self.layout(id: "com.apple.keylayout.US", file: file, line: line) + } + + // The German layout, whose ß key exercises legends that expand when uppercased. + static func german(file: StaticString = #filePath, line: UInt = #line) throws -> TISInputSource { + try self.layout(id: "com.apple.keylayout.German", file: file, line: line) + } + + private static func layout(id: String, + file: StaticString = #filePath, + line: UInt = #line) throws -> TISInputSource { + let properties: [String: Any] = [ + kTISPropertyInputSourceID as String: id, + kTISPropertyInputSourceType as String: kTISTypeKeyboardLayout as String + ] + let inputSources = try XCTUnwrap( + TISCreateInputSourceList(properties as CFDictionary, true)? + .takeRetainedValue() as? [TISInputSource], + "Expected the \(id) keyboard layout to be available", + file: file, + line: line + ) + return try XCTUnwrap( + inputSources.first, + "Expected the \(id) keyboard layout to be available", + file: file, + line: line + ) + } +} diff --git a/Apps/Keyty/Tests/KeytyTests/Support/TestKeyboardCharacters.swift b/Apps/Keyty/Tests/KeytyTests/Support/TestKeyboardCharacters.swift deleted file mode 100644 index eb8340db..00000000 --- a/Apps/Keyty/Tests/KeytyTests/Support/TestKeyboardCharacters.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// TestKeyboardCharacters.swift -// KeytyTests -// -// SPDX-FileCopyrightText: 2026 Serhii Bykov -// SPDX-License-Identifier: BSD-3-Clause -// - -import AppKit - -enum TestKeyboardCharacters { - static func functionKeyCharacter(_ key: Int) -> String { - String(UnicodeScalar(key)!) - } -} diff --git a/Apps/Keyty/Tests/KeytyTests/Support/TestKeyboardLayouts.swift b/Apps/Keyty/Tests/KeytyTests/Support/TestKeyboardLayouts.swift deleted file mode 100644 index 54bf3c6c..00000000 --- a/Apps/Keyty/Tests/KeytyTests/Support/TestKeyboardLayouts.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// TestKeyboardLayouts.swift -// KeytyTests -// -// SPDX-FileCopyrightText: 2026 Serhii Bykov -// SPDX-License-Identifier: BSD-3-Clause -// - -import Carbon -import XCTest - -enum TestKeyboardLayouts { - static func requireUSEnglish(file: StaticString = #filePath, - line: UInt = #line) throws -> TISInputSource { - try self.require(id: "com.apple.keylayout.US", file: file, line: line) - } - - /// The German layout, whose ß key exercises legends that expand when uppercased. - static func requireGerman(file: StaticString = #filePath, - line: UInt = #line) throws -> TISInputSource { - try self.require(id: "com.apple.keylayout.German", file: file, line: line) - } - - private static func require(id: String, - file: StaticString = #filePath, - line: UInt = #line) throws -> TISInputSource { - let properties: [String: Any] = [ - kTISPropertyInputSourceID as String: id, - kTISPropertyInputSourceType as String: kTISTypeKeyboardLayout as String - ] - let inputSources = try XCTUnwrap( - TISCreateInputSourceList(properties as CFDictionary, true)? - .takeRetainedValue() as? [TISInputSource], - "Expected the US English keyboard layout to be available", - file: file, - line: line - ) - return try XCTUnwrap( - inputSources.first, - "Expected the US English keyboard layout to be available", - file: file, - line: line - ) - } -} diff --git a/Apps/Keyty/Tests/KeytyTests/Support/TestModifierFlags.swift b/Apps/Keyty/Tests/KeytyTests/Support/TestModifierFlags.swift deleted file mode 100644 index 94da86ad..00000000 --- a/Apps/Keyty/Tests/KeytyTests/Support/TestModifierFlags.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// TestModifierFlags.swift -// KeytyTests -// -// SPDX-FileCopyrightText: 2026 Serhii Bykov -// SPDX-License-Identifier: BSD-3-Clause -// - -import AppKit -import IOKit.hidsystem -@testable import Keyty - -enum TestModifierFlags { - private static let recordedEventStateMask: UInt = 0x100 - - static let none = Self.recorded([]) - static let control = Self.recorded(.control, deviceMasks: UInt(NX_DEVICELCTLKEYMASK)) - static let shift = Self.recorded(.shift, deviceMasks: UInt(NX_DEVICELSHIFTKEYMASK)) - static let controlShift = Self.recorded( - [.control, .shift], - deviceMasks: UInt(NX_DEVICELCTLKEYMASK), UInt(NX_DEVICELSHIFTKEYMASK) - ) - static let command = Self.recorded(.command, deviceMasks: UInt(NX_DEVICELCMDKEYMASK)) - static let commandShift = Self.recorded( - [.command, .shift], - deviceMasks: UInt(NX_DEVICELCMDKEYMASK), UInt(NX_DEVICELSHIFTKEYMASK) - ) - static let commandOption = Self.recorded( - [.command, .option], - deviceMasks: UInt(NX_DEVICELCMDKEYMASK), UInt(NX_DEVICELALTKEYMASK) - ) - static let commandOptionShift = Self.recorded( - [.command, .option, .shift], - deviceMasks: UInt(NX_DEVICELCMDKEYMASK), UInt(NX_DEVICELALTKEYMASK), UInt(NX_DEVICELSHIFTKEYMASK) - ) - static let controlCommandShift = Self.recorded( - [.control, .command, .shift], - deviceMasks: UInt(NX_DEVICELCTLKEYMASK), UInt(NX_DEVICELCMDKEYMASK), UInt(NX_DEVICELSHIFTKEYMASK) - ) - static let controlOption = Self.recorded( - [.control, .option], - deviceMasks: UInt(NX_DEVICELCTLKEYMASK), UInt(NX_DEVICELALTKEYMASK) - ) - static let controlOptionShift = Self.recorded( - [.control, .option, .shift], - deviceMasks: UInt(NX_DEVICELCTLKEYMASK), UInt(NX_DEVICELALTKEYMASK), UInt(NX_DEVICELSHIFTKEYMASK) - ) - static let option = Self.recorded(.option, deviceMasks: UInt(NX_DEVICELALTKEYMASK)) - static let optionShift = Self.recorded( - [.option, .shift], - deviceMasks: UInt(NX_DEVICELALTKEYMASK), UInt(NX_DEVICELSHIFTKEYMASK) - ) - static let function = Self.recorded(.function) - static let functionOptionShiftNumericPad = Self.recorded( - [.function, .option, .shift, .numericPad], - deviceMasks: UInt(NX_DEVICELALTKEYMASK), UInt(NX_DEVICELSHIFTKEYMASK) - ) - - private static func recorded(_ flags: NSEvent.ModifierFlags, - deviceMasks: UInt...) -> NSEvent.ModifierFlags { - flags.addingRawMasks([recordedEventStateMask] + deviceMasks) - } -} From 74cb847905dd5fb2fbea3324ee0fa8ae242f01bb Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Fri, 14 Aug 2026 01:00:03 +0200 Subject: [PATCH 3/6] refactor(keyboard): derive device masks from the modifier key --- .../Domain/Keyboard/KeyboardModifierKey.swift | 33 +++++++++++-------- .../NSEventModifierFlags+Recorded.swift | 18 ++-------- 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/Apps/Keyty/Sources/Keyty/Domain/Keyboard/KeyboardModifierKey.swift b/Apps/Keyty/Sources/Keyty/Domain/Keyboard/KeyboardModifierKey.swift index 29934449..3e3cbe3a 100644 --- a/Apps/Keyty/Sources/Keyty/Domain/Keyboard/KeyboardModifierKey.swift +++ b/Apps/Keyty/Sources/Keyty/Domain/Keyboard/KeyboardModifierKey.swift @@ -53,10 +53,7 @@ extension KeyboardModifierKey { } static func keys(in flags: NSEvent.ModifierFlags) -> Set { - let rawValue = flags.rawValue - return Set(Self.deviceModifierKeys.compactMap { mask, key in - rawValue & mask == 0 ? nil : key - }) + Set(Self.all.filter { flags.rawValue & $0.deviceMask != 0 }) } } @@ -71,14 +68,24 @@ extension KeyboardModifierKey { static let leftControl = KeyboardModifierKey(.control, location: .left) static let rightControl = KeyboardModifierKey(.control, location: .right) - private static let deviceModifierKeys: [(mask: UInt, key: KeyboardModifierKey)] = [ - (UInt(NX_DEVICELCMDKEYMASK), .leftCommand), - (UInt(NX_DEVICERCMDKEYMASK), .rightCommand), - (UInt(NX_DEVICELSHIFTKEYMASK), .leftShift), - (UInt(NX_DEVICERSHIFTKEYMASK), .rightShift), - (UInt(NX_DEVICELALTKEYMASK), .leftOption), - (UInt(NX_DEVICERALTKEYMASK), .rightOption), - (UInt(NX_DEVICELCTLKEYMASK), .leftControl), - (UInt(NX_DEVICERCTLKEYMASK), .rightControl), + static let all: [KeyboardModifierKey] = [ + .leftCommand, .rightCommand, + .leftShift, .rightShift, + .leftOption, .rightOption, + .leftControl, .rightControl ] + + // The device-dependent bit macOS sets for this specific physical key. + var deviceMask: UInt { + switch (self.kind, self.location) { + case (.command, .left): return UInt(NX_DEVICELCMDKEYMASK) + case (.command, .right): return UInt(NX_DEVICERCMDKEYMASK) + case (.shift, .left): return UInt(NX_DEVICELSHIFTKEYMASK) + case (.shift, .right): return UInt(NX_DEVICERSHIFTKEYMASK) + case (.option, .left): return UInt(NX_DEVICELALTKEYMASK) + case (.option, .right): return UInt(NX_DEVICERALTKEYMASK) + case (.control, .left): return UInt(NX_DEVICELCTLKEYMASK) + case (.control, .right): return UInt(NX_DEVICERCTLKEYMASK) + } + } } diff --git a/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/NSEventModifierFlags+Recorded.swift b/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/NSEventModifierFlags+Recorded.swift index dba477ca..1df84705 100644 --- a/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/NSEventModifierFlags+Recorded.swift +++ b/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/NSEventModifierFlags+Recorded.swift @@ -16,21 +16,9 @@ extension NSEvent.ModifierFlags { // The flags a real key event carries: the modifiers themselves, the // device-dependent bit for each side-specific key, and the recorded-event bit. static func recorded(_ flags: NSEvent.ModifierFlags) -> NSEvent.ModifierFlags { - let deviceMasks = flags.elements().compactMap(Self.deviceMask(for:)) + let deviceMasks = KeyboardModifierKey.all + .filter { $0.location == .left && flags.contains($0.kind.flag) } + .map(\.deviceMask) return flags.addingRawMasks([Self.recordedEventStateMask] + deviceMasks) } - - private static func deviceMask(for flag: NSEvent.ModifierFlags) -> UInt? { - switch flag { - case .control: return UInt(NX_DEVICELCTLKEYMASK) - case .shift: return UInt(NX_DEVICELSHIFTKEYMASK) - case .command: return UInt(NX_DEVICELCMDKEYMASK) - case .option: return UInt(NX_DEVICELALTKEYMASK) - default: return nil - } - } - - private func elements() -> [NSEvent.ModifierFlags] { - [.control, .shift, .command, .option, .function, .numericPad].filter(self.contains) - } } From 02b634bb15750cc813ce89a780bdec7cd364f25e Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Fri, 14 Aug 2026 11:52:10 +0200 Subject: [PATCH 4/6] test: remove duplication between fixtures and source helper --- ...KeyboardVisualizerGroupViewSnapshotTests.swift | 6 +----- .../Support/Fixtures/MouseEvent+Stub.swift | 8 +------- .../NSEventModifierFlags+DeviceMasks.swift | 15 --------------- .../Support/Fixtures/String+FunctionKey.swift | 3 ++- 4 files changed, 4 insertions(+), 28 deletions(-) delete mode 100644 Apps/Keyty/Tests/KeytyTests/Support/Fixtures/NSEventModifierFlags+DeviceMasks.swift diff --git a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerGroupViewSnapshotTests.swift b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerGroupViewSnapshotTests.swift index 32c63b16..c44bbd0b 100644 --- a/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerGroupViewSnapshotTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Features/Visualizers/Keyboard/Keycaps/KeyboardVisualizerGroupViewSnapshotTests.swift @@ -47,11 +47,7 @@ final class KeyboardVisualizerGroupViewSnapshotTests: XCTestCase { private extension KeyboardVisualizerGroupViewSnapshotTests { static var commandShiftFlags: NSEvent.ModifierFlags { - NSEvent.ModifierFlags( - [.command, .shift], - deviceMasks: UInt(NX_DEVICELCMDKEYMASK), - UInt(NX_DEVICELSHIFTKEYMASK) - ) + .recorded([.command, .shift]) } func keycapItems( diff --git a/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/MouseEvent+Stub.swift b/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/MouseEvent+Stub.swift index e303dbe2..e701de00 100644 --- a/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/MouseEvent+Stub.swift +++ b/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/MouseEvent+Stub.swift @@ -58,12 +58,6 @@ extension MouseEvent { } private static func cgEventFlags(for modifierFlags: NSEvent.ModifierFlags) -> CGEventFlags { - var flags: CGEventFlags = [] - if modifierFlags.contains(.shift) { flags.insert(.maskShift) } - if modifierFlags.contains(.command) { flags.insert(.maskCommand) } - if modifierFlags.contains(.control) { flags.insert(.maskControl) } - if modifierFlags.contains(.option) { flags.insert(.maskAlternate) } - if modifierFlags.contains(.function) { flags.insert(.maskSecondaryFn) } - return flags + CGEventFlags(rawValue: UInt64(modifierFlags.rawValue)) } } diff --git a/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/NSEventModifierFlags+DeviceMasks.swift b/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/NSEventModifierFlags+DeviceMasks.swift deleted file mode 100644 index 4c171315..00000000 --- a/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/NSEventModifierFlags+DeviceMasks.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// NSEventModifierFlagsDeviceMasks.swift -// KeytyTests -// -// SPDX-FileCopyrightText: 2026 Serhii Bykov -// SPDX-License-Identifier: BSD-3-Clause -// - -import AppKit - -extension NSEvent.ModifierFlags { - init(_ flags: Self, deviceMasks masks: UInt...) { - self.init(rawValue: masks.reduce(flags.rawValue) { $0 | $1 }) - } -} diff --git a/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/String+FunctionKey.swift b/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/String+FunctionKey.swift index 70b8e721..243681b1 100644 --- a/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/String+FunctionKey.swift +++ b/Apps/Keyty/Tests/KeytyTests/Support/Fixtures/String+FunctionKey.swift @@ -7,10 +7,11 @@ // import AppKit +@testable import Keyty extension String { // The character AppKit reports for a function key such as `NSHelpFunctionKey`. static func functionKey(_ key: Int) -> String { - String(UnicodeScalar(key)!) + UnicodeScalar(key)!.string } } From 998621858802425cadfac9ecb2b108b6c0d9ea04 Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Fri, 14 Aug 2026 11:55:37 +0200 Subject: [PATCH 5/6] refactor(events): drop the unused isCommand property --- .../Sources/Keyty/Domain/Events/StandardKeyEvent.swift | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Apps/Keyty/Sources/Keyty/Domain/Events/StandardKeyEvent.swift b/Apps/Keyty/Sources/Keyty/Domain/Events/StandardKeyEvent.swift index 4a41cdbe..55079ddd 100644 --- a/Apps/Keyty/Sources/Keyty/Domain/Events/StandardKeyEvent.swift +++ b/Apps/Keyty/Sources/Keyty/Domain/Events/StandardKeyEvent.swift @@ -28,11 +28,7 @@ public struct StandardKeyEvent { } public var displayString: String { - EventTransformer.shared.transform(inputEvent) - } - - public var isCommand: Bool { - !self.modifierFlags.intersection([.control, .command]).isEmpty + EventTransformer.shared.transform(self.inputEvent) } public var isModified: Bool { From e8962b158bdd503c8bdb5ae9ca535c977fa82fbd Mon Sep 17 00:00:00 2001 From: Serhii Bykov Date: Fri, 14 Aug 2026 12:06:50 +0200 Subject: [PATCH 6/6] test: name tests after the event types they cover, without underscores --- ... => EventTransformerMouseEventTests.swift} | 18 +++++------ ...entTransformerStandardKeyEventTests.swift} | 32 +++++++++---------- .../AppKit/NSBezierPathCGPathTests.swift | 8 ++--- .../Extensions/AppKit/NSColorHexTests.swift | 32 +++++++++---------- .../AppKit/NSPointOffsetTests.swift | 6 ++-- .../CoreGraphics/CGRectDistanceTests.swift | 8 ++--- .../CGRectNormalizedPointTests.swift | 8 ++--- .../Foundation/CGFloatClampTests.swift | 8 ++--- .../Foundation/CGFloatSuperellipseTests.swift | 12 +++---- .../TimeIntervalNanosecondsTests.swift | 6 ++-- 10 files changed, 69 insertions(+), 69 deletions(-) rename Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/{EventTransformerMouseTests.swift => EventTransformerMouseEventTests.swift} (78%) rename Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/{EventTransformerKeystrokeTests.swift => EventTransformerStandardKeyEventTests.swift} (89%) diff --git a/Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventTransformerMouseTests.swift b/Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventTransformerMouseEventTests.swift similarity index 78% rename from Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventTransformerMouseTests.swift rename to Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventTransformerMouseEventTests.swift index 4654139e..1442a49d 100644 --- a/Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventTransformerMouseTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventTransformerMouseEventTests.swift @@ -1,5 +1,5 @@ // -// EventTransformerMouseTests.swift +// EventTransformerMouseEventTests.swift // KeytyTests // // SPDX-FileCopyrightText: 2026 Serhii Bykov @@ -10,7 +10,7 @@ import Carbon import XCTest @testable import Keyty -final class EventTransformerMouseTests: XCTestCase { +final class EventTransformerMouseEventTests: XCTestCase { var keyboardLayout: TISInputSource! func transform(_ event: MouseEvent) -> String { @@ -24,37 +24,37 @@ final class EventTransformerMouseTests: XCTestCase { // MARK: - Mouse buttons - func test_MouseEvent_leftMouseDownIsLMB() { + func testLeftMouseDownIsLMB() { let event = MouseEvent.stub(type: .leftMouseDown, buttonNumber: 0, modifiers: []) XCTAssertEqual(transform(event), "LMB") } - func test_MouseEvent_rightMouseDownIsRMB() { + func testRightMouseDownIsRMB() { let event = MouseEvent.stub(type: .rightMouseDown, buttonNumber: 1, modifiers: []) XCTAssertEqual(transform(event), "RMB") } - func test_MouseEvent_middleMouseDownIsMMB() { + func testMiddleMouseDownIsMMB() { let event = MouseEvent.stub(type: .otherMouseDown, buttonNumber: 2, modifiers: []) XCTAssertEqual(transform(event), "MMB") } - func test_MouseEvent_fourthButtonIsMB4() { + func testFourthButtonIsMB4() { let event = MouseEvent.stub(type: .otherMouseDown, buttonNumber: 3, modifiers: []) XCTAssertEqual(transform(event), "MB4") } - func test_MouseEvent_fifthButtonIsMB5() { + func testFifthButtonIsMB5() { let event = MouseEvent.stub(type: .otherMouseDown, buttonNumber: 4, modifiers: []) XCTAssertEqual(transform(event), "MB5") } - func test_MouseEvent_commandLeftClickShowsCommandLMB() { + func testCommandLeftClickShowsCommandLMB() { let event = MouseEvent.stub(type: .leftMouseDown, buttonNumber: 0, modifiers: .command) XCTAssertEqual(transform(event), KeyboardGlyphCatalog.command + "LMB") } - func test_MouseEvent_optionShiftRightClickShowsModifiersWithRMB() { + func testOptionShiftRightClickShowsModifiersWithRMB() { let event = MouseEvent.stub(type: .rightMouseDown, buttonNumber: 1, modifiers: [.option, .shift]) XCTAssertEqual(transform(event), KeyboardGlyphCatalog.option + KeyboardGlyphCatalog.shift + "RMB") } diff --git a/Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventTransformerKeystrokeTests.swift b/Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventTransformerStandardKeyEventTests.swift similarity index 89% rename from Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventTransformerKeystrokeTests.swift rename to Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventTransformerStandardKeyEventTests.swift index c7823bfa..73b82281 100644 --- a/Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventTransformerKeystrokeTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Services/EventPipeline/EventTransformerStandardKeyEventTests.swift @@ -1,5 +1,5 @@ // -// EventTransformerKeystrokeTests.swift +// EventTransformerStandardKeyEventTests.swift // KeytyTests // // SPDX-FileCopyrightText: 2026 Serhii Bykov @@ -10,7 +10,7 @@ import Carbon import XCTest @testable import Keyty -final class EventTransformerKeystrokeTests: XCTestCase { +final class EventTransformerStandardKeyEventTests: XCTestCase { var transformer: EventTransformer! func transform(_ event: StandardKeyEvent) -> String { @@ -25,7 +25,7 @@ final class EventTransformerKeystrokeTests: XCTestCase { // MARK: - Modifier Glyphs -extension EventTransformerKeystrokeTests { +extension EventTransformerStandardKeyEventTests { /// The order the glyphs are expected in, written out independently of the /// `canonicalDisplayOrder` the transformer reads, so a reordering there fails here. private static let expectedGlyphOrder: [(flag: NSEvent.ModifierFlags, glyph: String)] = [ @@ -55,7 +55,7 @@ extension EventTransformerKeystrokeTests { .joined() } - func test_modifierGlyphsPrecedeTheLegendInCanonicalOrder() { + func testModifierGlyphsPrecedeTheLegendInCanonicalOrder() { for (modifiers, name) in Self.allModifierCombinations { let keystroke = StandardKeyEvent.stub(keyCode: .digit7, modifiers: modifiers) @@ -68,7 +68,7 @@ extension EventTransformerKeystrokeTests { } /// A digit has no distinct uppercase form, so only letters show the casing rule. - func test_letterLegendIsUppercasedOnlyWhenModified() { + func testLetterLegendIsUppercasedOnlyWhenModified() { for (modifiers, name) in Self.allModifierCombinations { let keystroke = StandardKeyEvent.stub(keyCode: .a, modifiers: modifiers) let expectedLegend = modifiers.isEmpty ? "a" : "A" @@ -83,7 +83,7 @@ extension EventTransformerKeystrokeTests { /// Option produces a dead key or an alternate character, but the legend stays /// the one printed on the key. - func test_optionLegendIgnoresTheAlternateCharacter() { + func testOptionLegendIgnoresTheAlternateCharacter() { let cases: [(KeyboardKeyCode, String)] = [(.u, "U"), (.e, "E"), (.grave, "`")] for (keyCode, expectedLegend) in cases { @@ -100,9 +100,9 @@ extension EventTransformerKeystrokeTests { // MARK: - Legend Casing -extension EventTransformerKeystrokeTests { +extension EventTransformerStandardKeyEventTests { /// German ß uppercases to "SS", which would misreport the key's legend. - func test_legendThatExpandsWhenUppercasedIsLeftAlone() throws { + func testLegendThatExpandsWhenUppercasedIsLeftAlone() throws { let german = EventTransformer(keyboardLayout: try TISInputSource.german()) let keystroke = StandardKeyEvent.stub(keyCode: .minus, modifiers: .recorded(.command)) @@ -112,8 +112,8 @@ extension EventTransformerKeystrokeTests { // MARK: - Keys Named by the Layout -extension EventTransformerKeystrokeTests { - func test_editingKeysUseDistinctSymbols() { +extension EventTransformerStandardKeyEventTests { + func testEditingKeysUseDistinctSymbols() { let cases: [(KeyboardKeyCode, String)] = [ (.tab, KeyboardGlyphCatalog.tab), (.returnKey, UnicodeToken.returnKey.string), @@ -128,7 +128,7 @@ extension EventTransformerKeystrokeTests { } } - func test_arrowKeysUseFilledTriangleSymbols() { + func testArrowKeysUseFilledTriangleSymbols() { let cases: [(KeyboardKeyCode, String)] = [ (.leftArrow, UnicodeToken.leftArrow.string), (.upArrow, UnicodeToken.upArrow.string), @@ -142,7 +142,7 @@ extension EventTransformerKeystrokeTests { } } - func test_modifiersPrefixASpecialKeySymbol() { + func testModifiersPrefixASpecialKeySymbol() { let character = String.functionKey(NSUpArrowFunctionKey) let keystroke = StandardKeyEvent.stub( keyCode: .upArrow, @@ -157,7 +157,7 @@ extension EventTransformerKeystrokeTests { ) } - func test_systemKeysUseTheirOwnNames() { + func testSystemKeysUseTheirOwnNames() { let cases: [(KeyboardKeyCode, String)] = [ (.brightnessDown, "dimmer"), (.brightnessUp, "brighter"), @@ -171,7 +171,7 @@ extension EventTransformerKeystrokeTests { } } - func test_japaneseInputKeysUseTheirOwnLabels() { + func testJapaneseInputKeysUseTheirOwnLabels() { let cases: [(KeyboardKeyCode, KeyboardSpecialKey)] = [(.eisu, .eisu), (.kana, .kana)] for (keyCode, specialKey) in cases { @@ -183,10 +183,10 @@ extension EventTransformerKeystrokeTests { // MARK: - Keys Named by the Event -extension EventTransformerKeystrokeTests { +extension EventTransformerStandardKeyEventTests { /// macOS reuses the Help key code for Insert on many external keyboards, so /// these three keys are told apart by the event's characters, not the key code. - func test_helpKeyCodeResolvesFromTheEventCharacters() { + func testHelpKeyCodeResolvesFromTheEventCharacters() { let insertCharacter = String.functionKey(NSInsertFunctionKey) let insert = StandardKeyEvent.stub( keyCode: .help, diff --git a/Apps/Keyty/Tests/KeytyTests/Support/Extensions/AppKit/NSBezierPathCGPathTests.swift b/Apps/Keyty/Tests/KeytyTests/Support/Extensions/AppKit/NSBezierPathCGPathTests.swift index 8612bd9e..bf2fd07d 100644 --- a/Apps/Keyty/Tests/KeytyTests/Support/Extensions/AppKit/NSBezierPathCGPathTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Support/Extensions/AppKit/NSBezierPathCGPathTests.swift @@ -11,14 +11,14 @@ import XCTest @testable import Keyty final class NSBezierPathCGPathTests: XCTestCase { - func test_cgPath_emptyPathProducesEmptyCGPath() { + func testCGPathEmptyPathProducesEmptyCGPath() { let path = NSBezierPath() XCTAssertTrue(path.cgPath.isEmpty) XCTAssertEqual(path.cgPath.pathElements.map(\.type), []) } - func test_cgPath_linePathPreservesElementTypes() { + func testCGPathLinePathPreservesElementTypes() { let path = NSBezierPath() path.move(to: NSPoint(x: 10, y: 10)) path.line(to: NSPoint(x: 30, y: 10)) @@ -38,7 +38,7 @@ final class NSBezierPathCGPathTests: XCTestCase { ) } - func test_cgPath_curvePathPreservesCurveElement() { + func testCGPathCurvePathPreservesCurveElement() { let path = NSBezierPath() path.move(to: NSPoint(x: 0, y: 0)) path.curve( @@ -58,7 +58,7 @@ final class NSBezierPathCGPathTests: XCTestCase { XCTAssertEqual(elements[1].points.count, 3) } - func test_cgPath_boundingBoxMatchesBezierPathBounds() { + func testCGPathBoundingBoxMatchesBezierPathBounds() { let path = NSBezierPath( roundedRect: NSRect(x: 12, y: 18, width: 44, height: 28), xRadius: 14, diff --git a/Apps/Keyty/Tests/KeytyTests/Support/Extensions/AppKit/NSColorHexTests.swift b/Apps/Keyty/Tests/KeytyTests/Support/Extensions/AppKit/NSColorHexTests.swift index 07a6f3db..46467ce5 100644 --- a/Apps/Keyty/Tests/KeytyTests/Support/Extensions/AppKit/NSColorHexTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Support/Extensions/AppKit/NSColorHexTests.swift @@ -13,32 +13,32 @@ final class NSColorHexTests: XCTestCase { // MARK: - NSColor → hex string - func test_hexString_white() { + func testHexStringWhite() { XCTAssertEqual(NSColor.white.hexString, "#FFFFFFFF") } - func test_hexString_black() { + func testHexStringBlack() { XCTAssertEqual(NSColor.black.hexString, "#000000FF") } - func test_hexString_red() { + func testHexStringRed() { let color = NSColor(srgbRed: 1, green: 0, blue: 0, alpha: 1) XCTAssertEqual(color.hexString, "#FF0000FF") } - func test_hexString_semiTransparent() { + func testHexStringSemiTransparent() { let color = NSColor(srgbRed: 0, green: 0, blue: 0, alpha: 0.5) XCTAssertEqual(color.hexString, "#00000080") } - func test_hexString_fullyTransparent() { + func testHexStringFullyTransparent() { let color = NSColor(srgbRed: 1, green: 1, blue: 1, alpha: 0) XCTAssertEqual(color.hexString, "#FFFFFF00") } // MARK: - hex string → NSColor - func test_init_eightCharHex() { + func testInitEightCharHex() { let color = NSColor(hexString: "#FF0000FF") XCTAssertNotNil(color) XCTAssertEqual(color?.redComponent ?? 0, 1.0, accuracy: 0.01) @@ -47,46 +47,46 @@ final class NSColorHexTests: XCTestCase { XCTAssertEqual(color?.alphaComponent ?? 0, 1.0, accuracy: 0.01) } - func test_init_sixCharHex_defaultsAlphaToOne() { + func testInitSixCharHexDefaultsAlphaToOne() { let color = NSColor(hexString: "#FF0000") XCTAssertNotNil(color) XCTAssertEqual(color?.alphaComponent ?? 0, 1.0, accuracy: 0.01) } - func test_init_withoutHashPrefix() { + func testInitWithoutHashPrefix() { let color = NSColor(hexString: "FF0000FF") XCTAssertNotNil(color) XCTAssertEqual(color?.redComponent ?? 0, 1.0, accuracy: 0.01) } - func test_init_lowercaseHex() { + func testInitLowercaseHex() { let color = NSColor(hexString: "#ff0000ff") XCTAssertNotNil(color) XCTAssertEqual(color?.redComponent ?? 0, 1.0, accuracy: 0.01) } - func test_init_withLeadingAndTrailingWhitespace() { + func testInitWithLeadingAndTrailingWhitespace() { let color = NSColor(hexString: " #FF0000FF ") XCTAssertNotNil(color) } - func test_init_invalidLength_returnsNil() { + func testInitInvalidLengthReturnsNil() { XCTAssertNil(NSColor(hexString: "#FFF")) XCTAssertNil(NSColor(hexString: "#FFFFF")) XCTAssertNil(NSColor(hexString: "#FFFFFFFFF")) } - func test_init_nonHexCharacters_returnsNil() { + func testInitNonHexCharactersReturnsNil() { XCTAssertNil(NSColor(hexString: "#GGHHIIJJ")) } - func test_init_emptyString_returnsNil() { + func testInitEmptyStringReturnsNil() { XCTAssertNil(NSColor(hexString: "")) } // MARK: - Round-trip - func test_roundTrip_preservesColor() { + func testRoundTripPreservesColor() { let original = NSColor(srgbRed: 0.2, green: 0.5, blue: 0.8, alpha: 0.75) let recovered = NSColor(hexString: original.hexString) XCTAssertNotNil(recovered) @@ -96,12 +96,12 @@ final class NSColorHexTests: XCTestCase { XCTAssertEqual(original.alphaComponent, recovered!.alphaComponent, accuracy: 0.01) } - func test_roundTrip_black() { + func testRoundTripBlack() { let original = NSColor.black XCTAssertEqual(NSColor(hexString: original.hexString)?.hexString, original.hexString) } - func test_roundTrip_white() { + func testRoundTripWhite() { let original = NSColor.white XCTAssertEqual(NSColor(hexString: original.hexString)?.hexString, original.hexString) } diff --git a/Apps/Keyty/Tests/KeytyTests/Support/Extensions/AppKit/NSPointOffsetTests.swift b/Apps/Keyty/Tests/KeytyTests/Support/Extensions/AppKit/NSPointOffsetTests.swift index 1955018d..f0ec5540 100644 --- a/Apps/Keyty/Tests/KeytyTests/Support/Extensions/AppKit/NSPointOffsetTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Support/Extensions/AppKit/NSPointOffsetTests.swift @@ -10,19 +10,19 @@ import XCTest @testable import Keyty final class NSPointOffsetTests: XCTestCase { - func test_offsetBy_addsPositiveOffsets() { + func testOffsetByAddsPositiveOffsets() { let point = NSPoint(x: 10, y: 20) XCTAssertEqual(point.offsetBy(dx: 3, dy: 4), NSPoint(x: 13, y: 24)) } - func test_offsetBy_addsNegativeOffsets() { + func testOffsetByAddsNegativeOffsets() { let point = NSPoint(x: 10, y: 20) XCTAssertEqual(point.offsetBy(dx: -3, dy: -4), NSPoint(x: 7, y: 16)) } - func test_offsetBy_doesNotMutateOriginalPoint() { + func testOffsetByDoesNotMutateOriginalPoint() { let point = NSPoint(x: 10, y: 20) _ = point.offsetBy(dx: 3, dy: 4) diff --git a/Apps/Keyty/Tests/KeytyTests/Support/Extensions/CoreGraphics/CGRectDistanceTests.swift b/Apps/Keyty/Tests/KeytyTests/Support/Extensions/CoreGraphics/CGRectDistanceTests.swift index 8ad443bc..23305732 100644 --- a/Apps/Keyty/Tests/KeytyTests/Support/Extensions/CoreGraphics/CGRectDistanceTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Support/Extensions/CoreGraphics/CGRectDistanceTests.swift @@ -11,25 +11,25 @@ import XCTest @testable import Keyty final class CGRectDistanceTests: XCTestCase { - func test_squaredDistanceToPoint_returnsZeroForPointInsideRect() { + func testSquaredDistanceToPointReturnsZeroForPointInsideRect() { let rect = CGRect(x: 10, y: 20, width: 100, height: 50) XCTAssertEqual(rect.squaredDistance(to: CGPoint(x: 40, y: 30)), 0) } - func test_squaredDistanceToPoint_usesHorizontalDistanceOutsideRect() { + func testSquaredDistanceToPointUsesHorizontalDistanceOutsideRect() { let rect = CGRect(x: 10, y: 20, width: 100, height: 50) XCTAssertEqual(rect.squaredDistance(to: CGPoint(x: 4, y: 30)), 36) } - func test_squaredDistanceToPoint_usesVerticalDistanceOutsideRect() { + func testSquaredDistanceToPointUsesVerticalDistanceOutsideRect() { let rect = CGRect(x: 10, y: 20, width: 100, height: 50) XCTAssertEqual(rect.squaredDistance(to: CGPoint(x: 40, y: 75)), 25) } - func test_squaredDistanceToPoint_usesDiagonalDistanceOutsideRect() { + func testSquaredDistanceToPointUsesDiagonalDistanceOutsideRect() { let rect = CGRect(x: 10, y: 20, width: 100, height: 50) XCTAssertEqual(rect.squaredDistance(to: CGPoint(x: 4, y: 75)), 61) diff --git a/Apps/Keyty/Tests/KeytyTests/Support/Extensions/CoreGraphics/CGRectNormalizedPointTests.swift b/Apps/Keyty/Tests/KeytyTests/Support/Extensions/CoreGraphics/CGRectNormalizedPointTests.swift index 32eaa7b2..3f89353d 100644 --- a/Apps/Keyty/Tests/KeytyTests/Support/Extensions/CoreGraphics/CGRectNormalizedPointTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Support/Extensions/CoreGraphics/CGRectNormalizedPointTests.swift @@ -11,7 +11,7 @@ import XCTest @testable import Keyty final class CGRectNormalizedPointTests: XCTestCase { - func test_normalizedPoint_returnsPointRelativeToRect() { + func testNormalizedPointReturnsPointRelativeToRect() { let rect = CGRect(x: 10, y: 20, width: 100, height: 50) let point = rect.normalizedPoint(for: CGPoint(x: 35, y: 45)) @@ -19,7 +19,7 @@ final class CGRectNormalizedPointTests: XCTestCase { XCTAssertEqual(point.y, 0.5, accuracy: 0.0001) } - func test_normalizedPoint_clampsPointBelowRect() { + func testNormalizedPointClampsPointBelowRect() { let rect = CGRect(x: 10, y: 20, width: 100, height: 50) let point = rect.normalizedPoint(for: CGPoint(x: 0, y: 10)) @@ -27,7 +27,7 @@ final class CGRectNormalizedPointTests: XCTestCase { XCTAssertEqual(point.y, 0, accuracy: 0.0001) } - func test_normalizedPoint_clampsPointAboveRect() { + func testNormalizedPointClampsPointAboveRect() { let rect = CGRect(x: 10, y: 20, width: 100, height: 50) let point = rect.normalizedPoint(for: CGPoint(x: 120, y: 80)) @@ -35,7 +35,7 @@ final class CGRectNormalizedPointTests: XCTestCase { XCTAssertEqual(point.y, 1, accuracy: 0.0001) } - func test_normalizedPoint_usesOneForEmptyDimensions() { + func testNormalizedPointUsesOneForEmptyDimensions() { let rect = CGRect(x: 10, y: 20, width: 0, height: 0) let point = rect.normalizedPoint(for: CGPoint(x: 10.5, y: 20.25)) diff --git a/Apps/Keyty/Tests/KeytyTests/Support/Extensions/Foundation/CGFloatClampTests.swift b/Apps/Keyty/Tests/KeytyTests/Support/Extensions/Foundation/CGFloatClampTests.swift index 8803136c..82941020 100644 --- a/Apps/Keyty/Tests/KeytyTests/Support/Extensions/Foundation/CGFloatClampTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Support/Extensions/Foundation/CGFloatClampTests.swift @@ -11,19 +11,19 @@ import XCTest @testable import Keyty final class CGFloatClampTests: XCTestCase { - func test_clampedToRange_keepsValueInsideRange() { + func testClampedToRangeKeepsValueInsideRange() { XCTAssertEqual(CGFloat(4).clamped(to: 1...8), 4) } - func test_clampedToRange_limitsValueBelowRange() { + func testClampedToRangeLimitsValueBelowRange() { XCTAssertEqual(CGFloat(-2).clamped(to: 1...8), 1) } - func test_clampedToRange_limitsValueAboveRange() { + func testClampedToRangeLimitsValueAboveRange() { XCTAssertEqual(CGFloat(10).clamped(to: 1...8), 8) } - func test_clampedMinimumMaximum_returnsMinimumWhenMaximumIsBelowMinimum() { + func testClampedMinimumMaximumReturnsMinimumWhenMaximumIsBelowMinimum() { XCTAssertEqual(CGFloat(4).clamped(minimum: 8, maximum: 1), 8) } } diff --git a/Apps/Keyty/Tests/KeytyTests/Support/Extensions/Foundation/CGFloatSuperellipseTests.swift b/Apps/Keyty/Tests/KeytyTests/Support/Extensions/Foundation/CGFloatSuperellipseTests.swift index 9624d663..97c10c0c 100644 --- a/Apps/Keyty/Tests/KeytyTests/Support/Extensions/Foundation/CGFloatSuperellipseTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Support/Extensions/Foundation/CGFloatSuperellipseTests.swift @@ -13,21 +13,21 @@ final class CGFloatSuperellipseTests: XCTestCase { private let exponent: CGFloat = 4.0 private let accuracy: CGFloat = 1e-9 - func test_zero_returnsZero() { + func testZeroReturnsZero() { XCTAssertEqual(CGFloat(0).signedSuperellipseComponent(exponent: exponent), 0) } - func test_extremes_areUnchanged() { + func testExtremesAreUnchanged() { XCTAssertEqual(CGFloat(1).signedSuperellipseComponent(exponent: exponent), 1, accuracy: accuracy) XCTAssertEqual(CGFloat(-1).signedSuperellipseComponent(exponent: exponent), -1, accuracy: accuracy) } - func test_preservesSign() { + func testPreservesSign() { XCTAssertLessThan(CGFloat(-0.5).signedSuperellipseComponent(exponent: exponent), 0) XCTAssertGreaterThan(CGFloat(0.5).signedSuperellipseComponent(exponent: exponent), 0) } - func test_isSymmetricAcrossZero() { + func testIsSymmetricAcrossZero() { let value: CGFloat = 0.5 let positive = value.signedSuperellipseComponent(exponent: exponent) let negative = (-value).signedSuperellipseComponent(exponent: exponent) @@ -35,7 +35,7 @@ final class CGFloatSuperellipseTests: XCTestCase { XCTAssertEqual(positive, -negative, accuracy: accuracy) } - func test_matchesExpectedMagnitude() { + func testMatchesExpectedMagnitude() { // |0.5|^(2/4) == sqrt(0.5) XCTAssertEqual( CGFloat(0.5).signedSuperellipseComponent(exponent: exponent), @@ -44,7 +44,7 @@ final class CGFloatSuperellipseTests: XCTestCase { ) } - func test_exponentTwo_isIdentityMagnitude() { + func testExponentTwoIsIdentityMagnitude() { // exponent == 2 → |value|^1, i.e. the unit circle (identity). XCTAssertEqual(CGFloat(0.5).signedSuperellipseComponent(exponent: 2), 0.5, accuracy: accuracy) } diff --git a/Apps/Keyty/Tests/KeytyTests/Support/Extensions/Foundation/TimeIntervalNanosecondsTests.swift b/Apps/Keyty/Tests/KeytyTests/Support/Extensions/Foundation/TimeIntervalNanosecondsTests.swift index d7fc4040..ae9a2ce3 100644 --- a/Apps/Keyty/Tests/KeytyTests/Support/Extensions/Foundation/TimeIntervalNanosecondsTests.swift +++ b/Apps/Keyty/Tests/KeytyTests/Support/Extensions/Foundation/TimeIntervalNanosecondsTests.swift @@ -10,15 +10,15 @@ import XCTest @testable import Keyty final class TimeIntervalNanosecondsTests: XCTestCase { - func test_nanoseconds_convertsSecondsToNanoseconds() { + func testNanosecondsConvertsSecondsToNanoseconds() { XCTAssertEqual(TimeInterval(1).nanoseconds, 1_000_000_000) } - func test_nanoseconds_convertsFractionalSecondsToNanoseconds() { + func testNanosecondsConvertsFractionalSecondsToNanoseconds() { XCTAssertEqual(TimeInterval(0.82).nanoseconds, 820_000_000) } - func test_nanoseconds_roundsToNearestNanosecond() { + func testNanosecondsRoundsToNearestNanosecond() { XCTAssertEqual(TimeInterval(0.000_000_001_5).nanoseconds, 2) } }