diff --git a/McBopomofo.xcodeproj/project.pbxproj b/McBopomofo.xcodeproj/project.pbxproj index 8dd0e91f..59c63733 100644 --- a/McBopomofo.xcodeproj/project.pbxproj +++ b/McBopomofo.xcodeproj/project.pbxproj @@ -60,6 +60,7 @@ D485D3C02796CE3200657FF3 /* VersionUpdateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D485D3BF2796CE3200657FF3 /* VersionUpdateTests.swift */; }; D4A13D5A27A59F0B003BE359 /* InputMethodController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4A13D5927A59D5C003BE359 /* InputMethodController.swift */; }; D4C9CAB127AAC9690058DFEA /* NSStringUtils in Frameworks */ = {isa = PBXBuildFile; productRef = D4C9CAB027AAC9690058DFEA /* NSStringUtils */; }; + D4A8E43627A9E982002F7A07 /* KeyHandlerPlainBopomofoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4A8E43527A9E982002F7A07 /* KeyHandlerPlainBopomofoTests.swift */; }; D4E33D8A27A838CF006DB1CF /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D4E33D8827A838CF006DB1CF /* Localizable.strings */; }; D4E33D8F27A838F0006DB1CF /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D4E33D8D27A838F0006DB1CF /* InfoPlist.strings */; }; D4E569DC27A34D0E00AC2CEF /* KeyHandler.mm in Sources */ = {isa = PBXBuildFile; fileRef = D4E569DB27A34CC100AC2CEF /* KeyHandler.mm */; }; @@ -186,6 +187,7 @@ D495583A27A5C6C4006ADE1C /* LanguageModelManager+Privates.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "LanguageModelManager+Privates.h"; sourceTree = ""; }; D4A13D5927A59D5C003BE359 /* InputMethodController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputMethodController.swift; sourceTree = ""; }; D4C9CAAF27AAC8EC0058DFEA /* NSStringUtils */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = NSStringUtils; path = Packages/NSStringUtils; sourceTree = ""; }; + D4A8E43527A9E982002F7A07 /* KeyHandlerPlainBopomofoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyHandlerPlainBopomofoTests.swift; sourceTree = ""; }; D4E33D8927A838CF006DB1CF /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; D4E33D8B27A838D5006DB1CF /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; D4E33D8C27A838D8006DB1CF /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; @@ -430,6 +432,7 @@ isa = PBXGroup; children = ( D47D73A327A5D43900255A50 /* KeyHandlerBopomofoTests.swift */, + D4A8E43527A9E982002F7A07 /* KeyHandlerPlainBopomofoTests.swift */, D485D3B82796A8A000657FF3 /* PreferencesTests.swift */, D485D3BF2796CE3200657FF3 /* VersionUpdateTests.swift */, ); @@ -683,6 +686,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D4A8E43627A9E982002F7A07 /* KeyHandlerPlainBopomofoTests.swift in Sources */, D47D73A427A5D43900255A50 /* KeyHandlerBopomofoTests.swift in Sources */, D485D3B92796A8A000657FF3 /* PreferencesTests.swift in Sources */, D485D3C02796CE3200657FF3 /* VersionUpdateTests.swift in Sources */, diff --git a/McBopomofoTests/KeyHandlerBopomofoTests.swift b/McBopomofoTests/KeyHandlerBopomofoTests.swift index e1ec55a8..89672300 100644 --- a/McBopomofoTests/KeyHandlerBopomofoTests.swift +++ b/McBopomofoTests/KeyHandlerBopomofoTests.swift @@ -18,12 +18,311 @@ class KeyHandlerBopomofoTests: XCTestCase { override func tearDownWithError() throws { } - func testPunctuationComma() { + func testIgnoreEmpty() { + let input = KeyHandlerInput(inputText: "", keyCode: 0, charCode: 0, flags: [], isVerticalMode: false) + var state: InputState = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result) + } + + func testIgnoreEnter() { + let input = KeyHandlerInput(inputText: " ", keyCode: KeyCode.enter.rawValue, charCode: 0, flags: [], isVerticalMode: false) + var state: InputState = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result) + } + + func testIgnoreUp() { + let input = KeyHandlerInput(inputText: " ", keyCode: KeyCode.up.rawValue, charCode: 0, flags: [], isVerticalMode: false) + var state: InputState = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result, "\(state)") + } + + func testIgnoreDown() { + let input = KeyHandlerInput(inputText: " ", keyCode: KeyCode.down.rawValue, charCode: 0, flags: [], isVerticalMode: false) + var state: InputState = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result, "\(state)") + } + + func testIgnoreLeft() { + let input = KeyHandlerInput(inputText: " ", keyCode: KeyCode.left.rawValue, charCode: 0, flags: [], isVerticalMode: false) + var state: InputState = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result) + } + + func testIgnoreRight() { + let input = KeyHandlerInput(inputText: " ", keyCode: KeyCode.right.rawValue, charCode: 0, flags: [], isVerticalMode: false) + var state: InputState = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result) + } + + func testIgnorePageUp() { + let input = KeyHandlerInput(inputText: " ", keyCode: KeyCode.pageUp.rawValue, charCode: 0, flags: [], isVerticalMode: false) + var state: InputState = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result) + } + + func testIgnorePageDown() { + let input = KeyHandlerInput(inputText: " ", keyCode: KeyCode.pageDown.rawValue, charCode: 0, flags: [], isVerticalMode: false) + var state: InputState = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result) + } + + func testIgnoreHome() { + let input = KeyHandlerInput(inputText: " ", keyCode: KeyCode.home.rawValue, charCode: 0, flags: [], isVerticalMode: false) + var state: InputState = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result) + } + + func testIgnoreEnd() { + let input = KeyHandlerInput(inputText: " ", keyCode: KeyCode.end.rawValue, charCode: 0, flags: [], isVerticalMode: false) + var state: InputState = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result) + } + + func testIgnoreDelete() { + let input = KeyHandlerInput(inputText: " ", keyCode: KeyCode.delete.rawValue, charCode: 0, flags: [], isVerticalMode: false) + var state: InputState = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result) + } + + func testIgnoreCommand() { + let input = KeyHandlerInput(inputText: "A", keyCode: 0, charCode: 0, flags: .command, isVerticalMode: false) + var state: InputState = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result) + } + + func testIgnoreOption() { + let input = KeyHandlerInput(inputText: "A", keyCode: 0, charCode: 0, flags: .option, isVerticalMode: false) + var state: InputState = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result) + } + + func testIgnoreNumericPad() { + let input = KeyHandlerInput(inputText: "A", keyCode: 0, charCode: 0, flags: .numericPad, isVerticalMode: false) + var state: InputState = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result) + } + + func testIgnoreCapslock() { + let input = KeyHandlerInput(inputText: "A", keyCode: 0, charCode: 0, flags: .capsLock, isVerticalMode: false) + var state: InputState = InputState.Empty() + let result = handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertFalse(result) + } + + func testCapslock() { + var input = KeyHandlerInput(inputText: "b", keyCode: 0, charCode: charCode("b"), flags: [], isVerticalMode: false) + var state: InputState = InputState.Empty() + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + var count = 0 + + input = KeyHandlerInput(inputText: "a", keyCode: 0, charCode: charCode("a"), flags: .capsLock, isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + if count == 1 { + state = newState + } + count += 1 + } errorCallback: { + } + XCTAssertTrue(state is InputState.Committing, "\(state)") + if let state = state as? InputState.Committing { + XCTAssertEqual(state.poppedText, "a") + } + } + + func testCapslockShift() { + var input = KeyHandlerInput(inputText: "b", keyCode: 0, charCode: charCode("b"), flags: [], isVerticalMode: false) + var state: InputState = InputState.Empty() + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + input = KeyHandlerInput(inputText: "a", keyCode: 0, charCode: charCode("a"), flags: [.capsLock, .shift], isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertTrue(state is InputState.Empty, "\(state)") + } + + func testisNumericPad() { + var input = KeyHandlerInput(inputText: "b", keyCode: 0, charCode: charCode("b"), flags: [], isVerticalMode: false) + var state: InputState = InputState.Empty() + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + input = KeyHandlerInput(inputText: "1", keyCode: 0, charCode: charCode("1"), flags: .numericPad, isVerticalMode: false) + var count = 0 + var empty: InputState = InputState.Empty() + var target: InputState = InputState.Empty() + handler.handle(input: input, state: state) { newState in + switch count { + case 0: + state = newState + case 1: + target = newState + case 2: + empty = newState + default: + break + } + count += 1 + + } errorCallback: { + } + XCTAssertEqual(count, 3) + XCTAssertTrue(state is InputState.Empty, "\(state)") + XCTAssertTrue(empty is InputState.Empty, "\(empty)") + XCTAssertTrue(target is InputState.Committing, "\(target)") + if let state = target as? InputState.Committing { + XCTAssertEqual(state.poppedText, "1") + } + } + + func testLetter() { + let input = KeyHandlerInput(inputText: "A", keyCode: 0, charCode: charCode("A"), flags: .shift, isVerticalMode: false) + var state: InputState = InputState.Empty() + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "a") + } + } + + func testPunctuationTable() { + let enabled = Preferences.halfWidthPunctuationEnabled + Preferences.halfWidthPunctuationEnabled = false + let input = KeyHandlerInput(inputText: "`", keyCode: 0, charCode: charCode("`"), flags: .shift, isVerticalMode: false) + var state: InputState = InputState.Empty() + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)") + if let state = state as? InputState.ChoosingCandidate { + XCTAssertTrue(state.candidates.contains(",")) + } + Preferences.halfWidthPunctuationEnabled = enabled + } + + func testIgnorePunctuationTable() { + let enabled = Preferences.halfWidthPunctuationEnabled + Preferences.halfWidthPunctuationEnabled = false + var state: InputState = InputState.Empty() + var input = KeyHandlerInput(inputText: "1", keyCode: 0, charCode: charCode("1"), flags: .shift, isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + input = KeyHandlerInput(inputText: "`", keyCode: 0, charCode: charCode("`"), flags: .shift, isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "ㄅ") + } + Preferences.halfWidthPunctuationEnabled = enabled + } + + + func testHalfPunctuationComma() { + let enabled = Preferences.halfWidthPunctuationEnabled + Preferences.halfWidthPunctuationEnabled = true let input = KeyHandlerInput(inputText: "<", keyCode: 0, charCode: charCode("<"), flags: .shift, isVerticalMode: false) var state: InputState = InputState.Empty() - handler.handle(input, state: state) { newState in + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, ",") + } + Preferences.halfWidthPunctuationEnabled = enabled + } + + + func testPunctuationComma() { + let enabled = Preferences.halfWidthPunctuationEnabled + Preferences.halfWidthPunctuationEnabled = false + let input = KeyHandlerInput(inputText: "<", keyCode: 0, charCode: charCode("<"), flags: .shift, isVerticalMode: false) + var state: InputState = InputState.Empty() + handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } @@ -31,14 +330,34 @@ class KeyHandlerBopomofoTests: XCTestCase { if let state = state as? InputState.Inputting { XCTAssertEqual(state.composingBuffer, ",") } + Preferences.halfWidthPunctuationEnabled = enabled + } + + func testHalfPunctuationPeriod() { + let enabled = Preferences.halfWidthPunctuationEnabled + Preferences.halfWidthPunctuationEnabled = true + let input = KeyHandlerInput(inputText: ">", keyCode: 0, charCode: charCode(">"), flags: .shift, isVerticalMode: false) + var state: InputState = InputState.Empty() + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, ".") + } + Preferences.halfWidthPunctuationEnabled = enabled } func testPunctuationPeriod() { + let enabled = Preferences.halfWidthPunctuationEnabled + Preferences.halfWidthPunctuationEnabled = false + let input = KeyHandlerInput(inputText: ">", keyCode: 0, charCode: charCode(">"), flags: .shift, isVerticalMode: false) var state: InputState = InputState.Empty() - handler.handle(input, state: state) { newState in + handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } @@ -46,16 +365,54 @@ class KeyHandlerBopomofoTests: XCTestCase { if let state = state as? InputState.Inputting { XCTAssertEqual(state.composingBuffer, "。") } + Preferences.halfWidthPunctuationEnabled = enabled + } + + func testCtrlPunctuationPeriod() { + let enabled = Preferences.halfWidthPunctuationEnabled + Preferences.halfWidthPunctuationEnabled = false + + let input = KeyHandlerInput(inputText: ".", keyCode: 0, charCode: charCode("."), flags: .control, isVerticalMode: false) + var state: InputState = InputState.Empty() + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "。") + } + Preferences.halfWidthPunctuationEnabled = enabled + } + + func testInvalidBpmf() { + var state: InputState = InputState.Empty() + let keys = Array("ni4").map { + String($0) + } + for key in keys { + let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "ㄙㄛˋ") + } } func testInputting() { var state: InputState = InputState.Empty() - let keys = Array("vul3a945j4up gj bj4z83").map { String($0) } + let keys = Array("vul3a945j4up gj bj4z83").map { + String($0) + } for key in keys { let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) - handler.handle(input, state: state) { newState in + handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } } @@ -67,12 +424,13 @@ class KeyHandlerBopomofoTests: XCTestCase { func testInputtingNihao() { var state: InputState = InputState.Empty() - let keys = Array("su3cl3").map { String($0) } + let keys = Array("su3cl3").map { + String($0) + } for key in keys { let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) - handler.handle(input, state: state) { newState in + handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } } @@ -84,12 +442,13 @@ class KeyHandlerBopomofoTests: XCTestCase { func testInputtingTianKong() { var state: InputState = InputState.Empty() - let keys = Array("wu0 dj/ ").map { String($0) } + let keys = Array("wu0 dj/ ").map { + String($0) + } for key in keys { let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) - handler.handle(input, state: state) { newState in + handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } } @@ -101,12 +460,13 @@ class KeyHandlerBopomofoTests: XCTestCase { func testCommittingNihao() { var state: InputState = InputState.Empty() - let keys = Array("su3cl3").map { String($0) } + let keys = Array("su3cl3").map { + String($0) + } for key in keys { let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) - handler.handle(input, state: state) { newState in + handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } } @@ -120,7 +480,7 @@ class KeyHandlerBopomofoTests: XCTestCase { var empty: InputState? var count = 0 - handler.handle(enter, state: state) { newState in + handler.handle(input: enter, state: state) { newState in switch count { case 0: committing = newState @@ -130,7 +490,6 @@ class KeyHandlerBopomofoTests: XCTestCase { break } count += 1 - } candidateSelectionCallback: { } errorCallback: { } @@ -143,12 +502,13 @@ class KeyHandlerBopomofoTests: XCTestCase { func testDelete() { var state: InputState = InputState.Empty() - let keys = Array("su3cl3").map { String($0) } + let keys = Array("su3cl3").map { + String($0) + } for key in keys { let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) - handler.handle(input, state: state) { newState in + handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } } @@ -162,9 +522,8 @@ class KeyHandlerBopomofoTests: XCTestCase { let delete = KeyHandlerInput(inputText: " ", keyCode: KeyCode.delete.rawValue, charCode: 0, flags: [], isVerticalMode: false) var errorCalled = false - handler.handle(left, state: state) { newState in + handler.handle(input: left, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } @@ -174,9 +533,8 @@ class KeyHandlerBopomofoTests: XCTestCase { XCTAssertEqual(state.cursorIndex, 1) } - handler.handle(delete, state: state) { newState in + handler.handle(input: delete, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } @@ -186,9 +544,8 @@ class KeyHandlerBopomofoTests: XCTestCase { XCTAssertEqual(state.cursorIndex, 1) } - handler.handle(delete, state: state) { newState in + handler.handle(input: delete, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { errorCalled = true } @@ -202,9 +559,8 @@ class KeyHandlerBopomofoTests: XCTestCase { errorCalled = false - handler.handle(left, state: state) { newState in + handler.handle(input: left, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } @@ -214,9 +570,8 @@ class KeyHandlerBopomofoTests: XCTestCase { XCTAssertEqual(state.cursorIndex, 0) } - handler.handle(delete, state: state) { newState in + handler.handle(input: delete, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } @@ -225,12 +580,13 @@ class KeyHandlerBopomofoTests: XCTestCase { func testBackspace() { var state: InputState = InputState.Empty() - let keys = Array("su3cl3").map { String($0) } + let keys = Array("su3cl3").map { + String($0) + } for key in keys { let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) - handler.handle(input, state: state) { newState in + handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } } @@ -242,9 +598,8 @@ class KeyHandlerBopomofoTests: XCTestCase { let backspace = KeyHandlerInput(inputText: " ", keyCode: 0, charCode: 8, flags: [], isVerticalMode: false) - handler.handle(backspace, state: state) { newState in + handler.handle(input: backspace, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } @@ -254,9 +609,8 @@ class KeyHandlerBopomofoTests: XCTestCase { XCTAssertEqual(state.cursorIndex, 1) } - handler.handle(backspace, state: state) { newState in + handler.handle(input: backspace, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } @@ -265,12 +619,13 @@ class KeyHandlerBopomofoTests: XCTestCase { func testCursor() { var state: InputState = InputState.Empty() - let keys = Array("su3cl3").map { String($0) } + let keys = Array("su3cl3").map { + String($0) + } for key in keys { let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) - handler.handle(input, state: state) { newState in + handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } } @@ -285,9 +640,8 @@ class KeyHandlerBopomofoTests: XCTestCase { var errorCalled = false - handler.handle(left, state: state) { newState in + handler.handle(input: left, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } XCTAssertTrue(state is InputState.Inputting, "\(state)") @@ -296,9 +650,8 @@ class KeyHandlerBopomofoTests: XCTestCase { XCTAssertEqual(state.cursorIndex, 1) } - handler.handle(left, state: state) { newState in + handler.handle(input: left, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } XCTAssertTrue(state is InputState.Inputting, "\(state)") @@ -307,9 +660,8 @@ class KeyHandlerBopomofoTests: XCTestCase { XCTAssertEqual(state.cursorIndex, 0) } - handler.handle(left, state: state) { newState in + handler.handle(input: left, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { errorCalled = true } @@ -320,9 +672,8 @@ class KeyHandlerBopomofoTests: XCTestCase { } XCTAssertTrue(errorCalled) - handler.handle(right, state: state) { newState in + handler.handle(input: right, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } XCTAssertTrue(state is InputState.Inputting, "\(state)") @@ -331,9 +682,8 @@ class KeyHandlerBopomofoTests: XCTestCase { XCTAssertEqual(state.cursorIndex, 1) } - handler.handle(right, state: state) { newState in + handler.handle(input: right, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } XCTAssertTrue(state is InputState.Inputting, "\(state)") @@ -343,9 +693,8 @@ class KeyHandlerBopomofoTests: XCTestCase { } errorCalled = false - handler.handle(right, state: state) { newState in + handler.handle(input: right, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { errorCalled = true } @@ -359,12 +708,13 @@ class KeyHandlerBopomofoTests: XCTestCase { func testCandidateWithDown() { var state: InputState = InputState.Empty() - let keys = Array("su3").map { String($0) } + let keys = Array("su3").map { + String($0) + } for key in keys { let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) - handler.handle(input, state: state) { newState in + handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } } @@ -374,10 +724,9 @@ class KeyHandlerBopomofoTests: XCTestCase { XCTAssertEqual(state.cursorIndex, 1) } - let space = KeyHandlerInput(inputText: " ", keyCode: KeyCode.down.rawValue, charCode: 0, flags: [], isVerticalMode: false) - handler.handle(space, state: state) { newState in + let down = KeyHandlerInput(inputText: " ", keyCode: KeyCode.down.rawValue, charCode: 0, flags: [], isVerticalMode: false) + handler.handle(input: down, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } @@ -390,14 +739,139 @@ class KeyHandlerBopomofoTests: XCTestCase { } } - func testHomeAndEnd() { + func testCandidateWithSpace() { + let enabled = Preferences.chooseCandidateUsingSpace + Preferences.chooseCandidateUsingSpace = true var state: InputState = InputState.Empty() - let keys = Array("su3cl3").map { String($0) } + let keys = Array("su3").map { + String($0) + } for key in keys { let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) - handler.handle(input, state: state) { newState in + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你") + XCTAssertEqual(state.cursorIndex, 1) + } + + let space = KeyHandlerInput(inputText: " ", keyCode: 0, charCode: 32, flags: [], isVerticalMode: false) + handler.handle(input: space, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)") + if let state = state as? InputState.ChoosingCandidate { + XCTAssertEqual(state.composingBuffer, "你") + XCTAssertEqual(state.cursorIndex, 1) + let candidates = state.candidates + XCTAssertTrue(candidates.contains("你")) + } + Preferences.chooseCandidateUsingSpace = enabled + } + + func testInputSpace() { + let enabled = Preferences.chooseCandidateUsingSpace + Preferences.chooseCandidateUsingSpace = false + var state: InputState = InputState.Empty() + let keys = Array("su3").map { + String($0) + } + for key in keys { + let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你") + } + + var count = 0 + var target: InputState = InputState.Empty() + var empty: InputState = InputState.Empty() + + let input = KeyHandlerInput(inputText: " ", keyCode: 0, charCode: 32, flags: [], isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + switch count { + case 0: + state = newState + case 1: + target = newState + case 2: + empty = newState + default: + break + } + count += 1 + } errorCallback: { + } + + XCTAssertEqual(count, 3) + XCTAssertTrue(state is InputState.Committing, "\(state)") + if let state = state as? InputState.Committing { + XCTAssertEqual(state.poppedText, "你") + } + XCTAssertTrue(target is InputState.Committing, "\(target)") + if let target = target as? InputState.Committing { + XCTAssertEqual(target.poppedText, " ") + } + XCTAssertTrue(empty is InputState.Empty, "\(empty)") + Preferences.chooseCandidateUsingSpace = enabled + } + + func testInputSpaceInBetween() { + let enabled = Preferences.chooseCandidateUsingSpace + Preferences.chooseCandidateUsingSpace = false + var state: InputState = InputState.Empty() + let keys = Array("su3cl3").map { + String($0) + } + for key in keys { + let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + } + + var input = KeyHandlerInput(inputText: " ", keyCode: KeyCode.left.rawValue, charCode: 0, flags: [], isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + input = KeyHandlerInput(inputText: " ", keyCode: 0, charCode: 32, flags: [], isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你 好") + } + Preferences.chooseCandidateUsingSpace = enabled + } + + func testHomeAndEnd() { + var state: InputState = InputState.Empty() + let keys = Array("su3cl3").map { + String($0) + } + for key in keys { + let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) + handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } } @@ -410,9 +884,8 @@ class KeyHandlerBopomofoTests: XCTestCase { let home = KeyHandlerInput(inputText: " ", keyCode: KeyCode.home.rawValue, charCode: 0, flags: [], isVerticalMode: false) let end = KeyHandlerInput(inputText: " ", keyCode: KeyCode.end.rawValue, charCode: 0, flags: [], isVerticalMode: false) - handler.handle(home, state: state) { newState in + handler.handle(input: home, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } @@ -422,9 +895,8 @@ class KeyHandlerBopomofoTests: XCTestCase { XCTAssertEqual(state.cursorIndex, 0) } - handler.handle(end, state: state) { newState in + handler.handle(input: end, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } @@ -434,4 +906,246 @@ class KeyHandlerBopomofoTests: XCTestCase { } } + func testMarkingLeft() { + var state: InputState = InputState.Empty() + let keys = Array("su3cl3").map { + String($0) + } + for key in keys { + let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 2) + } + + var input = KeyHandlerInput(inputText: " ", keyCode: KeyCode.left.rawValue, charCode: 0, flags: .shift, isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Marking, "\(state)") + if let state = state as? InputState.Marking { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 2) + XCTAssertEqual(state.markerIndex, 1) + XCTAssertEqual(state.markedRange, NSRange(location: 1, length: 1)) + } + + input = KeyHandlerInput(inputText: " ", keyCode: KeyCode.left.rawValue, charCode: 0, flags: .shift, isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Marking, "\(state)") + if let state = state as? InputState.Marking { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 2) + XCTAssertEqual(state.markerIndex, 0) + XCTAssertEqual(state.markedRange, NSRange(location: 0, length: 2)) + } + } + + func testMarkingRight() { + var state: InputState = InputState.Empty() + let keys = Array("su3cl3").map { + String($0) + } + for key in keys { + let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 2) + } + + let left = KeyHandlerInput(inputText: " ", keyCode: KeyCode.left.rawValue, charCode: 0, flags: [], isVerticalMode: false) + handler.handle(input: left, state: state) { newState in + state = newState + } errorCallback: { + } + handler.handle(input: left, state: state) { newState in + state = newState + } errorCallback: { + } + + let input = KeyHandlerInput(inputText: " ", keyCode: KeyCode.right.rawValue, charCode: 0, flags: .shift, isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Marking, "\(state)") + if let state = state as? InputState.Marking { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 0) + XCTAssertEqual(state.markerIndex, 1) + XCTAssertEqual(state.markedRange, NSRange(location: 0, length: 1)) + } + + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Marking, "\(state)") + if let state = state as? InputState.Marking { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 0) + XCTAssertEqual(state.markerIndex, 2) + XCTAssertEqual(state.markedRange, NSRange(location: 0, length: 2)) + } + } + + func testCancelMarking() { + var state: InputState = InputState.Empty() + let keys = Array("su3cl3").map { + String($0) + } + for key in keys { + let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 2) + } + + var input = KeyHandlerInput(inputText: " ", keyCode: KeyCode.left.rawValue, charCode: 0, flags: .shift, isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Marking, "\(state)") + if let state = state as? InputState.Marking { + XCTAssertEqual(state.composingBuffer, "你好") + XCTAssertEqual(state.cursorIndex, 2) + XCTAssertEqual(state.markerIndex, 1) + XCTAssertEqual(state.markedRange, NSRange(location: 1, length: 1)) + } + + input = KeyHandlerInput(inputText: "1", keyCode: 0, charCode: charCode("1"), flags: [], isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你好ㄅ") + } + } + + func testEscToClearReadingAndGoToEmpty() { + let enabled = Preferences.escToCleanInputBuffer + Preferences.escToCleanInputBuffer = false + var state: InputState = InputState.Empty() + let keys = Array("su").map { + String($0) + } + for key in keys { + let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "ㄋㄧ") + XCTAssertEqual(state.cursorIndex, 2) + } + + let input = KeyHandlerInput(inputText: " ", keyCode: 0, charCode: 27, flags: [], isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Empty, "\(state)") + Preferences.escToCleanInputBuffer = enabled + } + + func testEscToClearReadingAndGoToInputting() { + let enabled = Preferences.escToCleanInputBuffer + Preferences.escToCleanInputBuffer = false + var state: InputState = InputState.Empty() + let keys = Array("su3cl").map { + String($0) + } + for key in keys { + let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你ㄏㄠ") + XCTAssertEqual(state.cursorIndex, 3) + } + + let input = KeyHandlerInput(inputText: " ", keyCode: 0, charCode: 27, flags: [], isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你") + XCTAssertEqual(state.cursorIndex, 1) + } + Preferences.escToCleanInputBuffer = enabled + } + + + func testEscToClearAll() { + let enabled = Preferences.escToCleanInputBuffer + Preferences.escToCleanInputBuffer = true + var state: InputState = InputState.Empty() + let keys = Array("su3cl").map { + String($0) + } + for key in keys { + let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "你ㄏㄠ") + XCTAssertEqual(state.cursorIndex, 3) + } + + let input = KeyHandlerInput(inputText: " ", keyCode: 0, charCode: 27, flags: [], isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.EmptyIgnoringPreviousState, "\(state)") + Preferences.escToCleanInputBuffer = enabled + } + } diff --git a/McBopomofoTests/KeyHandlerPlainBopomofoTests.swift b/McBopomofoTests/KeyHandlerPlainBopomofoTests.swift new file mode 100644 index 00000000..34d59318 --- /dev/null +++ b/McBopomofoTests/KeyHandlerPlainBopomofoTests.swift @@ -0,0 +1,203 @@ +import XCTest +@testable import McBopomofo + +class KeyHandlerPlainBopomofoTests: XCTestCase { + var handler = KeyHandler() + + override func setUpWithError() throws { + LanguageModelManager.loadDataModels() + handler = KeyHandler() + handler.inputMode = .plainBopomofo + } + + override func tearDownWithError() throws { + } + + func testPunctuationTable() { + let input = KeyHandlerInput(inputText: "`", keyCode: 0, charCode: charCode("`"), flags: .shift, isVerticalMode: false) + var state: InputState = InputState.Empty() + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)") + if let state = state as? InputState.ChoosingCandidate { + XCTAssertTrue(state.candidates.contains(",")) + } + } + + func testPunctuationComma() { + let enabled = Preferences.halfWidthPunctuationEnabled + Preferences.halfWidthPunctuationEnabled = false + let input = KeyHandlerInput(inputText: "<", keyCode: 0, charCode: charCode("<"), flags: .shift, isVerticalMode: false) + var state: InputState = InputState.Empty() + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)") + if let state = state as? InputState.ChoosingCandidate { + XCTAssertEqual(state.composingBuffer, ",") + } + Preferences.halfWidthPunctuationEnabled = enabled + } + + func testPunctuationPeriod() { + let enabled = Preferences.halfWidthPunctuationEnabled + Preferences.halfWidthPunctuationEnabled = false + let input = KeyHandlerInput(inputText: ">", keyCode: 0, charCode: charCode(">"), flags: .shift, isVerticalMode: false) + var state: InputState = InputState.Empty() + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)") + if let state = state as? InputState.ChoosingCandidate { + XCTAssertEqual(state.composingBuffer, "。") + } + Preferences.halfWidthPunctuationEnabled = enabled + } + + func testInputNe() { + let input = KeyHandlerInput(inputText: "s", keyCode: 0, charCode: charCode("s"), flags: .shift, isVerticalMode: false) + var state: InputState = InputState.Empty() + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "ㄋ") + } + } + + func testInputNi() { + var state: InputState = InputState.Empty() + let keys = Array("su").map { + String($0) + } + for key in keys { + let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + + XCTAssertTrue(state is InputState.Inputting, "\(state)") + if let state = state as? InputState.Inputting { + XCTAssertEqual(state.composingBuffer, "ㄋㄧ") + } + } + + func testInputNi3() { + var state: InputState = InputState.Empty() + let keys = Array("su3").map { + String($0) + } + for key in keys { + let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + + XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)") + if let state = state as? InputState.ChoosingCandidate { + XCTAssertTrue(state.candidates.contains("你")) + } + } + + func testCancelCandidateUsingDelete() { + var state: InputState = InputState.Empty() + let keys = Array("su3").map { + String($0) + } + for key in keys { + let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + + let input = KeyHandlerInput(inputText: " ", keyCode: KeyCode.delete.rawValue, charCode: 0, flags: [], isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.EmptyIgnoringPreviousState, "\(state)") + } + + func testCancelCandidateUsingEsc() { + var state: InputState = InputState.Empty() + let keys = Array("su3").map { + String($0) + } + for key in keys { + let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + + let input = KeyHandlerInput(inputText: " ", keyCode: 0, charCode: 27, flags: [], isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + + XCTAssertTrue(state is InputState.EmptyIgnoringPreviousState, "\(state)") + } + + func testAssociatedPhrases() { + let enabled = Preferences.associatedPhrasesEnabled + Preferences.associatedPhrasesEnabled = true + var state: InputState = InputState.Empty() + let keys = Array("aul ").map { + String($0) + } + for key in keys { + let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + } errorCallback: { + } + } + + XCTAssertTrue(state is InputState.AssociatedPhrases, "\(state)") + if let state = state as? InputState.AssociatedPhrases { + XCTAssertTrue(state.candidates.contains("嗚")) + } + Preferences.associatedPhrasesEnabled = enabled + } + + + func testNoAssociatedPhrases() { + let enabled = Preferences.associatedPhrasesEnabled + Preferences.associatedPhrasesEnabled = false + var state: InputState = InputState.Empty() + let keys = Array("aul ").map { + String($0) + } + for key in keys { + let input = KeyHandlerInput(inputText: key, keyCode: 0, charCode: charCode(key), flags: [], isVerticalMode: false) + handler.handle(input: input, state: state) { newState in + state = newState + + } errorCallback: { + } + } + + XCTAssertTrue(state is InputState.Empty, "\(state)") + Preferences.associatedPhrasesEnabled = enabled + } + +} diff --git a/McBopomofoTests/PreferencesTests.swift b/McBopomofoTests/PreferencesTests.swift index 03e71781..9977ed6d 100644 --- a/McBopomofoTests/PreferencesTests.swift +++ b/McBopomofoTests/PreferencesTests.swift @@ -3,12 +3,37 @@ import XCTest class PreferencesTests: XCTestCase { + func reset() { + Preferences.allKeys.forEach { + UserDefaults.standard.removeObject(forKey: $0) + } + } + + func makeSnapshot() -> [String: Any] { + var dict = [String: Any]() + Preferences.allKeys.forEach { + dict[$0] = UserDefaults.standard.object(forKey: $0) + } + return dict + } + + func restore(from snapshot:[String: Any]) { + Preferences.allKeys.forEach { + UserDefaults.standard.set(snapshot[$0], forKey: $0) + } + } + + var snapshot: [String: Any]? + override func setUpWithError() throws { - Preferences.reset() + snapshot = makeSnapshot() + reset() } override func tearDownWithError() throws { - Preferences.reset() + if let snapshot = snapshot { + restore(from: snapshot) + } } func testKeyboardLayout() { diff --git a/Source/InputMethodController.swift b/Source/InputMethodController.swift index f236fb5a..98262b9c 100644 --- a/Source/InputMethodController.swift +++ b/Source/InputMethodController.swift @@ -71,7 +71,7 @@ class McBopomofoInputMethodController: IMKInputController { let halfWidthPunctuationItem = menu.addItem(withTitle: NSLocalizedString("Use Half-Width Punctuations", comment: ""), action: #selector(toggleHalfWidthPunctuation(_:)), keyEquivalent: "h") halfWidthPunctuationItem.keyEquivalentModifierMask = [.command, .control] - halfWidthPunctuationItem.state = Preferences.chineseConversionEnabled.state + halfWidthPunctuationItem.state = Preferences.halfWidthPunctuationEnabled.state let inputMode = keyHandler.inputMode let optionKeyPressed = NSEvent.modifierFlags.contains(.option) @@ -181,9 +181,8 @@ class McBopomofoInputMethodController: IMKInputController { let input = KeyHandlerInput(event: event, isVerticalMode: useVerticalMode) - let result = keyHandler.handle(input, state: state) { newState in + let result = keyHandler.handle(input: input, state: state) { newState in self.handle(state: newState, client: client) - } candidateSelectionCallback: { } errorCallback: { NSSound.beep() } @@ -575,7 +574,7 @@ extension McBopomofoInputMethodController: CandidateControllerDelegate { if let state = state as? InputState.ChoosingCandidate { let selectedValue = state.candidates[Int(index)] - keyHandler.fixNode(withValue: selectedValue) + keyHandler.fixNode(value: selectedValue) guard let inputting = keyHandler.buildInputtingState() as? InputState.Inputting else { return diff --git a/Source/KeyHandler.h b/Source/KeyHandler.h index 4c0093e0..c537b7f4 100644 --- a/Source/KeyHandler.h +++ b/Source/KeyHandler.h @@ -45,11 +45,10 @@ extern InputMode InputModePlainBopomofo; - (BOOL)handleInput:(KeyHandlerInput *)input state:(InputState *)state stateCallback:(void (^)(InputState *))stateCallback -candidateSelectionCallback:(void (^)(void))candidateSelectionCallback - errorCallback:(void (^)(void))errorCallback; + errorCallback:(void (^)(void))errorCallback NS_SWIFT_NAME(handle(input:state:stateCallback:errorCallback:)); - (void)syncWithPreferences; -- (void)fixNodeWithValue:(NSString *)value; +- (void)fixNodeWithValue:(NSString *)value NS_SWIFT_NAME(fixNode(value:)); - (void)clear; - (InputState *)buildInputtingState; diff --git a/Source/KeyHandler.mm b/Source/KeyHandler.mm index b0f49e97..7c63b0da 100644 --- a/Source/KeyHandler.mm +++ b/Source/KeyHandler.mm @@ -218,7 +218,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot return layout; } -- (BOOL)handleInput:(KeyHandlerInput *)input state:(InputState *)inState stateCallback:(void (^)(InputState *))stateCallback candidateSelectionCallback:(void (^)(void))candidateSelectionCallback errorCallback:(void (^)(void))errorCallback +- (BOOL)handleInput:(KeyHandlerInput *)input state:(InputState *)inState stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback { InputState *state = inState; UniChar charCode = input.charCode; @@ -277,12 +277,12 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot // MARK: Handle Candidates if ([state isKindOfClass:[InputStateChoosingCandidate class]]) { - return [self _handleCandidateState:state input:input stateCallback:stateCallback candidateSelectionCallback:candidateSelectionCallback errorCallback:errorCallback]; + return [self _handleCandidateState:state input:input stateCallback:stateCallback errorCallback:errorCallback]; } // MARK: Handle Associated Phrases if ([state isKindOfClass:[InputStateAssociatedPhrases class]]) { - BOOL result = [self _handleCandidateState:state input:input stateCallback:stateCallback candidateSelectionCallback:candidateSelectionCallback errorCallback:errorCallback]; + BOOL result = [self _handleCandidateState:state input:input stateCallback:stateCallback errorCallback:errorCallback]; if (result) { return YES; } @@ -293,7 +293,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot // MARK: Handle Marking if ([state isKindOfClass:[InputStateMarking class]]) { InputStateMarking *marking = (InputStateMarking *) state; - if ([self _handleMarkingState:(InputStateMarking *) state input:input stateCallback:stateCallback candidateSelectionCallback:candidateSelectionCallback errorCallback:errorCallback]) { + if ([self _handleMarkingState:(InputStateMarking *) state input:input stateCallback:stateCallback errorCallback:errorCallback]) { return YES; } state = [marking convertToInputting]; @@ -370,8 +370,7 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot if (!Preferences.associatedPhrasesEnabled) { InputStateEmpty *empty = [[InputStateEmpty alloc] init]; stateCallback(empty); - } - else { + } else { InputStateAssociatedPhrases *associatedPhrases = (InputStateAssociatedPhrases *)[self buildAssociatePhraseStateWithKey:text useVerticalMode:input.useVerticalMode]; if (associatedPhrases) { stateCallback(associatedPhrases); @@ -531,6 +530,10 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot - (BOOL)_handleEscWithState:(InputState *)state stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback { + if (![state isKindOfClass:[InputStateInputting class]]) { + return NO; + } + BOOL escToClearInputBufferEnabled = Preferences.escToCleanInputBuffer; if (escToClearInputBufferEnabled) { @@ -546,15 +549,15 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot // Bopomofo reading, in odds with the expectation of users from // other platforms - if (_bpmfReadingBuffer->isEmpty()) { - // no need to beep since the event is deliberately triggered by user - if (![state isKindOfClass:[InputStateInputting class]]) { - return NO; - } - } else { + if (!_bpmfReadingBuffer->isEmpty()) { _bpmfReadingBuffer->clear(); - InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; - stateCallback(inputting); + if (!_builder->length()) { + InputStateEmpty *empty = [[InputStateEmpty alloc] init]; + stateCallback(empty); + } else { + InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; + stateCallback(inputting); + } } } return YES; @@ -562,16 +565,16 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot - (BOOL)_handleBackwardWithState:(InputState *)state input:(KeyHandlerInput *)input stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback { + if (![state isKindOfClass:[InputStateInputting class]]) { + return NO; + } + if (!_bpmfReadingBuffer->isEmpty()) { errorCallback(); stateCallback(state); return YES; } - if (![state isKindOfClass:[InputStateInputting class]]) { - return NO; - } - InputStateInputting *currentState = (InputStateInputting *) state; if ([input isShiftHold]) { @@ -600,16 +603,16 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot - (BOOL)_handleForwardWithState:(InputState *)state input:(KeyHandlerInput *)input stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback { + if (![state isKindOfClass:[InputStateInputting class]]) { + return NO; + } + if (!_bpmfReadingBuffer->isEmpty()) { errorCallback(); stateCallback(state); return YES; } - if (![state isKindOfClass:[InputStateInputting class]]) { - return NO; - } - InputStateInputting *currentState = (InputStateInputting *) state; if ([input isShiftHold]) { @@ -639,16 +642,16 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot - (BOOL)_handleHomeWithState:(InputState *)state stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback { + if (![state isKindOfClass:[InputStateInputting class]]) { + return NO; + } + if (!_bpmfReadingBuffer->isEmpty()) { errorCallback(); stateCallback(state); return YES; } - if (![state isKindOfClass:[InputStateInputting class]]) { - return NO; - } - if (_builder->cursorIndex()) { _builder->setCursorIndex(0); InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; @@ -663,16 +666,16 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot - (BOOL)_handleEndWithState:(InputState *)state stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback { + if (![state isKindOfClass:[InputStateInputting class]]) { + return NO; + } + if (!_bpmfReadingBuffer->isEmpty()) { errorCallback(); stateCallback(state); return YES; } - if (![state isKindOfClass:[InputStateInputting class]]) { - return NO; - } - if (_builder->cursorIndex() != _builder->length()) { _builder->setCursorIndex(_builder->length()); InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; @@ -687,6 +690,10 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot - (BOOL)_handleAbsorbedArrowKeyWithState:(InputState *)state stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback { + if (![state isKindOfClass:[InputStateInputting class]]) { + return NO; + } + if (!_bpmfReadingBuffer->isEmpty()) { errorCallback(); } @@ -696,11 +703,11 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot - (BOOL)_handleBackspaceWithState:(InputState *)state stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback { - if (_bpmfReadingBuffer->isEmpty()) { - if (![state isKindOfClass:[InputStateInputting class]]) { - return NO; - } + if (![state isKindOfClass:[InputStateInputting class]]) { + return NO; + } + if (_bpmfReadingBuffer->isEmpty()) { if (_builder->cursorIndex()) { _builder->deleteReadingBeforeCursor(); [self _walk]; @@ -713,11 +720,11 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot _bpmfReadingBuffer->backspace(); } - InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; - if (!inputting.composingBuffer.length) { + if (!_builder->length()) { InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; stateCallback(empty); } else { + InputStateInputting *inputting = (InputStateInputting *)[self buildInputtingState]; stateCallback(inputting); } return YES; @@ -725,11 +732,11 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot - (BOOL)_handleDeleteWithState:(InputState *)state stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback { - if (_bpmfReadingBuffer->isEmpty()) { - if (![state isKindOfClass:[InputStateInputting class]]) { - return NO; - } + if (![state isKindOfClass:[InputStateInputting class]]) { + return NO; + } + if (_bpmfReadingBuffer->isEmpty()) { if (_builder->cursorIndex() != _builder->length()) { _builder->deleteReadingAfterCursor(); [self _walk]; @@ -754,26 +761,26 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot - (BOOL)_handleEnterWithState:(InputState *)state stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback { - if ([state isKindOfClass:[InputStateInputting class]]) { - if (_inputMode == InputModePlainBopomofo) { - if (!_bpmfReadingBuffer->isEmpty()) { - errorCallback(); - } - return YES; + if (![state isKindOfClass:[InputStateInputting class]]) { + return NO; + } + + if (_inputMode == InputModePlainBopomofo) { + if (!_bpmfReadingBuffer->isEmpty()) { + errorCallback(); } - - [self clear]; - - InputStateInputting *current = (InputStateInputting *) state; - NSString *composingBuffer = current.composingBuffer; - InputStateCommitting *committing = [[InputStateCommitting alloc] initWithPoppedText:composingBuffer]; - stateCallback(committing); - InputStateEmpty *empty = [[InputStateEmpty alloc] init]; - stateCallback(empty); return YES; } - return NO; + [self clear]; + + InputStateInputting *current = (InputStateInputting *) state; + NSString *composingBuffer = current.composingBuffer; + InputStateCommitting *committing = [[InputStateCommitting alloc] initWithPoppedText:composingBuffer]; + stateCallback(committing); + InputStateEmpty *empty = [[InputStateEmpty alloc] init]; + stateCallback(empty); + return YES; } - (BOOL)_handlePunctuation:(string)customPunctuation state:(InputState *)state usingVerticalMode:(BOOL)useVerticalMode stateCallback:(void (^)(InputState *))stateCallback errorCallback:(void (^)(void))errorCallback @@ -816,7 +823,6 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot - (BOOL)_handleMarkingState:(InputStateMarking *)state input:(KeyHandlerInput *)input stateCallback:(void (^)(InputState *))stateCallback - candidateSelectionCallback:(void (^)(void))candidateSelectionCallback errorCallback:(void (^)(void))errorCallback { UniChar charCode = input.charCode; @@ -876,7 +882,6 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot - (BOOL)_handleCandidateState:(InputState *)state input:(KeyHandlerInput *)input stateCallback:(void (^)(InputState *))stateCallback - candidateSelectionCallback:(void (^)(void))candidateSelectionCallback errorCallback:(void (^)(void))errorCallback; { NSString *inputText = input.inputText; @@ -918,7 +923,6 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot if (!updated) { errorCallback(); } - candidateSelectionCallback(); return YES; } @@ -927,7 +931,6 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot if (!updated) { errorCallback(); } - candidateSelectionCallback(); return YES; } @@ -943,7 +946,6 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot errorCallback(); } } - candidateSelectionCallback(); return YES; } @@ -952,7 +954,6 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot if (!updated) { errorCallback(); } - candidateSelectionCallback(); return YES; } @@ -968,7 +969,6 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot errorCallback(); } } - candidateSelectionCallback(); return YES; } @@ -977,7 +977,6 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot if (!updated) { errorCallback(); } - candidateSelectionCallback(); return YES; } @@ -993,7 +992,6 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot errorCallback(); } } - candidateSelectionCallback(); return YES; } @@ -1009,7 +1007,6 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot errorCallback(); } } - candidateSelectionCallback(); return YES; } @@ -1020,7 +1017,6 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot gCurrentCandidateController.selectedCandidateIndex = 0; } - candidateSelectionCallback(); return YES; } @@ -1042,8 +1038,6 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot } else { gCurrentCandidateController.selectedCandidateIndex = candidates.count - 1; } - - candidateSelectionCallback(); return YES; } @@ -1111,14 +1105,13 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot [self clear]; InputStateEmptyIgnoringPreviousState *empty = [[InputStateEmptyIgnoringPreviousState alloc] init]; stateCallback(empty); - [self handleInput:input state:empty stateCallback:stateCallback candidateSelectionCallback:candidateSelectionCallback errorCallback:errorCallback]; + [self handleInput:input state:empty stateCallback:stateCallback errorCallback:errorCallback]; } return YES; } } errorCallback(); - candidateSelectionCallback(); return YES; } diff --git a/Source/Preferences.swift b/Source/Preferences.swift index dd332818..8d0b6f64 100644 --- a/Source/Preferences.swift +++ b/Source/Preferences.swift @@ -198,29 +198,29 @@ struct ComposingBufferSize { // MARK: - class Preferences: NSObject { - static func reset() { - let defaults = UserDefaults.standard - defaults.removeObject(forKey: kKeyboardLayoutPreferenceKey) - defaults.removeObject(forKey: kBasisKeyboardLayoutPreferenceKey) - defaults.removeObject(forKey: kFunctionKeyKeyboardLayoutPreferenceKey) - defaults.removeObject(forKey: kFunctionKeyKeyboardLayoutOverrideIncludeShiftKey) - defaults.removeObject(forKey: kCandidateListTextSizeKey) - defaults.removeObject(forKey: kSelectPhraseAfterCursorAsCandidatePreferenceKey) - defaults.removeObject(forKey: kUseHorizontalCandidateListPreferenceKey) - defaults.removeObject(forKey: kComposingBufferSizePreferenceKey) - defaults.removeObject(forKey: kChooseCandidateUsingSpaceKey) - defaults.removeObject(forKey: kChineseConversionEnabledKey) - defaults.removeObject(forKey: kHalfWidthPunctuationEnabledKey) - defaults.removeObject(forKey: kEscToCleanInputBufferKey) - defaults.removeObject(forKey: kCandidateTextFontName) - defaults.removeObject(forKey: kCandidateKeyLabelFontName) - defaults.removeObject(forKey: kCandidateKeys) - defaults.removeObject(forKey: kPhraseReplacementEnabledKey) - defaults.removeObject(forKey: kChineseConversionEngineKey) - defaults.removeObject(forKey: kChineseConversionStyle) - defaults.removeObject(forKey: kAssociatedPhrasesEnabledKey) + static var allKeys:[String] { + [kKeyboardLayoutPreferenceKey, + kBasisKeyboardLayoutPreferenceKey, + kFunctionKeyKeyboardLayoutPreferenceKey, + kFunctionKeyKeyboardLayoutOverrideIncludeShiftKey, + kCandidateListTextSizeKey, + kSelectPhraseAfterCursorAsCandidatePreferenceKey, + kUseHorizontalCandidateListPreferenceKey, + kComposingBufferSizePreferenceKey, + kChooseCandidateUsingSpaceKey, + kChineseConversionEnabledKey, + kHalfWidthPunctuationEnabledKey, + kEscToCleanInputBufferKey, + kCandidateTextFontName, + kCandidateKeyLabelFontName, + kCandidateKeys, + kPhraseReplacementEnabledKey, + kChineseConversionEngineKey, + kChineseConversionStyle, + kAssociatedPhrasesEnabledKey] } + @UserDefault(key: kKeyboardLayoutPreferenceKey, defaultValue: 0) @objc static var keyboardLayout: Int