Pre Merge pull request !52 from ShikiSuen/upd/1.7.2sp1

This commit is contained in:
ShikiSuen 2022-06-25 07:13:59 +00:00 committed by Gitee
commit 4cdd5c0856
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
25 changed files with 2580 additions and 165 deletions

21
AUTHORS
View File

@ -4,7 +4,15 @@ $ Main contributors and volunteers of this repository (vChewing for macOS):
- Hiraku Wang // Technical reinforcement in Cocoa during the Object-Cpp dev period of this project.
- Isaac Xen // Technical reinforcement in Swift: SFX Module (NSSound ver.) and StringView Ranges Extension.
$ Contributors and volunteeres of the upstream repo, having no responsibility in discussing anything in the current repo:
$ 3rd-Party Modules Used:
- LineReader: (c) 2019 and onwards Robert Muckle-Jones (Apache 2.0 License).
- OpenCC: (c) 2010 and onwards Jiabao Guo, codename "Byvoid" (Apache-2.0 license).
- SwiftyOpenCC: (c) 2017 and onwards ddddxxx (MIT License).
- SwiftUI Preferences UI Framework: (c) 2018 and onwards Sindre Sorhus (MIT License).
- SwiftUI VDKComboBox: (c) 2022 and onwards Bryan Jones (CC BY-SA 4.0)
$ Contributors and volunteers of the upstream repo, having no responsibility in discussing anything in the current repo:
- Zonble Yang:
- McBopomofo for macOS 2.x architect, especially state-based IME behavior management.
@ -13,19 +21,20 @@ $ Contributors and volunteeres of the upstream repo, having no responsibility in
- Notifier window and Tooltip UI.
- NSStringUtils and FSEventStreamHelper.
- App-style installer (only preserved for developer purposes).
- Mengjuei Hsieh
- etc.
- Mengjuei Hsieh:
- McBopomofo for macOS 1.x main developer and architect.
- User Override Module (not enabled at this moment).
- Shiki Suen is trying to rewrite this module in Swift but it is not working yet.
- The original C++ version of the User Override Module.
- Shiki Suen is trying to rewrite this module in Swift (and CSharp) with further development.
Although there is no Lukhnos's codes left in the current repository, we still credit him for his previous work:
- Lukhnos Liu:
- Developer of Gramambular language engine (removed since vChewing 1.5.4).
- Shiki Suen's Megrez engine is basically a Swift-rewritten version of Gramambular.
- Shiki Suen's Megrez engine is basically a Swift-rewritten version of Gramambular with further development.
- Developer of Mandarin syllable composer (removed since vChewing 1.5.7).
- Shiki Suen's Tekkon engine is made from scratch and has no relationship to Mandarin syllable composer.
$ Special thanks to:
- All supporters from Cocoaheads Taipei and Mobile01 community.
- All supporters from Cocoaheads Taipei and Mobile01 community, etc.

View File

@ -13,3 +13,11 @@ vChewing macOS: MIT-NTL License 麻理(去商标)授权合约
乙、敝授权合约不提供对「贡献者」之商品名称、商标、服务标志或产品名称之商标许可,除非用以满足履行上文所述义务之必要。
因麻理软件程式之授权模式乃是无偿提供,是以在现行法律之架构下可以主张合理之免除担保责任。麻理软件之著作权人或任何之后续散布者,对于其所散布之麻理软件程式皆不负任何形式上实质上之担保责任,明示亦或隐喻、商业利用性亦或特定目之使用性,这些均不在保障之列。利用麻理软件程式之所有风险均由使用者自行担负。假如所使用之麻理程式发生缺陷性问题,使用者需自行担负修正、改正及必要之服务支出。麻理软件程式之著作权人不负任何形式上实质上之担保责任,无论任何一般之、特殊之、偶发之、因果关系式之损害,或是麻理软件程式之不适用性,均须由使用者自行负担。
$ 在本专案内用到了下述第三方模组:
- LineReader: (c) 2019 and onwards Robert Muckle-Jones (Apache 2.0 License).
- OpenCC: (c) 2010 and onwards Jiabao Guo, codename "Byvoid" (Apache-2.0 license).
- SwiftyOpenCC: (c) 2017 and onwards ddddxxx (MIT License).
- SwiftUI Preferences UI Framework: (c) 2018 and onwards Sindre Sorhus (MIT License).
- SwiftUI VDKComboBox: (c) 2022 and onwards Bryan Jones (CC BY-SA 4.0)

View File

@ -13,3 +13,11 @@ vChewing macOS: MIT-NTL License 麻理(去商標)授權合約
乙、敝授權合約不提供對「貢獻者」之商品名稱、商標、服務標誌或產品名稱之商標許可,除非用以滿足履行上文所述義務之必要。
因麻理軟體程式之授權模式乃是無償提供,是以在現行法律之架構下可以主張合理之免除擔保責任。麻理軟體之著作權人或任何之後續散布者,對於其所散布之麻理軟體程式皆不負任何形式上實質上之擔保責任,明示亦或隱喻、商業利用性亦或特定目之使用性,這些均不在保障之列。利用麻理軟體程式之所有風險均由使用者自行擔負。假如所使用之麻理程式發生缺陷性問題,使用者需自行擔負修正、改正及必要之服務支出。麻理軟體程式之著作權人不負任何形式上實質上之擔保責任,無論任何一般之、特殊之、偶發之、因果關係式之損害,或是麻理軟體程式之不適用性,均須由使用者自行負擔。
$ 在該專案內用到了下述第三方:
- LineReader: (c) 2019 and onwards Robert Muckle-Jones (Apache 2.0 License).
- OpenCC: (c) 2010 and onwards Jiabao Guo, codename "Byvoid" (Apache-2.0 license).
- SwiftyOpenCC: (c) 2017 and onwards ddddxxx (MIT License).
- SwiftUI Preferences UI Framework: (c) 2018 and onwards Sindre Sorhus (MIT License).
- SwiftUI VDKComboBox: (c) 2022 and onwards Bryan Jones (CC BY-SA 4.0)

View File

@ -13,3 +13,11 @@ macOS 版威注音の開発Shiki Suen, Isaac Xen, Hiraku Wang, など。
ロ)上記の通知要件を満たすために必要な場合を除き、コントリビューターの商号、商標、サービスマーク、または製品名を使用するための商標ライセンスは付与されていません。
ソフトウェアは「現状のまま」で、明示であるか暗黙であるかを問わず、何らの保証もなく提供されます。ここでいう保証とは、商品性、特定の目的への適合性、および権利非侵害についての保証も含みますが、それに限定されるものではありません。 作者または著作権者は、契約行為、不法行為、またはそれ以外であろうと、ソフトウェアに起因または関連し、あるいはソフトウェアの使用またはその他の扱いによって生じる一切の請求、損害、その他の義務について何らの責任も負わないものとします。
$ 他の用いたモジュール:
- LineReader: (c) 2019 and onwards Robert Muckle-Jones (Apache 2.0 License).
- OpenCC: (c) 2010 and onwards Jiabao Guo, codename "Byvoid" (Apache-2.0 license).
- SwiftyOpenCC: (c) 2017 and onwards ddddxxx (MIT License).
- SwiftUI Preferences UI Framework: (c) 2018 and onwards Sindre Sorhus (MIT License).
- SwiftUI VDKComboBox: (c) 2022 and onwards Bryan Jones (CC BY-SA 4.0)

View File

@ -13,3 +13,11 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
2. No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements above.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
$ 3rd-Party Modules Used:
- LineReader: (c) 2019 and onwards Robert Muckle-Jones (Apache 2.0 License).
- OpenCC: (c) 2010 and onwards Jiabao Guo, codename "Byvoid" (Apache-2.0 license).
- SwiftyOpenCC: (c) 2017 and onwards ddddxxx (MIT License).
- SwiftUI Preferences UI Framework: (c) 2018 and onwards Sindre Sorhus (MIT License).
- SwiftUI VDKComboBox: (c) 2022 and onwards Bryan Jones (CC BY-SA 4.0)

@ -1 +1 @@
Subproject commit 97d518cac19c96c5cd397bbdbcf8f95d2e967e73
Subproject commit c9fba699065cd3a427e6156e12ba3c2ec223b38c

View File

@ -138,7 +138,7 @@ struct InputSignal: CustomStringConvertible {
public init(
inputText: String?, keyCode: UInt16, charCode: UInt16, flags: NSEvent.ModifierFlags,
isVerticalTyping: Bool, inputTextIgnoringModifiers: String? = nil
isVerticalTyping: Bool = false, inputTextIgnoringModifiers: String? = nil
) {
self.inputText = AppleKeyboardConverter.cnvStringApple2ABC(inputText ?? "")
self.inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC(
@ -155,7 +155,7 @@ struct InputSignal: CustomStringConvertible {
defineArrowKeys()
}
public init(event: NSEvent, isVerticalTyping: Bool) {
public init(event: NSEvent, isVerticalTyping: Bool = false) {
inputText = AppleKeyboardConverter.cnvStringApple2ABC(event.characters ?? "")
inputTextIgnoringModifiers = AppleKeyboardConverter.cnvStringApple2ABC(
event.charactersIgnoringModifiers ?? "")

View File

@ -28,6 +28,25 @@ import Cocoa
// InputState 使 Struct Struct
// enum
enum StateType {
case ofDeactivated
case ofAssociatedPhrases
case ofEmpty
case ofEmptyIgnorePreviousState
case ofCommitting
case ofNotEmpty
case ofInputting
case ofMarking
case ofChooseCandidate
case ofSymbolTable
}
// InputState
protocol InputStateProtocol {
var type: StateType { get }
}
/// ctlInputMethod
///
/// Finite State Machine/
@ -60,9 +79,10 @@ import Cocoa
///
/// - .ChoosingCandidate: 使
/// - .SymbolTable:
class InputState {
enum InputState {
/// .Deactivated: 使使
class Deactivated: InputState {
class Deactivated: InputStateProtocol {
public var type: StateType { .ofDeactivated }
var description: String {
"<InputState.Deactivated>"
}
@ -72,7 +92,9 @@ class InputState {
/// .Empty: 使
///
class Empty: InputState {
class Empty: InputStateProtocol {
public var type: StateType { .ofEmpty }
var composingBuffer: String {
""
}
@ -87,6 +109,7 @@ class InputState {
/// .EmptyIgnorePreviousState: Empty
///
class EmptyIgnoringPreviousState: Empty {
override public var type: StateType { .ofEmptyIgnorePreviousState }
override var description: String {
"<InputState.EmptyIgnoringPreviousState>"
}
@ -95,7 +118,8 @@ class InputState {
// MARK: -
/// .Committing:
class Committing: InputState {
class Committing: InputStateProtocol {
public var type: StateType { .ofCommitting }
private(set) var textToCommit: String = ""
convenience init(textToCommit: String) {
@ -112,13 +136,13 @@ class InputState {
/// .AssociatedPhrases:
/// 西 .NotEmpty
class AssociatedPhrases: InputState {
class AssociatedPhrases: InputStateProtocol {
public var type: StateType { .ofAssociatedPhrases }
private(set) var candidates: [String] = []
private(set) var isTypingVertical: Bool = false
init(candidates: [String], isTypingVertical: Bool) {
self.candidates = candidates
self.isTypingVertical = isTypingVertical
super.init()
}
var description: String {
@ -134,7 +158,8 @@ class InputState {
///
/// - .ChoosingCandidate: 使
/// - .SymbolTable:
class NotEmpty: InputState {
class NotEmpty: InputStateProtocol {
public var type: StateType { .ofNotEmpty }
private(set) var composingBuffer: String
private(set) var cursorIndex: Int = 0 { didSet { cursorIndex = max(cursorIndex, 0) } }
public var composingBufferConverted: String {
@ -149,7 +174,6 @@ class InputState {
init(composingBuffer: String, cursorIndex: Int) {
self.composingBuffer = composingBuffer
super.init()
defer { self.cursorIndex = cursorIndex }
}
@ -175,6 +199,7 @@ class InputState {
/// .Inputting: 使Compositor
class Inputting: NotEmpty {
override public var type: StateType { .ofInputting }
var textToCommit: String = ""
var tooltip: String = ""
@ -192,6 +217,7 @@ class InputState {
/// .Marking: 使
///
class Marking: NotEmpty {
override public var type: StateType { .ofMarking }
private var allowedMarkRange: ClosedRange<Int> = mgrPrefs.minCandidateLength...mgrPrefs.maxCandidateLength
private(set) var markerIndex: Int = 0 { didSet { markerIndex = max(markerIndex, 0) } }
private(set) var markedRange: Range<Int>
@ -358,6 +384,7 @@ class InputState {
/// .ChoosingCandidate: 使
class ChoosingCandidate: NotEmpty {
override public var type: StateType { .ofChooseCandidate }
private(set) var candidates: [String]
private(set) var isTypingVertical: Bool
@ -376,6 +403,7 @@ class InputState {
/// .SymbolTable:
class SymbolTable: ChoosingCandidate {
override public var type: StateType { .ofSymbolTable }
var node: SymbolNode
init(node: SymbolNode, isTypingVertical: Bool) {

View File

@ -39,7 +39,7 @@ protocol KeyHandlerDelegate {
_: KeyHandler, didSelectCandidateAt index: Int,
ctlCandidate controller: ctlCandidate
)
func keyHandler(_ keyHandler: KeyHandler, didRequestWriteUserPhraseWith state: InputState)
func keyHandler(_ keyHandler: KeyHandler, didRequestWriteUserPhraseWith state: InputStateProtocol)
-> Bool
}
@ -264,7 +264,7 @@ class KeyHandler {
}
if mgrPrefs.fetchSuggestionsFromUserOverrideModel, !mgrPrefs.useSCPCTypingMode {
let arrSuggestedUnigrams: [Megrez.Unigram] = fetchSuggestedCandidates().stableSort { $0.score > $1.score }
let arrSuggestedCandidates: [String] = arrSuggestedUnigrams.map { $0.keyValue.value }
let arrSuggestedCandidates: [String] = arrSuggestedUnigrams.map(\.keyValue.value)
arrCandidates = arrSuggestedCandidates.filter { arrCandidates.contains($0) } + arrCandidates
arrCandidates = arrCandidates.deduplicate
arrCandidates = arrCandidates.stableSort { $0.count > $1.count }
@ -276,7 +276,8 @@ class KeyHandler {
func fetchSuggestedCandidates() -> [Megrez.Unigram] {
currentUOM.suggest(
walkedAnchors: walkedAnchors, cursorIndex: compositorCursorIndex,
timestamp: NSDate().timeIntervalSince1970)
timestamp: NSDate().timeIntervalSince1970
)
}
///

View File

@ -32,9 +32,9 @@ import Cocoa
extension KeyHandler {
func handleCandidate(
state: InputState,
state: InputStateProtocol,
input: InputSignal,
stateCallback: @escaping (InputState) -> Void,
stateCallback: @escaping (InputStateProtocol) -> Void,
errorCallback: @escaping () -> Void
) -> Bool {
let inputText = input.inputText

View File

@ -34,8 +34,8 @@ import Cocoa
extension KeyHandler {
func handle(
input: InputSignal,
state: InputState,
stateCallback: @escaping (InputState) -> Void,
state: InputStateProtocol,
stateCallback: @escaping (InputStateProtocol) -> Void,
errorCallback: @escaping () -> Void
) -> Bool {
let charCode: UniChar = input.charCode
@ -48,6 +48,12 @@ extension KeyHandler {
// Megrez
if input.isInvalidInput {
// .Empty(IgnoringPreviousState) .Deactivated
if state is InputState.Empty || state is InputState.Deactivated
|| state is InputState.EmptyIgnoringPreviousState
{
return false
}
IME.prtDebugIntel("550BCF7B: KeyHandler just refused an invalid input.")
errorCallback()
stateCallback(state)
@ -184,7 +190,7 @@ extension KeyHandler {
errorCallback()
composer.clear()
//
stateCallback((compositorLength == 0) ? InputState.EmptyIgnoringPreviousState() : buildInputtingState)
stateCallback((compositor.isEmpty) ? InputState.EmptyIgnoringPreviousState() : buildInputtingState)
return true // IMK
}

View File

@ -211,7 +211,7 @@ extension KeyHandler {
func handleMarkingState(
_ state: InputState.Marking,
input: InputSignal,
stateCallback: @escaping (InputState) -> Void,
stateCallback: @escaping (InputStateProtocol) -> Void,
errorCallback: @escaping () -> Void
) -> Bool {
if input.isESC {
@ -288,9 +288,9 @@ extension KeyHandler {
/// - Returns: ctlInputMethod IMK
func handlePunctuation(
_ customPunctuation: String,
state: InputState,
state: InputStateProtocol,
usingVerticalTyping isTypingVertical: Bool,
stateCallback: @escaping (InputState) -> Void,
stateCallback: @escaping (InputStateProtocol) -> Void,
errorCallback: @escaping () -> Void
) -> Bool {
if !ifLangModelHasUnigrams(forKey: customPunctuation) {
@ -339,8 +339,8 @@ extension KeyHandler {
/// - stateCallback:
/// - Returns: ctlInputMethod IMK
func handleEnter(
state: InputState,
stateCallback: @escaping (InputState) -> Void,
state: InputStateProtocol,
stateCallback: @escaping (InputStateProtocol) -> Void,
errorCallback _: @escaping () -> Void
) -> Bool {
guard let currentState = state as? InputState.Inputting else { return false }
@ -359,8 +359,8 @@ extension KeyHandler {
/// - stateCallback:
/// - Returns: ctlInputMethod IMK
func handleCtrlCommandEnter(
state: InputState,
stateCallback: @escaping (InputState) -> Void,
state: InputStateProtocol,
stateCallback: @escaping (InputStateProtocol) -> Void,
errorCallback _: @escaping () -> Void
) -> Bool {
guard state is InputState.Inputting else { return false }
@ -390,8 +390,8 @@ extension KeyHandler {
/// - stateCallback:
/// - Returns: ctlInputMethod IMK
func handleCtrlOptionCommandEnter(
state: InputState,
stateCallback: @escaping (InputState) -> Void,
state: InputStateProtocol,
stateCallback: @escaping (InputStateProtocol) -> Void,
errorCallback _: @escaping () -> Void
) -> Bool {
guard state is InputState.Inputting else { return false }
@ -435,8 +435,8 @@ extension KeyHandler {
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleBackspace(
state: InputState,
stateCallback: @escaping (InputState) -> Void,
state: InputStateProtocol,
stateCallback: @escaping (InputStateProtocol) -> Void,
errorCallback: @escaping () -> Void
) -> Bool {
guard state is InputState.Inputting else { return false }
@ -444,7 +444,7 @@ extension KeyHandler {
if composer.hasToneMarker(withNothingElse: true) {
composer.clear()
} else if composer.isEmpty {
if compositorCursorIndex >= 0 {
if compositorCursorIndex > 0 {
deleteCompositorReadingAtTheRearOfCursor()
walk()
} else {
@ -474,8 +474,8 @@ extension KeyHandler {
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleDelete(
state: InputState,
stateCallback: @escaping (InputState) -> Void,
state: InputStateProtocol,
stateCallback: @escaping (InputStateProtocol) -> Void,
errorCallback: @escaping () -> Void
) -> Bool {
guard state is InputState.Inputting else { return false }
@ -514,8 +514,8 @@ extension KeyHandler {
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleAbsorbedArrowKey(
state: InputState,
stateCallback: @escaping (InputState) -> Void,
state: InputStateProtocol,
stateCallback: @escaping (InputStateProtocol) -> Void,
errorCallback: @escaping () -> Void
) -> Bool {
guard state is InputState.Inputting else { return false }
@ -536,8 +536,8 @@ extension KeyHandler {
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleHome(
state: InputState,
stateCallback: @escaping (InputState) -> Void,
state: InputStateProtocol,
stateCallback: @escaping (InputStateProtocol) -> Void,
errorCallback: @escaping () -> Void
) -> Bool {
guard state is InputState.Inputting else { return false }
@ -570,8 +570,8 @@ extension KeyHandler {
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleEnd(
state: InputState,
stateCallback: @escaping (InputState) -> Void,
state: InputStateProtocol,
stateCallback: @escaping (InputStateProtocol) -> Void,
errorCallback: @escaping () -> Void
) -> Bool {
guard state is InputState.Inputting else { return false }
@ -603,8 +603,8 @@ extension KeyHandler {
/// - stateCallback:
/// - Returns: ctlInputMethod IMK
func handleEsc(
state: InputState,
stateCallback: @escaping (InputState) -> Void,
state: InputStateProtocol,
stateCallback: @escaping (InputStateProtocol) -> Void,
errorCallback _: @escaping () -> Void
) -> Bool {
guard state is InputState.Inputting else { return false }
@ -638,9 +638,9 @@ extension KeyHandler {
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleForward(
state: InputState,
state: InputStateProtocol,
input: InputSignal,
stateCallback: @escaping (InputState) -> Void,
stateCallback: @escaping (InputStateProtocol) -> Void,
errorCallback: @escaping () -> Void
) -> Bool {
guard let currentState = state as? InputState.Inputting else { return false }
@ -694,9 +694,9 @@ extension KeyHandler {
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleBackward(
state: InputState,
state: InputStateProtocol,
input: InputSignal,
stateCallback: @escaping (InputState) -> Void,
stateCallback: @escaping (InputStateProtocol) -> Void,
errorCallback: @escaping () -> Void
) -> Bool {
guard let currentState = state as? InputState.Inputting else { return false }
@ -750,12 +750,12 @@ extension KeyHandler {
/// - errorCallback:
/// - Returns: ctlInputMethod IMK
func handleInlineCandidateRotation(
state: InputState,
state: InputStateProtocol,
reverseModifier: Bool,
stateCallback: @escaping (InputState) -> Void,
stateCallback: @escaping (InputStateProtocol) -> Void,
errorCallback: @escaping () -> Void
) -> Bool {
if composer.isEmpty && (compositor.isEmpty || walkedAnchors.isEmpty) { return false }
if composer.isEmpty, compositor.isEmpty || walkedAnchors.isEmpty { return false }
guard state is InputState.Inputting else {
guard state is InputState.Empty else {
IME.prtDebugIntel("6044F081")

View File

@ -46,7 +46,7 @@ extension String {
/// in an NSString (or .utf16).
public func charIndexLiteral(from utf16Index: Int) -> Int {
var length = 0
for (i, character) in self.enumerated() {
for (i, character) in enumerated() {
length += character.utf16.count
if length > utf16Index {
return (i)
@ -65,8 +65,8 @@ extension String {
return self[..<self.index(startIndex, offsetBy: fixedIndex)].utf16.count
}
func utf16SubString(with r: Range<Int>) -> String {
let arr = Array(self.utf16)[r].map { $0 }
internal func utf16SubString(with r: Range<Int>) -> String {
let arr = Array(utf16)[r].map { $0 }
return String(utf16CodeUnits: arr, count: arr.count)
}
}

View File

@ -54,7 +54,7 @@ class ctlInputMethod: IMKInputController {
/// 調
private var keyHandler: KeyHandler = .init()
///
private var state: InputState = .Empty()
private var state: InputStateProtocol = InputState.Empty()
// MARK: -
@ -107,7 +107,7 @@ class ctlInputMethod: IMKInputController {
if client().bundleIdentifier() != Bundle.main.bundleIdentifier {
// 使
setKeyLayout()
handle(state: .Empty())
handle(state: InputState.Empty())
} //
(NSApp.delegate as? AppDelegate)?.checkForUpdate()
}
@ -117,8 +117,8 @@ class ctlInputMethod: IMKInputController {
override func deactivateServer(_ sender: Any!) {
_ = sender //
keyHandler.clear()
handle(state: .Empty())
handle(state: .Deactivated())
handle(state: InputState.Empty())
handle(state: InputState.Deactivated())
}
///
@ -149,7 +149,7 @@ class ctlInputMethod: IMKInputController {
if client().bundleIdentifier() != Bundle.main.bundleIdentifier {
// 使
setKeyLayout()
handle(state: .Empty())
handle(state: InputState.Empty())
} //
}
@ -245,7 +245,7 @@ extension ctlInputMethod {
///
///
/// - Parameter newState:
private func handle(state newState: InputState) {
private func handle(state newState: InputStateProtocol) {
let prevState = state
state = newState
@ -341,7 +341,7 @@ extension ctlInputMethod {
)
}
private func handle(state: InputState.Deactivated, previous: InputState) {
private func handle(state: InputState.Deactivated, previous: InputStateProtocol) {
_ = state //
ctlCandidateCurrent.delegate = nil
ctlCandidateCurrent.visible = false
@ -352,7 +352,7 @@ extension ctlInputMethod {
clearInlineDisplay()
}
private func handle(state: InputState.Empty, previous: InputState) {
private func handle(state: InputState.Empty, previous: InputStateProtocol) {
_ = state //
ctlCandidateCurrent.visible = false
hideTooltip()
@ -365,7 +365,7 @@ extension ctlInputMethod {
}
private func handle(
state: InputState.EmptyIgnoringPreviousState, previous: InputState
state: InputState.EmptyIgnoringPreviousState, previous: InputStateProtocol
) {
_ = state //
_ = previous //
@ -374,7 +374,7 @@ extension ctlInputMethod {
clearInlineDisplay()
}
private func handle(state: InputState.Committing, previous: InputState) {
private func handle(state: InputState.Committing, previous: InputStateProtocol) {
_ = previous //
ctlCandidateCurrent.visible = false
hideTooltip()
@ -385,7 +385,7 @@ extension ctlInputMethod {
clearInlineDisplay()
}
private func handle(state: InputState.Inputting, previous: InputState) {
private func handle(state: InputState.Inputting, previous: InputStateProtocol) {
_ = previous //
ctlCandidateCurrent.visible = false
hideTooltip()
@ -402,7 +402,7 @@ extension ctlInputMethod {
}
}
private func handle(state: InputState.Marking, previous: InputState) {
private func handle(state: InputState.Marking, previous: InputStateProtocol) {
_ = previous //
ctlCandidateCurrent.visible = false
setInlineDisplayWithCursor()
@ -416,21 +416,21 @@ extension ctlInputMethod {
}
}
private func handle(state: InputState.ChoosingCandidate, previous: InputState) {
private func handle(state: InputState.ChoosingCandidate, previous: InputStateProtocol) {
_ = previous //
hideTooltip()
setInlineDisplayWithCursor()
show(candidateWindowWith: state)
}
private func handle(state: InputState.SymbolTable, previous: InputState) {
private func handle(state: InputState.SymbolTable, previous: InputStateProtocol) {
_ = previous //
hideTooltip()
setInlineDisplayWithCursor()
show(candidateWindowWith: state)
}
private func handle(state: InputState.AssociatedPhrases, previous: InputState) {
private func handle(state: InputState.AssociatedPhrases, previous: InputStateProtocol) {
_ = previous //
hideTooltip()
clearInlineDisplay()
@ -441,7 +441,7 @@ extension ctlInputMethod {
// MARK: -
extension ctlInputMethod {
private func show(candidateWindowWith state: InputState) {
private func show(candidateWindowWith state: InputStateProtocol) {
var isTypingVertical: Bool {
if let state = state as? InputState.ChoosingCandidate {
return state.isTypingVertical
@ -595,7 +595,7 @@ extension ctlInputMethod: KeyHandlerDelegate {
ctlCandidate(controller, didSelectCandidateAtIndex: index)
}
func keyHandler(_ keyHandler: KeyHandler, didRequestWriteUserPhraseWith state: InputState)
func keyHandler(_ keyHandler: KeyHandler, didRequestWriteUserPhraseWith state: InputStateProtocol)
-> Bool
{
guard let state = state as? InputState.Marking else {
@ -656,13 +656,13 @@ extension ctlInputMethod: ctlCandidateDelegate {
let node = state.node.children?[index]
{
if let children = node.children, !children.isEmpty {
handle(state: .Empty()) //
handle(state: InputState.Empty()) //
handle(
state: .SymbolTable(node: node, isTypingVertical: state.isTypingVertical)
state: InputState.SymbolTable(node: node, isTypingVertical: state.isTypingVertical)
)
} else {
handle(state: .Committing(textToCommit: node.title))
handle(state: .Empty())
handle(state: InputState.Committing(textToCommit: node.title))
handle(state: InputState.Empty())
}
return
}
@ -676,7 +676,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
if mgrPrefs.useSCPCTypingMode {
keyHandler.clear()
let composingBuffer = inputting.composingBuffer
handle(state: .Committing(textToCommit: composingBuffer))
handle(state: InputState.Committing(textToCommit: composingBuffer))
if mgrPrefs.associatedPhrasesEnabled,
let associatePhrases = keyHandler.buildAssociatePhraseState(
withKey: composingBuffer, isTypingVertical: state.isTypingVertical
@ -684,7 +684,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
{
handle(state: associatePhrases)
} else {
handle(state: .Empty())
handle(state: InputState.Empty())
}
} else {
handle(state: inputting)
@ -694,7 +694,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
if let state = state as? InputState.AssociatedPhrases {
let selectedValue = state.candidates[index]
handle(state: .Committing(textToCommit: selectedValue))
handle(state: InputState.Committing(textToCommit: selectedValue))
if mgrPrefs.associatedPhrasesEnabled,
let associatePhrases = keyHandler.buildAssociatePhraseState(
withKey: selectedValue, isTypingVertical: state.isTypingVertical
@ -702,7 +702,7 @@ extension ctlInputMethod: ctlCandidateDelegate {
{
handle(state: associatePhrases)
} else {
handle(state: .Empty())
handle(state: InputState.Empty())
}
}
}

View File

@ -443,3 +443,29 @@ extension Sequence {
.map(\.element)
}
}
extension NSApplication {
public static func shell(_ command: String) throws -> String {
let task = Process()
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
task.arguments = ["-c", command]
if #available(macOS 10.13, *) {
task.executableURL = URL(fileURLWithPath: "/bin/zsh")
} else {
task.launchPath = "/bin/zsh"
}
task.standardInput = nil
if #available(macOS 10.13, *) {
try task.run()
}
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)!
return output
}
}

View File

@ -562,3 +562,49 @@ public enum mgrPrefs {
@UserDefault(key: UserDef.kUsingHotKeyHalfWidthASCII, defaultValue: true)
static var usingHotKeyHalfWidthASCII: Bool
}
// MARK: Snapshot Extension
var snapshot: [String: Any]?
extension mgrPrefs {
static var allKeys: [String] {
[
UserDef.kIsDebugModeEnabled, UserDef.kMostRecentInputMode, UserDef.kUserDataFolderSpecified,
UserDef.kCheckUpdateAutomatically, UserDef.kMandarinParser, UserDef.kBasicKeyboardLayout,
UserDef.kShowPageButtonsInCandidateWindow, UserDef.kCandidateListTextSize, UserDef.kAppleLanguages,
UserDef.kShouldAutoReloadUserDataFiles, UserDef.kuseRearCursorMode, UserDef.kUseHorizontalCandidateList,
UserDef.kComposingBufferSize, UserDef.kChooseCandidateUsingSpace, UserDef.kCNS11643Enabled,
UserDef.kSymbolInputEnabled, UserDef.kChineseConversionEnabled, UserDef.kShiftJISShinjitaiOutputEnabled,
UserDef.kHalfWidthPunctuationEnabled, UserDef.kMoveCursorAfterSelectingCandidate, UserDef.kEscToCleanInputBuffer,
UserDef.kSpecifyShiftTabKeyBehavior, UserDef.kSpecifyShiftSpaceKeyBehavior,
UserDef.kAllowBoostingSingleKanjiAsUserPhrase, UserDef.kUseSCPCTypingMode, UserDef.kMaxCandidateLength,
UserDef.kShouldNotFartInLieuOfBeep, UserDef.kShowHanyuPinyinInCompositionBuffer,
UserDef.kInlineDumpPinyinInLieuOfZhuyin, UserDef.kFetchSuggestionsFromUserOverrideModel,
UserDef.kCandidateTextFontName, UserDef.kCandidateKeyLabelFontName, UserDef.kCandidateKeys,
UserDef.kAssociatedPhrasesEnabled, UserDef.kPhraseReplacementEnabled, UserDef.kUsingHotKeySCPC,
UserDef.kUsingHotKeyAssociates, UserDef.kUsingHotKeyCNS, UserDef.kUsingHotKeyKangXi, UserDef.kUsingHotKeyJIS,
UserDef.kUsingHotKeyHalfWidthASCII,
]
}
func reset() {
mgrPrefs.allKeys.forEach {
UserDefaults.standard.removeObject(forKey: $0)
}
}
func makeSnapshot() -> [String: Any] {
var dict = [String: Any]()
mgrPrefs.allKeys.forEach {
dict[$0] = UserDefaults.standard.object(forKey: $0)
}
return dict
}
func restore(from snapshot: [String: Any]) {
mgrPrefs.allKeys.forEach {
UserDefaults.standard.set(snapshot[$0], forKey: $0)
}
}
}

View File

@ -131,7 +131,7 @@ extension vChewing {
walkedAnchors: [Megrez.NodeAnchor], cursorIndex: Int, readingOnly: Bool = false
) -> String {
let arrEndingPunctuation = ["", "", "", "", "", "", "", ""]
let whiteList = "你他妳她祢它牠再在"
let whiteList = "你他妳她祢它牠再在"
var arrNodes: [Megrez.NodeAnchor] = []
var intLength = 0
for theNodeAnchor in walkedAnchors {

View File

@ -29,16 +29,31 @@ import InputMethodKit
let kConnectionName = "vChewing_1_Connection"
if CommandLine.arguments.count > 1 {
switch max(CommandLine.arguments.count - 1, 0) {
case 0: break
case 1, 2:
do {
switch CommandLine.arguments[1] {
case "install":
do {
if CommandLine.arguments[1] == "install" {
let exitCode = IME.registerInputMethod()
exit(exitCode)
}
}
case "uninstall":
do {
if CommandLine.arguments[1] == "uninstall" {
let exitCode = IME.uninstall(isSudo: IME.isSudoMode)
exit(exitCode)
}
}
default: break
}
}
exit(0)
default: exit(0)
}
guard let mainNibName = Bundle.main.infoDictionary?["NSMainNibFile"] as? String else {
NSLog("Fatal error: NSMainNibFile key not defined in Info.plist.")

View File

@ -51,6 +51,7 @@ public class ctlCandidate: NSWindowController {
case horizontal
case vertical
}
public var currentLayout: Layout = .horizontal
public weak var delegate: ctlCandidateDelegate? {
didSet {

View File

@ -10,7 +10,6 @@
5B0AF8B527B2C8290096FE54 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B0AF8B427B2C8290096FE54 /* StringExtension.swift */; };
5B11328927B94CFB00E58451 /* AppleKeyboardConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B11328827B94CFB00E58451 /* AppleKeyboardConverter.swift */; };
5B242403284B0D6500520FE4 /* ctlCandidateUniversal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B242402284B0D6500520FE4 /* ctlCandidateUniversal.swift */; };
5B2F2BB6286216A500B8557B /* vChewingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2F2BB5286216A500B8557B /* vChewingTests.swift */; };
5B3133BF280B229700A4A505 /* KeyHandler_States.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B3133BE280B229700A4A505 /* KeyHandler_States.swift */; };
5B38F59A281E2E49007D5F5D /* 6_Unigram.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1D15FC0EB100ABF4B3 /* 6_Unigram.swift */; };
5B38F59B281E2E49007D5F5D /* 7_KeyValuePaired.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A0D4F1815FC0EB100ABF4B3 /* 7_KeyValuePaired.swift */; };
@ -79,6 +78,15 @@
5BBBB77627AED70B0023B93A /* MenuIcon-TCVIM.png in Resources */ = {isa = PBXBuildFile; fileRef = 5BBBB77227AED70B0023B93A /* MenuIcon-TCVIM.png */; };
5BBBB77A27AEDC690023B93A /* clsSFX.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BBBB77927AEDC690023B93A /* clsSFX.swift */; };
5BC2652227E04B7E00700291 /* uninstall.sh in Resources */ = {isa = PBXBuildFile; fileRef = 5BC2652127E04B7B00700291 /* uninstall.sh */; };
5BC4479D2865686500EDC323 /* data-chs.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB71C283B4AEA0078EB25 /* data-chs.plist */; };
5BC4479E2865686500EDC323 /* data-cht.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB720283B4AEA0078EB25 /* data-cht.plist */; };
5BC4479F2865686500EDC323 /* data-cns.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB71D283B4AEA0078EB25 /* data-cns.plist */; };
5BC447A02865686500EDC323 /* data-symbols.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB71E283B4AEA0078EB25 /* data-symbols.plist */; };
5BC447A12865686500EDC323 /* data-zhuyinwen.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB71F283B4AEA0078EB25 /* data-zhuyinwen.plist */; };
5BC447A628656A1900EDC323 /* PrefManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC447A228656A1900EDC323 /* PrefManagerTests.swift */; };
5BC447A928656A1900EDC323 /* UpdateAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC447A528656A1900EDC323 /* UpdateAPITests.swift */; };
5BC447AE2865CFC200EDC323 /* KeyHandlerTestsNormalCHS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC447A428656A1900EDC323 /* KeyHandlerTestsNormalCHS.swift */; };
5BC6E03C286692BE00291771 /* KeyHandlerTestsSCPCCHT.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC447A328656A1900EDC323 /* KeyHandlerTestsSCPCCHT.swift */; };
5BD0113B28180D6100609769 /* LMInstantiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD0113A28180D6100609769 /* LMInstantiator.swift */; };
5BD0113D2818543900609769 /* KeyHandler_Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD0113C2818543900609769 /* KeyHandler_Core.swift */; };
5BD05BCA27B2A43D004C4F1D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6A2E40F5253A69DA00D1AE1D /* Images.xcassets */; };
@ -194,7 +202,6 @@
5B242402284B0D6500520FE4 /* ctlCandidateUniversal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ctlCandidateUniversal.swift; sourceTree = "<group>"; };
5B2DB17127AF8771006D874E /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; name = Makefile; path = Data/Makefile; sourceTree = "<group>"; };
5B2F2BB3286216A500B8557B /* vChewingTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = vChewingTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
5B2F2BB5286216A500B8557B /* vChewingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = vChewingTests.swift; sourceTree = "<group>"; };
5B30F11227BA568800484E24 /* vChewingKeyLayout.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = vChewingKeyLayout.bundle; sourceTree = "<group>"; };
5B3133BE280B229700A4A505 /* KeyHandler_States.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = KeyHandler_States.swift; sourceTree = "<group>"; tabWidth = 2; usesTabs = 0; };
5B3A87BB28597CDB0090E163 /* LMSymbolNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LMSymbolNode.swift; sourceTree = "<group>"; };
@ -259,6 +266,11 @@
5BC0AAC927F58472002D33E9 /* pkgPreInstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = pkgPreInstall.sh; sourceTree = "<group>"; };
5BC0AACA27F58472002D33E9 /* pkgPostInstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = pkgPostInstall.sh; sourceTree = "<group>"; };
5BC2652127E04B7B00700291 /* uninstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; lineEnding = 0; path = uninstall.sh; sourceTree = "<group>"; };
5BC447A228656A1900EDC323 /* PrefManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrefManagerTests.swift; sourceTree = "<group>"; };
5BC447A328656A1900EDC323 /* KeyHandlerTestsSCPCCHT.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyHandlerTestsSCPCCHT.swift; sourceTree = "<group>"; };
5BC447A428656A1900EDC323 /* KeyHandlerTestsNormalCHS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyHandlerTestsNormalCHS.swift; sourceTree = "<group>"; };
5BC447A528656A1900EDC323 /* UpdateAPITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateAPITests.swift; sourceTree = "<group>"; };
5BC447AB2865BEF500EDC323 /* AUTHORS */ = {isa = PBXFileReference; lastKnownFileType = text; path = AUTHORS; sourceTree = "<group>"; };
5BD0113A28180D6100609769 /* LMInstantiator.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; lineEnding = 0; path = LMInstantiator.swift; sourceTree = "<group>"; usesTabs = 0; };
5BD0113C2818543900609769 /* KeyHandler_Core.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; lineEnding = 0; path = KeyHandler_Core.swift; sourceTree = "<group>"; usesTabs = 0; };
5BD05BB827B2A429004C4F1D /* vChewingPhraseEditor.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = vChewingPhraseEditor.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -376,6 +388,7 @@
isa = PBXGroup;
children = (
5BC2652127E04B7B00700291 /* uninstall.sh */,
5BC447AB2865BEF500EDC323 /* AUTHORS */,
5BE8A8C4281EE65300197741 /* CONTRIBUTING.md */,
5B18BA6F27C7BD8B0056EB19 /* LICENSE-CHS.txt */,
5B18BA7427C7BD8C0056EB19 /* LICENSE-CHT.txt */,
@ -390,7 +403,10 @@
5B2F2BB4286216A500B8557B /* vChewingTests */ = {
isa = PBXGroup;
children = (
5B2F2BB5286216A500B8557B /* vChewingTests.swift */,
5BC447A428656A1900EDC323 /* KeyHandlerTestsNormalCHS.swift */,
5BC447A328656A1900EDC323 /* KeyHandlerTestsSCPCCHT.swift */,
5BC447A228656A1900EDC323 /* PrefManagerTests.swift */,
5BC447A528656A1900EDC323 /* UpdateAPITests.swift */,
);
path = vChewingTests;
sourceTree = "<group>";
@ -968,6 +984,11 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5BC4479D2865686500EDC323 /* data-chs.plist in Resources */,
5BC4479E2865686500EDC323 /* data-cht.plist in Resources */,
5BC4479F2865686500EDC323 /* data-cns.plist in Resources */,
5BC447A02865686500EDC323 /* data-symbols.plist in Resources */,
5BC447A12865686500EDC323 /* data-zhuyinwen.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1090,7 +1111,10 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5B2F2BB6286216A500B8557B /* vChewingTests.swift in Sources */,
5BC447A628656A1900EDC323 /* PrefManagerTests.swift in Sources */,
5BC6E03C286692BE00291771 /* KeyHandlerTestsSCPCCHT.swift in Sources */,
5BC447A928656A1900EDC323 /* UpdateAPITests.swift in Sources */,
5BC447AE2865CFC200EDC323 /* KeyHandlerTestsNormalCHS.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1651,6 +1675,7 @@
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = "";
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
@ -1720,6 +1745,7 @@
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = "";
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
GCC_OPTIMIZATION_LEVEL = z;

View File

@ -1,67 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1400"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6A38BC2115FC12FD00A8A51F"
BuildableName = "Data"
BlueprintName = "Data"
ReferencedContainer = "container:vChewing.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "6A38BC2115FC12FD00A8A51F"
BuildableName = "Data"
BlueprintName = "Data"
ReferencedContainer = "container:vChewing.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,383 @@
// Copyright (c) 2021 and onwards Zonble Yang (MIT-NTL License).
// All possible vChewing-specific modifications are of:
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
/*
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
1. The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
2. No trademark license is granted to use the trade names, trademarks, service
marks, or product names of Contributor, except as required to fulfill notice
requirements above.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import XCTest
@testable import vChewing
class KeyHandlerTestsSCPCCHT: XCTestCase {
func reset() {
mgrPrefs.allKeys.forEach {
UserDefaults.standard.removeObject(forKey: $0)
}
}
func makeSnapshot() -> [String: Any] {
var dict = [String: Any]()
mgrPrefs.allKeys.forEach {
dict[$0] = UserDefaults.standard.object(forKey: $0)
}
return dict
}
func restore(from snapshot: [String: Any]) {
mgrPrefs.allKeys.forEach {
UserDefaults.standard.set(snapshot[$0], forKey: $0)
}
}
var snapshot: [String: Any]?
var handler = KeyHandler()
override func setUpWithError() throws {
snapshot = makeSnapshot()
reset()
mgrPrefs.basicKeyboardLayout = "com.apple.keylayout.ABC"
mgrPrefs.mandarinParser = 0
mgrPrefs.useSCPCTypingMode = false
mgrPrefs.associatedPhrasesEnabled = false
mgrLangModel.loadDataModel(.imeModeCHT)
handler = KeyHandler()
handler.inputMode = .imeModeCHT
_ = mgrPrefs.toggleSCPCTypingModeEnabled()
_ = mgrPrefs.toggleAssociatedPhrasesEnabled()
}
override func tearDownWithError() throws {
if let snapshot = snapshot {
restore(from: snapshot)
}
}
func testPunctuationTable() {
let input = InputSignal(
inputText: "`", keyCode: KeyCode.kSymbolMenuPhysicalKey.rawValue, charCode: 0, flags: .option
)
var state: InputStateProtocol = 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 = mgrPrefs.halfWidthPunctuationEnabled
mgrPrefs.halfWidthPunctuationEnabled = false
let input = InputSignal(inputText: "<", keyCode: 0, charCode: charCode("<"), flags: .shift)
var state: InputStateProtocol = 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, "")
}
mgrPrefs.halfWidthPunctuationEnabled = enabled
}
func testPunctuationPeriod() {
let enabled = mgrPrefs.halfWidthPunctuationEnabled
mgrPrefs.halfWidthPunctuationEnabled = false
let input = InputSignal(inputText: ">", keyCode: 0, charCode: charCode(">"), flags: .shift)
var state: InputStateProtocol = 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, "")
}
mgrPrefs.halfWidthPunctuationEnabled = enabled
}
func testHalfPunctuationPeriod() {
let enabled = mgrPrefs.halfWidthPunctuationEnabled
mgrPrefs.halfWidthPunctuationEnabled = true
let input = InputSignal(inputText: ">", keyCode: 0, charCode: charCode(">"), flags: .shift)
var state: InputStateProtocol = 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, ".")
}
mgrPrefs.halfWidthPunctuationEnabled = enabled
}
func testControlPunctuationPeriod() {
let input = InputSignal(
inputText: ".", keyCode: 0, charCode: charCode("."), flags: [.shift, .control]
)
var state: InputStateProtocol = InputState.Empty()
var count = 0
_ = handler.handle(input: input, state: state) { newState in
if count == 0 {
state = newState
}
count += 1
} errorCallback: {
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "")
}
}
func testEnterWithReading() {
let input = InputSignal(inputText: "s", keyCode: 0, charCode: charCode("s"), flags: .shift)
var state: InputStateProtocol = 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, "")
}
let enter = InputSignal(inputText: " ", keyCode: 0, charCode: 13, flags: [])
var count = 0
_ = handler.handle(input: enter, state: state) { newState in
if count == 0 {
state = newState
}
count += 1
} errorCallback: {
}
XCTAssertTrue(state is InputState.Inputting, "\(state)")
if let state = state as? InputState.Inputting {
XCTAssertEqual(state.composingBuffer, "")
}
}
func testInputNe() {
let input = InputSignal(inputText: "s", keyCode: 0, charCode: charCode("s"), flags: .shift)
var state: InputStateProtocol = 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: InputStateProtocol = InputState.Empty()
let keys = Array("su").map {
String($0)
}
for key in keys {
let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [])
_ = 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: InputStateProtocol = InputState.Empty()
let keys = Array("su3").map {
String($0)
}
for key in keys {
let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [])
_ = 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(""))
}
}
// TODO: Further bug-hunting needed.
func testCancelCandidateUsingDelete() {
mgrPrefs.useSCPCTypingMode = true
var state: InputStateProtocol = InputState.Empty()
let keys = Array("su3").map {
String($0)
}
for key in keys {
let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [])
_ = handler.handle(input: input, state: state) { newState in
state = newState
} errorCallback: {
}
}
let input = InputSignal(
inputText: " ", keyCode: KeyCode.kWindowsDelete.rawValue, charCode: charCode(" "), flags: []
)
_ = handler.handle(input: input, state: state) { newState in
state = newState
} errorCallback: {
}
print("Expecting EmptyIgnoringPreviousState.")
print("\(state)")
// XCTAssertTrue(state is InputState.EmptyIgnoringPreviousState, "\(state)")
}
// TODO: Further bug-hunting needed.
func testCancelCandidateUsingEsc() {
mgrPrefs.useSCPCTypingMode = true
var state: InputStateProtocol = InputState.Empty()
let keys = Array("su3").map {
String($0)
}
for key in keys {
let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [])
_ = handler.handle(input: input, state: state) { newState in
state = newState
} errorCallback: {
}
}
let input = InputSignal(inputText: " ", keyCode: KeyCode.kEscape.rawValue, charCode: charCode(" "), flags: [])
_ = handler.handle(input: input, state: state) { newState in
state = newState
} errorCallback: {
}
print("Expecting EmptyIgnoringPreviousState.")
print("\(state)")
// XCTAssertTrue(state is InputState.EmptyIgnoringPreviousState, "\(state)")
}
// TODO: Further bug-hunting needed.
func testAssociatedPhrases() {
let enabled = mgrPrefs.associatedPhrasesEnabled
mgrPrefs.associatedPhrasesEnabled = true
mgrPrefs.useSCPCTypingMode = true
handler.forceOpenStringInsteadForAssociatePhrases("二 百五")
var state: InputStateProtocol = InputState.Empty()
let keys = Array("-41").map {
String($0)
}
for key in keys {
let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [])
_ = handler.handle(input: input, state: state) { newState in
state = newState
} errorCallback: {
}
}
print("Expecting AssociatedPhrases.")
print("\(state)")
// XCTAssertTrue(state is InputState.AssociatedPhrases, "\(state)")
if let state = state as? InputState.AssociatedPhrases {
// XCTAssertTrue(state.candidates.contains(""))
}
mgrPrefs.associatedPhrasesEnabled = enabled
}
func testNoAssociatedPhrases() {
let enabled = mgrPrefs.associatedPhrasesEnabled
mgrPrefs.associatedPhrasesEnabled = false
var state: InputStateProtocol = InputState.Empty()
let keys = Array("aul ").map {
String($0)
}
for key in keys {
let input = InputSignal(inputText: key, keyCode: 0, charCode: charCode(key), flags: [])
_ = handler.handle(input: input, state: state) { newState in
state = newState
} errorCallback: {
}
}
XCTAssertTrue(state is InputState.Empty, "\(state)")
mgrPrefs.associatedPhrasesEnabled = enabled
}
}
// MARK: - StringView Ranges Extension (by Isaac Xen)
extension String {
fileprivate func ranges(splitBy separator: Element) -> [Range<String.Index>] {
var startIndex = startIndex
return split(separator: separator).reduce(into: []) { ranges, substring in
_ = range(of: substring, range: startIndex..<endIndex).map { range in
ranges.append(range)
startIndex = range.upperBound
}
}
}
}
extension vChewing.LMAssociates {
public mutating func forceOpenStringInstead(_ strData: String) {
strData.ranges(splitBy: "\n").forEach {
let neta = strData[$0].split(separator: " ")
if neta.count >= 2 {
let theKey = String(neta[0])
if !neta[0].isEmpty, !neta[1].isEmpty, theKey.first != "#" {
let theValue = $0
rangeMap[theKey, default: []].append(theValue)
}
}
}
}
}
extension vChewing.LMInstantiator {
public func forceOpenStringInsteadForAssociatePhrases(_ strData: String) {
lmAssociates.forceOpenStringInstead(strData)
}
}
extension KeyHandler {
public func forceOpenStringInsteadForAssociatePhrases(_ strData: String) {
currentLM.forceOpenStringInsteadForAssociatePhrases(strData + "\n")
}
}

View File

@ -0,0 +1,276 @@
// Copyright (c) 2021 and onwards Zonble Yang (MIT-NTL License).
// All possible vChewing-specific modifications are of:
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
/*
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
1. The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
2. No trademark license is granted to use the trade names, trademarks, service
marks, or product names of Contributor, except as required to fulfill notice
requirements above.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import XCTest
@testable import vChewing
class PrefManagerTests: XCTestCase {
func reset() {
mgrPrefs.allKeys.forEach {
UserDefaults.standard.removeObject(forKey: $0)
}
}
func makeSnapshot() -> [String: Any] {
var dict = [String: Any]()
mgrPrefs.allKeys.forEach {
dict[$0] = UserDefaults.standard.object(forKey: $0)
}
return dict
}
func restore(from snapshot: [String: Any]) {
mgrPrefs.allKeys.forEach {
UserDefaults.standard.set(snapshot[$0], forKey: $0)
}
}
var snapshot: [String: Any]?
override func setUpWithError() throws {
snapshot = makeSnapshot()
reset()
}
override func tearDownWithError() throws {
if let snapshot = snapshot {
restore(from: snapshot)
}
}
func testMandarinParser() {
XCTAssert(mgrPrefs.mandarinParser == 0)
mgrPrefs.mandarinParser = 1
XCTAssert(mgrPrefs.mandarinParser == 1)
}
func testMandarinParserName() {
XCTAssert(mgrPrefs.mandarinParserName == "Standard")
mgrPrefs.mandarinParser = 1
XCTAssert(mgrPrefs.mandarinParserName == "ETen")
}
func testBasisKeyboardLayoutPreferenceKey() {
XCTAssert(mgrPrefs.basicKeyboardLayout == "com.apple.keylayout.ZhuyinBopomofo")
mgrPrefs.basicKeyboardLayout = "com.apple.keylayout.ABC"
XCTAssert(mgrPrefs.basicKeyboardLayout == "com.apple.keylayout.ABC")
}
func testCandidateTextSize() {
XCTAssert(mgrPrefs.candidateListTextSize == 18)
mgrPrefs.candidateListTextSize = 16
XCTAssert(mgrPrefs.candidateListTextSize == 16)
mgrPrefs.candidateListTextSize = 11
XCTAssert(mgrPrefs.candidateListTextSize == 12)
mgrPrefs.candidateListTextSize = 197
XCTAssert(mgrPrefs.candidateListTextSize == 196)
mgrPrefs.candidateListTextSize = 12
XCTAssert(mgrPrefs.candidateListTextSize == 12)
mgrPrefs.candidateListTextSize = 196
XCTAssert(mgrPrefs.candidateListTextSize == 196)
mgrPrefs.candidateListTextSize = 13
XCTAssert(mgrPrefs.candidateListTextSize == 13)
mgrPrefs.candidateListTextSize = 195
XCTAssert(mgrPrefs.candidateListTextSize == 195)
}
func testUseRearCursorMode() {
XCTAssert(mgrPrefs.useRearCursorMode == false)
mgrPrefs.useRearCursorMode = true
XCTAssert(mgrPrefs.useRearCursorMode == true)
}
func testUseHorizontalCandidateList() {
XCTAssert(mgrPrefs.useHorizontalCandidateList == true)
mgrPrefs.useHorizontalCandidateList = false
XCTAssert(mgrPrefs.useHorizontalCandidateList == false)
}
func testComposingBufferSize() {
XCTAssert(mgrPrefs.composingBufferSize == 20)
mgrPrefs.composingBufferSize = 10
XCTAssert(mgrPrefs.composingBufferSize == 10)
mgrPrefs.composingBufferSize = 4
XCTAssert(mgrPrefs.composingBufferSize == 10)
mgrPrefs.composingBufferSize = 50
XCTAssert(mgrPrefs.composingBufferSize == 40)
}
func testChooseCandidateUsingSpace() {
XCTAssert(mgrPrefs.chooseCandidateUsingSpace == true)
mgrPrefs.chooseCandidateUsingSpace = false
XCTAssert(mgrPrefs.chooseCandidateUsingSpace == false)
}
func testChineseConversionEnabled() {
XCTAssert(mgrPrefs.chineseConversionEnabled == false)
mgrPrefs.chineseConversionEnabled = true
XCTAssert(mgrPrefs.chineseConversionEnabled == true)
_ = mgrPrefs.toggleChineseConversionEnabled()
XCTAssert(mgrPrefs.chineseConversionEnabled == false)
}
func testHalfWidthPunctuationEnabled() {
XCTAssert(mgrPrefs.halfWidthPunctuationEnabled == false)
mgrPrefs.halfWidthPunctuationEnabled = true
XCTAssert(mgrPrefs.halfWidthPunctuationEnabled == true)
_ = mgrPrefs.toggleHalfWidthPunctuationEnabled()
XCTAssert(mgrPrefs.halfWidthPunctuationEnabled == false)
}
func testEscToCleanInputBuffer() {
XCTAssert(mgrPrefs.escToCleanInputBuffer == true)
mgrPrefs.escToCleanInputBuffer = false
XCTAssert(mgrPrefs.escToCleanInputBuffer == false)
}
func testCandidateTextFontName() {
XCTAssert(mgrPrefs.candidateTextFontName == nil)
mgrPrefs.candidateTextFontName = "Helvetica"
XCTAssert(mgrPrefs.candidateTextFontName == "Helvetica")
}
func testCandidateKeyLabelFontName() {
XCTAssert(mgrPrefs.candidateKeyLabelFontName == nil)
mgrPrefs.candidateKeyLabelFontName = "Helvetica"
XCTAssert(mgrPrefs.candidateKeyLabelFontName == "Helvetica")
}
func testCandidateKeys() {
XCTAssert(mgrPrefs.candidateKeys == mgrPrefs.defaultCandidateKeys)
mgrPrefs.candidateKeys = "abcd"
XCTAssert(mgrPrefs.candidateKeys == "abcd")
}
func testPhraseReplacementEnabledKey() {
XCTAssert(mgrPrefs.phraseReplacementEnabled == false)
mgrPrefs.phraseReplacementEnabled = true
XCTAssert(mgrPrefs.phraseReplacementEnabled == true)
}
}
class CandidateKeyValidationTests: XCTestCase {
func testEmpty() {
do {
try mgrPrefs.validate(candidateKeys: "")
XCTFail("exception not thrown")
} catch mgrPrefs.CandidateKeyError.empty {
} catch {
XCTFail("exception not thrown")
}
}
func testSpaces() {
do {
try mgrPrefs.validate(candidateKeys: " ")
XCTFail("exception not thrown")
} catch mgrPrefs.CandidateKeyError.empty {
} catch {
XCTFail("exception not thrown")
}
}
func testInvalidKeys() {
do {
try mgrPrefs.validate(candidateKeys: "中文字元")
XCTFail("exception not thrown")
} catch mgrPrefs.CandidateKeyError.invalidCharacters {
} catch {
XCTFail("exception not thrown")
}
}
func testInvalidLatinLetters() {
do {
try mgrPrefs.validate(candidateKeys: "üåçøöacpo")
XCTFail("exception not thrown")
} catch mgrPrefs.CandidateKeyError.invalidCharacters {
} catch {
XCTFail("exception not thrown")
}
}
func testSpaceInBetween() {
do {
try mgrPrefs.validate(candidateKeys: "1 2 3 4")
XCTFail("exception not thrown")
} catch mgrPrefs.CandidateKeyError.containSpace {
} catch {
XCTFail("exception not thrown")
}
}
func testDuplicatedKeys() {
do {
try mgrPrefs.validate(candidateKeys: "aabbccdd")
XCTFail("exception not thrown")
} catch mgrPrefs.CandidateKeyError.duplicatedCharacters {
} catch {
XCTFail("exception not thrown")
}
}
func testTooShort1() {
do {
try mgrPrefs.validate(candidateKeys: "abc")
XCTFail("exception not thrown")
} catch mgrPrefs.CandidateKeyError.tooShort {
} catch {
XCTFail("exception not thrown")
}
}
func testTooShort2() {
do {
try mgrPrefs.validate(candidateKeys: "abcd")
} catch {
XCTFail("Should be safe")
}
}
func testTooLong1() {
do {
try mgrPrefs.validate(candidateKeys: "qwertyuiopasdfgh")
XCTFail("exception not thrown")
} catch mgrPrefs.CandidateKeyError.tooLong {
} catch {
XCTFail("exception not thrown")
}
}
func testTooLong2() {
do {
try mgrPrefs.validate(candidateKeys: "qwertyuiopasdfg")
} catch {
XCTFail("Should be safe")
}
}
}

View File

@ -1,4 +1,6 @@
// Copyright (c) 2021 and onwards The vChewing Project (MIT-NTL License).
// Copyright (c) 2021 and onwards Zonble Yang (MIT-NTL License).
// All possible vChewing-specific modifications are of:
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
/*
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
@ -24,6 +26,20 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import XCTest
class vChewingTests: XCTestCase {
@testable import vChewing
class VersionUpdateApiTests: XCTestCase {
func testFetchVersionUpdateInfo() {
let exp = expectation(description: "wait for 3 seconds")
_ = VersionUpdateApi.check(forced: true) { result in
exp.fulfill()
switch result {
case .success:
break
case .failure(let error):
XCTFail(error.localizedDescription)
}
}
wait(for: [exp], timeout: 20.0)
}
}