From 9c8d740fc623db48de98502cd20bc6b9f42d3004 Mon Sep 17 00:00:00 2001 From: zonble Date: Wed, 2 Feb 2022 06:49:24 +0800 Subject: [PATCH 1/6] Adds tests. --- McBopomofo.xcodeproj/project.pbxproj | 4 + .../KeyHandlerPlainBopomofoTests.swift | 163 ++++++++++++++++++ McBopomofoTests/PreferencesTests.swift | 29 +++- Source/Preferences.swift | 42 ++--- 4 files changed, 215 insertions(+), 23 deletions(-) create mode 100644 McBopomofoTests/KeyHandlerPlainBopomofoTests.swift diff --git a/McBopomofo.xcodeproj/project.pbxproj b/McBopomofo.xcodeproj/project.pbxproj index 5f4ba050..5c06ae41 100644 --- a/McBopomofo.xcodeproj/project.pbxproj +++ b/McBopomofo.xcodeproj/project.pbxproj @@ -60,6 +60,7 @@ D485D3B92796A8A000657FF3 /* PreferencesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D485D3B82796A8A000657FF3 /* PreferencesTests.swift */; }; D485D3C02796CE3200657FF3 /* VersionUpdateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D485D3BF2796CE3200657FF3 /* VersionUpdateTests.swift */; }; D4A13D5A27A59F0B003BE359 /* InputMethodController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4A13D5927A59D5C003BE359 /* InputMethodController.swift */; }; + 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 */; }; @@ -218,6 +219,7 @@ D485D3BF2796CE3200657FF3 /* VersionUpdateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionUpdateTests.swift; sourceTree = ""; }; 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 = ""; }; + 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 = ""; }; @@ -501,6 +503,7 @@ isa = PBXGroup; children = ( D47D73A327A5D43900255A50 /* KeyHandlerBopomofoTests.swift */, + D4A8E43527A9E982002F7A07 /* KeyHandlerPlainBopomofoTests.swift */, D485D3B82796A8A000657FF3 /* PreferencesTests.swift */, D485D3BF2796CE3200657FF3 /* VersionUpdateTests.swift */, ); @@ -754,6 +757,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/KeyHandlerPlainBopomofoTests.swift b/McBopomofoTests/KeyHandlerPlainBopomofoTests.swift new file mode 100644 index 00000000..7eaecfc2 --- /dev/null +++ b/McBopomofoTests/KeyHandlerPlainBopomofoTests.swift @@ -0,0 +1,163 @@ +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 testPunctuationComma() { + let input = KeyHandlerInput(inputText: "<", keyCode: 0, charCode: charCode("<"), flags: .shift, isVerticalMode: false) + var state: InputState = InputState.Empty() + handler.handle(input, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + + XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)") + if let state = state as? InputState.ChoosingCandidate { + XCTAssertEqual(state.composingBuffer, ",") + } + } + + func testPunctuationPeriod() { + let input = KeyHandlerInput(inputText: ">", keyCode: 0, charCode: charCode(">"), flags: .shift, isVerticalMode: false) + var state: InputState = InputState.Empty() + handler.handle(input, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + + XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)") + if let state = state as? InputState.ChoosingCandidate { + XCTAssertEqual(state.composingBuffer, "。") + } + } + + func testInputNe() { + let input = KeyHandlerInput(inputText: "s", keyCode: 0, charCode: charCode("s"), flags: .shift, isVerticalMode: false) + var state: InputState = InputState.Empty() + handler.handle(input, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } 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, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } 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, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } 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, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + } + + let input = KeyHandlerInput(inputText: " ", keyCode: KeyCode.delete.rawValue, charCode: 0, flags: [], isVerticalMode: false) + handler.handle(input, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } 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, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + } + + let input = KeyHandlerInput(inputText: " ", keyCode: 0, charCode: 27, flags: [], isVerticalMode: false) + handler.handle(input, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } 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, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + } + + XCTAssertTrue(state is InputState.AssociatedPhrases, "\(state)") + if let state = state as? InputState.AssociatedPhrases { + XCTAssertTrue(state.candidates.contains("嗚")) + } + 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/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 From 49f998ad5d37ae20ddbc380791e559021eda958b Mon Sep 17 00:00:00 2001 From: zonble Date: Wed, 2 Feb 2022 07:23:39 +0800 Subject: [PATCH 2/6] Adds tests. --- McBopomofoTests/KeyHandlerBopomofoTests.swift | 221 +++++++++++++++++- .../KeyHandlerPlainBopomofoTests.swift | 15 ++ Source/KeyHandler.mm | 95 ++++---- 3 files changed, 284 insertions(+), 47 deletions(-) diff --git a/McBopomofoTests/KeyHandlerBopomofoTests.swift b/McBopomofoTests/KeyHandlerBopomofoTests.swift index e1ec55a8..5a9e8b70 100644 --- a/McBopomofoTests/KeyHandlerBopomofoTests.swift +++ b/McBopomofoTests/KeyHandlerBopomofoTests.swift @@ -18,7 +18,167 @@ class KeyHandlerBopomofoTests: XCTestCase { override func tearDownWithError() throws { } + func testIgnoreEmpty() { + let input = KeyHandlerInput(inputText: "", keyCode: 0, charCode: 0, flags: [], isVerticalMode: false) + var state: InputState = InputState.Empty() + let result = handler.handle(input, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } 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, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } 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, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } 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, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } 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, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } 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, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } 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, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } 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, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } 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, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } 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, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } 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, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + XCTAssertFalse(result) + } + + 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, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + + XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)") + if let state = state as? InputState.ChoosingCandidate { + XCTAssertTrue(state.candidates.contains(",")) + } + 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 + state = newState + } candidateSelectionCallback: { + } 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, state: state) { newState in @@ -31,9 +191,31 @@ 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, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } 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 @@ -46,6 +228,7 @@ class KeyHandlerBopomofoTests: XCTestCase { if let state = state as? InputState.Inputting { XCTAssertEqual(state.composingBuffer, "。") } + Preferences.halfWidthPunctuationEnabled = enabled } func testInputting() { @@ -374,7 +557,42 @@ class KeyHandlerBopomofoTests: XCTestCase { XCTAssertEqual(state.cursorIndex, 1) } - let space = KeyHandlerInput(inputText: " ", keyCode: KeyCode.down.rawValue, charCode: 0, flags: [], isVerticalMode: false) + let down = KeyHandlerInput(inputText: " ", keyCode: KeyCode.down.rawValue, charCode: 0, flags: [], isVerticalMode: false) + handler.handle(down, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } 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("你")) + } + } + + func testCandidateWithSpace() { + let enabled = Preferences.chooseCandidateUsingSpace + Preferences.chooseCandidateUsingSpace = true + 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, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } 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(space, state: state) { newState in state = newState } candidateSelectionCallback: { @@ -388,6 +606,7 @@ class KeyHandlerBopomofoTests: XCTestCase { let candidates = state.candidates XCTAssertTrue(candidates.contains("你")) } + Preferences.chooseCandidateUsingSpace = enabled } func testHomeAndEnd() { diff --git a/McBopomofoTests/KeyHandlerPlainBopomofoTests.swift b/McBopomofoTests/KeyHandlerPlainBopomofoTests.swift index 7eaecfc2..621f68f2 100644 --- a/McBopomofoTests/KeyHandlerPlainBopomofoTests.swift +++ b/McBopomofoTests/KeyHandlerPlainBopomofoTests.swift @@ -13,6 +13,21 @@ class KeyHandlerPlainBopomofoTests: XCTestCase { 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, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + + XCTAssertTrue(state is InputState.ChoosingCandidate, "\(state)") + if let state = state as? InputState.ChoosingCandidate { + XCTAssertTrue(state.candidates.contains(",")) + } + } + func testPunctuationComma() { let input = KeyHandlerInput(inputText: "<", keyCode: 0, charCode: charCode("<"), flags: .shift, isVerticalMode: false) var state: InputState = InputState.Empty() diff --git a/Source/KeyHandler.mm b/Source/KeyHandler.mm index 569eca31..8d32facf 100644 --- a/Source/KeyHandler.mm +++ b/Source/KeyHandler.mm @@ -532,6 +532,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) { @@ -547,12 +551,7 @@ 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); @@ -563,16 +562,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]) { @@ -601,16 +600,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]) { @@ -640,16 +639,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]; @@ -664,16 +663,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]; @@ -688,6 +687,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(); } @@ -697,11 +700,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]; @@ -726,11 +729,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]; @@ -755,26 +758,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 From 61c4a32f05d07c6ee3bac19a51ac9e4adf34c171 Mon Sep 17 00:00:00 2001 From: zonble Date: Wed, 2 Feb 2022 07:34:27 +0800 Subject: [PATCH 3/6] Adds tests. --- McBopomofoTests/KeyHandlerBopomofoTests.swift | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/McBopomofoTests/KeyHandlerBopomofoTests.swift b/McBopomofoTests/KeyHandlerBopomofoTests.swift index 5a9e8b70..8344842f 100644 --- a/McBopomofoTests/KeyHandlerBopomofoTests.swift +++ b/McBopomofoTests/KeyHandlerBopomofoTests.swift @@ -139,6 +139,50 @@ class KeyHandlerBopomofoTests: XCTestCase { 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, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } 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, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } 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, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } 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, state: state) { newState in + state = newState + } candidateSelectionCallback: { + } errorCallback: { + } + XCTAssertFalse(result) + } + func testPunctuationTable() { let enabled = Preferences.halfWidthPunctuationEnabled Preferences.halfWidthPunctuationEnabled = false From cac954a00b4cdbcb77bce1411c95c0e8df4cd470 Mon Sep 17 00:00:00 2001 From: zonble Date: Wed, 2 Feb 2022 20:29:23 +0800 Subject: [PATCH 4/6] Updates test cases. --- McBopomofoTests/KeyHandlerBopomofoTests.swift | 341 ++++++++++++------ .../KeyHandlerPlainBopomofoTests.swift | 79 ++-- Source/InputMethodController.swift | 7 +- Source/KeyHandler.h | 5 +- Source/KeyHandler.mm | 24 +- 5 files changed, 296 insertions(+), 160 deletions(-) diff --git a/McBopomofoTests/KeyHandlerBopomofoTests.swift b/McBopomofoTests/KeyHandlerBopomofoTests.swift index 8344842f..6fb50e17 100644 --- a/McBopomofoTests/KeyHandlerBopomofoTests.swift +++ b/McBopomofoTests/KeyHandlerBopomofoTests.swift @@ -21,9 +21,8 @@ class KeyHandlerBopomofoTests: XCTestCase { func testIgnoreEmpty() { let input = KeyHandlerInput(inputText: "", keyCode: 0, charCode: 0, flags: [], isVerticalMode: false) var state: InputState = InputState.Empty() - let result = handler.handle(input, state: state) { newState in + let result = handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } XCTAssertFalse(result) @@ -32,9 +31,8 @@ class KeyHandlerBopomofoTests: XCTestCase { 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, state: state) { newState in + let result = handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } XCTAssertFalse(result) @@ -43,9 +41,8 @@ class KeyHandlerBopomofoTests: XCTestCase { 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, state: state) { newState in + let result = handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } XCTAssertFalse(result, "\(state)") @@ -54,9 +51,8 @@ class KeyHandlerBopomofoTests: XCTestCase { 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, state: state) { newState in + let result = handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } XCTAssertFalse(result, "\(state)") @@ -65,9 +61,8 @@ class KeyHandlerBopomofoTests: XCTestCase { 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, state: state) { newState in + let result = handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } XCTAssertFalse(result) @@ -76,9 +71,8 @@ class KeyHandlerBopomofoTests: XCTestCase { 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, state: state) { newState in + let result = handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } XCTAssertFalse(result) @@ -87,9 +81,8 @@ class KeyHandlerBopomofoTests: XCTestCase { 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, state: state) { newState in + let result = handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } XCTAssertFalse(result) @@ -98,9 +91,8 @@ class KeyHandlerBopomofoTests: XCTestCase { 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, state: state) { newState in + let result = handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } XCTAssertFalse(result) @@ -109,9 +101,8 @@ class KeyHandlerBopomofoTests: XCTestCase { 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, state: state) { newState in + let result = handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } XCTAssertFalse(result) @@ -120,9 +111,8 @@ class KeyHandlerBopomofoTests: XCTestCase { 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, state: state) { newState in + let result = handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } XCTAssertFalse(result) @@ -131,9 +121,8 @@ class KeyHandlerBopomofoTests: XCTestCase { 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, state: state) { newState in + let result = handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } XCTAssertFalse(result) @@ -142,9 +131,8 @@ class KeyHandlerBopomofoTests: XCTestCase { 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, state: state) { newState in + let result = handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } XCTAssertFalse(result) @@ -153,9 +141,8 @@ class KeyHandlerBopomofoTests: XCTestCase { 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, state: state) { newState in + let result = handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } XCTAssertFalse(result) @@ -164,9 +151,8 @@ class KeyHandlerBopomofoTests: XCTestCase { 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, state: state) { newState in + let result = handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } XCTAssertFalse(result) @@ -175,22 +161,69 @@ class KeyHandlerBopomofoTests: XCTestCase { 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, state: state) { newState in + let result = handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } XCTAssertFalse(result) } + 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, state: state) { newState in + handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } @@ -206,9 +239,8 @@ class KeyHandlerBopomofoTests: XCTestCase { 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 - } candidateSelectionCallback: { } errorCallback: { } @@ -225,9 +257,8 @@ class KeyHandlerBopomofoTests: XCTestCase { 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: { } @@ -243,9 +274,8 @@ class KeyHandlerBopomofoTests: XCTestCase { 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 - } candidateSelectionCallback: { } errorCallback: { } @@ -262,9 +292,26 @@ class KeyHandlerBopomofoTests: XCTestCase { 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 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 - } candidateSelectionCallback: { } errorCallback: { } @@ -277,12 +324,13 @@ class KeyHandlerBopomofoTests: XCTestCase { 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: { } } @@ -294,12 +342,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: { } } @@ -311,12 +360,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: { } } @@ -328,12 +378,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: { } } @@ -347,7 +398,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 @@ -357,7 +408,6 @@ class KeyHandlerBopomofoTests: XCTestCase { break } count += 1 - } candidateSelectionCallback: { } errorCallback: { } @@ -370,12 +420,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: { } } @@ -389,9 +440,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: { } @@ -401,9 +451,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: { } @@ -413,9 +462,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 } @@ -429,9 +477,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: { } @@ -441,9 +488,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: { } @@ -452,12 +498,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: { } } @@ -469,9 +516,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: { } @@ -481,9 +527,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: { } @@ -492,12 +537,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: { } } @@ -512,9 +558,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)") @@ -523,9 +568,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)") @@ -534,9 +578,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 } @@ -547,9 +590,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)") @@ -558,9 +600,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)") @@ -570,9 +611,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 } @@ -586,12 +626,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: { } } @@ -602,9 +643,8 @@ class KeyHandlerBopomofoTests: XCTestCase { } let down = KeyHandlerInput(inputText: " ", keyCode: KeyCode.down.rawValue, charCode: 0, flags: [], isVerticalMode: false) - handler.handle(down, state: state) { newState in + handler.handle(input: down, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } @@ -621,12 +661,13 @@ class KeyHandlerBopomofoTests: XCTestCase { let enabled = Preferences.chooseCandidateUsingSpace Preferences.chooseCandidateUsingSpace = true 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: { } } @@ -637,9 +678,8 @@ class KeyHandlerBopomofoTests: XCTestCase { } let space = KeyHandlerInput(inputText: " ", keyCode: 0, charCode: 32, flags: [], isVerticalMode: false) - handler.handle(space, state: state) { newState in + handler.handle(input: space, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } @@ -653,14 +693,103 @@ class KeyHandlerBopomofoTests: XCTestCase { Preferences.chooseCandidateUsingSpace = enabled } - func testHomeAndEnd() { + func testInputSpace() { + let enabled = Preferences.chooseCandidateUsingSpace + Preferences.chooseCandidateUsingSpace = false 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, "你") + } + + 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: { } } @@ -673,9 +802,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: { } @@ -685,9 +813,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: { } diff --git a/McBopomofoTests/KeyHandlerPlainBopomofoTests.swift b/McBopomofoTests/KeyHandlerPlainBopomofoTests.swift index 621f68f2..34d59318 100644 --- a/McBopomofoTests/KeyHandlerPlainBopomofoTests.swift +++ b/McBopomofoTests/KeyHandlerPlainBopomofoTests.swift @@ -16,9 +16,8 @@ class KeyHandlerPlainBopomofoTests: XCTestCase { func testPunctuationTable() { 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: { } @@ -29,11 +28,12 @@ class KeyHandlerPlainBopomofoTests: XCTestCase { } 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, state: state) { newState in + handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } @@ -41,14 +41,16 @@ class KeyHandlerPlainBopomofoTests: XCTestCase { 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, state: state) { newState in + handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } @@ -56,14 +58,14 @@ class KeyHandlerPlainBopomofoTests: XCTestCase { 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, state: state) { newState in + handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } @@ -75,12 +77,13 @@ class KeyHandlerPlainBopomofoTests: XCTestCase { func testInputNi() { var state: InputState = InputState.Empty() - let keys = Array("su").map { String($0) } + 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, state: state) { newState in + handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } } @@ -93,12 +96,13 @@ class KeyHandlerPlainBopomofoTests: XCTestCase { func testInputNi3() { 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: { } } @@ -111,20 +115,20 @@ class KeyHandlerPlainBopomofoTests: XCTestCase { func testCancelCandidateUsingDelete() { 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: { } } let input = KeyHandlerInput(inputText: " ", keyCode: KeyCode.delete.rawValue, charCode: 0, flags: [], isVerticalMode: false) - handler.handle(input, state: state) { newState in + handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } @@ -133,20 +137,20 @@ class KeyHandlerPlainBopomofoTests: XCTestCase { func testCancelCandidateUsingEsc() { 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: { } } let input = KeyHandlerInput(inputText: " ", keyCode: 0, charCode: 27, flags: [], isVerticalMode: false) - handler.handle(input, state: state) { newState in + handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } @@ -157,12 +161,13 @@ class KeyHandlerPlainBopomofoTests: XCTestCase { let enabled = Preferences.associatedPhrasesEnabled Preferences.associatedPhrasesEnabled = true var state: InputState = InputState.Empty() - let keys = Array("aul ").map { String($0) } + 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, state: state) { newState in + handler.handle(input: input, state: state) { newState in state = newState - } candidateSelectionCallback: { } errorCallback: { } } @@ -175,4 +180,24 @@ class KeyHandlerPlainBopomofoTests: XCTestCase { } + 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/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 8d32facf..0d78c231 100644 --- a/Source/KeyHandler.mm +++ b/Source/KeyHandler.mm @@ -219,7 +219,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; @@ -278,12 +278,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; } @@ -294,7 +294,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]; @@ -820,7 +820,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; @@ -880,7 +879,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; @@ -922,7 +920,6 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot if (!updated) { errorCallback(); } - candidateSelectionCallback(); return YES; } @@ -931,7 +928,6 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot if (!updated) { errorCallback(); } - candidateSelectionCallback(); return YES; } @@ -947,7 +943,6 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot errorCallback(); } } - candidateSelectionCallback(); return YES; } @@ -956,7 +951,6 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot if (!updated) { errorCallback(); } - candidateSelectionCallback(); return YES; } @@ -972,7 +966,6 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot errorCallback(); } } - candidateSelectionCallback(); return YES; } @@ -981,7 +974,6 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot if (!updated) { errorCallback(); } - candidateSelectionCallback(); return YES; } @@ -997,7 +989,6 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot errorCallback(); } } - candidateSelectionCallback(); return YES; } @@ -1013,7 +1004,6 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot errorCallback(); } } - candidateSelectionCallback(); return YES; } @@ -1024,7 +1014,6 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot gCurrentCandidateController.selectedCandidateIndex = 0; } - candidateSelectionCallback(); return YES; } @@ -1046,8 +1035,6 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot } else { gCurrentCandidateController.selectedCandidateIndex = candidates.count - 1; } - - candidateSelectionCallback(); return YES; } @@ -1115,14 +1102,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; } From 44ecf223d7aa451a789464e2dd76c7ab312f8f64 Mon Sep 17 00:00:00 2001 From: zonble Date: Wed, 2 Feb 2022 21:10:26 +0800 Subject: [PATCH 5/6] Adds test cases. --- McBopomofoTests/KeyHandlerBopomofoTests.swift | 260 ++++++++++++++++++ Source/KeyHandler.mm | 13 +- 2 files changed, 269 insertions(+), 4 deletions(-) diff --git a/McBopomofoTests/KeyHandlerBopomofoTests.swift b/McBopomofoTests/KeyHandlerBopomofoTests.swift index 6fb50e17..e3976834 100644 --- a/McBopomofoTests/KeyHandlerBopomofoTests.swift +++ b/McBopomofoTests/KeyHandlerBopomofoTests.swift @@ -322,6 +322,24 @@ class KeyHandlerBopomofoTests: XCTestCase { 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 { @@ -824,4 +842,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/Source/KeyHandler.mm b/Source/KeyHandler.mm index 0d78c231..1f13187b 100644 --- a/Source/KeyHandler.mm +++ b/Source/KeyHandler.mm @@ -553,8 +553,13 @@ static NSString *const kGraphVizOutputfile = @"/tmp/McBopomofo-visualization.dot 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; @@ -717,11 +722,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; From 56ced2dd0f2689e871a07ee153fca179dde27b4b Mon Sep 17 00:00:00 2001 From: zonble Date: Wed, 2 Feb 2022 21:25:51 +0800 Subject: [PATCH 6/6] Adds test cases. --- McBopomofoTests/KeyHandlerBopomofoTests.swift | 72 +++++++++++++++++-- Source/KeyHandler.mm | 3 +- 2 files changed, 69 insertions(+), 6 deletions(-) diff --git a/McBopomofoTests/KeyHandlerBopomofoTests.swift b/McBopomofoTests/KeyHandlerBopomofoTests.swift index e3976834..89672300 100644 --- a/McBopomofoTests/KeyHandlerBopomofoTests.swift +++ b/McBopomofoTests/KeyHandlerBopomofoTests.swift @@ -129,7 +129,7 @@ class KeyHandlerBopomofoTests: XCTestCase { } func testIgnoreCommand() { - let input = KeyHandlerInput(inputText: "A", keyCode: 0, charCode: 0, flags: [.command], isVerticalMode: false) + 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 @@ -139,7 +139,7 @@ class KeyHandlerBopomofoTests: XCTestCase { } func testIgnoreOption() { - let input = KeyHandlerInput(inputText: "A", keyCode: 0, charCode: 0, flags: [.option], isVerticalMode: false) + 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 @@ -149,7 +149,7 @@ class KeyHandlerBopomofoTests: XCTestCase { } func testIgnoreNumericPad() { - let input = KeyHandlerInput(inputText: "A", keyCode: 0, charCode: 0, flags: [.numericPad], isVerticalMode: false) + 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 @@ -159,7 +159,7 @@ class KeyHandlerBopomofoTests: XCTestCase { } func testIgnoreCapslock() { - let input = KeyHandlerInput(inputText: "A", keyCode: 0, charCode: 0, flags: [.capsLock], isVerticalMode: false) + 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 @@ -168,6 +168,46 @@ class KeyHandlerBopomofoTests: XCTestCase { 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() @@ -234,6 +274,30 @@ class KeyHandlerBopomofoTests: XCTestCase { 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 diff --git a/Source/KeyHandler.mm b/Source/KeyHandler.mm index 1f13187b..0025caab 100644 --- a/Source/KeyHandler.mm +++ b/Source/KeyHandler.mm @@ -371,8 +371,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);