MainAssembly // Implementing Unit Tests.
* Further unit tests will be implemented later according to other necessities.
This commit is contained in:
parent
f81e6837b8
commit
136e8088be
|
@ -0,0 +1,17 @@
|
||||||
|
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||||
|
// ====================
|
||||||
|
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||||
|
// ... with NTL restriction stating that:
|
||||||
|
// 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 defined in MIT License.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
let mapKeyCodesANSIForTests: [String: UInt16] = [
|
||||||
|
"1": 18, "2": 19, "3": 20, "4": 21, "5": 23, "6": 22, "7": 26, "8": 28, "9": 25, "0": 29, "-": 27,
|
||||||
|
"=": 24, "q": 12, "w": 13, "e": 14, "r": 15, "t": 17, "y": 16, "u": 32, "i": 34, "o": 31, "p": 35,
|
||||||
|
"[": 33, "]": 30, "\\": 42, "a": 0, "s": 1, "d": 2, "f": 3, "g": 5, "h": 4, "j": 38, "k": 40,
|
||||||
|
"l": 37, ";": 41, "'": 39, "z": 6, "x": 7, "c": 8, "v": 9, "b": 11, "n": 45, "m": 46, ",": 43,
|
||||||
|
".": 47, "/": 44,
|
||||||
|
]
|
|
@ -0,0 +1,65 @@
|
||||||
|
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||||
|
// ====================
|
||||||
|
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||||
|
// ... with NTL restriction stating that:
|
||||||
|
// 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 defined in MIT License.
|
||||||
|
|
||||||
|
import LangModelAssembly
|
||||||
|
import MainAssembly
|
||||||
|
|
||||||
|
// MARK: - Converting Sample String Data to LMCoreJSON Instance.
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
func toDictMap(swapKeyValue: Bool = false, encrypt: Bool = false) -> [String: [String]] {
|
||||||
|
var theDict = [String: [String]]()
|
||||||
|
enumerateLines { currentLine, _ in
|
||||||
|
if currentLine.isEmpty || currentLine.hasPrefix("#") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let linestream = currentLine.split(separator: " ")
|
||||||
|
let col0 = String(linestream[0])
|
||||||
|
let col1 = String(linestream[1])
|
||||||
|
let col2: Double? = Double(linestream[2])
|
||||||
|
var key = swapKeyValue ? col1 : col0
|
||||||
|
if encrypt {
|
||||||
|
key = vChewingLM.LMCoreJSON.cnvPhonabetToASCII(key)
|
||||||
|
}
|
||||||
|
var storedValue = swapKeyValue ? col0 : col1
|
||||||
|
if let col2 = col2 {
|
||||||
|
storedValue.insert(contentsOf: "\(col2.description) ", at: storedValue.startIndex)
|
||||||
|
}
|
||||||
|
theDict[key, default: []].append(storedValue)
|
||||||
|
}
|
||||||
|
return theDict
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Allow LMInstantiator to Load Test Data.
|
||||||
|
|
||||||
|
extension vChewingLM.LMInstantiator {
|
||||||
|
static func construct(
|
||||||
|
isCHS: Bool = false, completionHandler: @escaping (_ this: vChewingLM.LMInstantiator) -> Void
|
||||||
|
) -> vChewingLM.LMInstantiator {
|
||||||
|
let this = vChewingLM.LMInstantiator(isCHS: isCHS)
|
||||||
|
completionHandler(this)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadTestData() {
|
||||||
|
resetFactoryJSONModels()
|
||||||
|
loadLanguageModel(
|
||||||
|
json: (
|
||||||
|
dict: strSampleDataFactoryCore.toDictMap(swapKeyValue: false, encrypt: true),
|
||||||
|
path: "/dev/null"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
loadSymbolData(
|
||||||
|
json: (
|
||||||
|
dict: strSampleDataFactorySymbol.toDictMap(swapKeyValue: true, encrypt: true),
|
||||||
|
path: "/dev/null"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||||
|
// ====================
|
||||||
|
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||||
|
// ... with NTL restriction stating that:
|
||||||
|
// 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 defined in MIT License.
|
||||||
|
|
||||||
|
import MainAssembly
|
||||||
|
|
||||||
|
public let strSampleDataFactorySymbol = #"""
|
||||||
|
ㄈㄥ 🐝 -11
|
||||||
|
ㄇㄧˋ-ㄈㄥ 🐝 -11
|
||||||
|
"""#
|
||||||
|
|
||||||
|
public let strSampleDataFactoryCore = #"""
|
||||||
|
#
|
||||||
|
# 下述詞頻資料取自 libTaBE 資料庫 (http://sourceforge.net/projects/libtabe/)
|
||||||
|
# (2002 最終版). 該專案於 1999 年由 Pai-Hsiang Hsiao 發起、以 BSD 授權發行。
|
||||||
|
#
|
||||||
|
ㄇㄧˋ-ㄈㄥ 蜜蜂 -3.6231
|
||||||
|
ㄇㄧˋ 蜜 -4.6231
|
||||||
|
ㄈㄥ 蜂 -4.6231
|
||||||
|
ㄋㄧˇ 你 -6.000000 // Non-LibTaBE
|
||||||
|
ㄓㄜˋ 這 -6.000000 // Non-LibTaBE
|
||||||
|
ㄧㄤˋ 樣 -6.000000 // Non-LibTaBE
|
||||||
|
ㄙ 絲 -9.495858
|
||||||
|
ㄙ 思 -9.006414
|
||||||
|
ㄙ 私 -99.000000
|
||||||
|
ㄙ 斯 -8.091803
|
||||||
|
ㄙ 司 -99.000000
|
||||||
|
ㄙ 嘶 -13.513987
|
||||||
|
ㄙ 撕 -12.259095
|
||||||
|
ㄍㄠ 高 -7.171551
|
||||||
|
ㄎㄜ 顆 -10.574273
|
||||||
|
ㄎㄜ 棵 -11.504072
|
||||||
|
ㄎㄜ 刻 -10.450457
|
||||||
|
ㄎㄜ 科 -7.171052
|
||||||
|
ㄎㄜ 柯 -99.000000
|
||||||
|
ㄍㄠ 膏 -11.928720
|
||||||
|
ㄍㄠ 篙 -13.624335
|
||||||
|
ㄍㄠ 糕 -12.390804
|
||||||
|
ㄉㄜ˙ 的 -3.516024
|
||||||
|
ㄉㄧˊ 的 -3.516024
|
||||||
|
ㄉㄧˋ 的 -3.516024
|
||||||
|
ㄓㄨㄥ 中 -5.809297
|
||||||
|
ㄉㄜ˙ 得 -7.427179
|
||||||
|
ㄍㄨㄥ 共 -8.381971
|
||||||
|
ㄍㄨㄥ 供 -8.501463
|
||||||
|
ㄐㄧˋ 既 -99.000000
|
||||||
|
ㄐㄧㄣ 今 -8.034095
|
||||||
|
ㄍㄨㄥ 紅 -8.858181
|
||||||
|
ㄐㄧˋ 際 -7.608341
|
||||||
|
ㄐㄧˋ 季 -99.000000
|
||||||
|
ㄐㄧㄣ 金 -7.290109
|
||||||
|
ㄐㄧˋ 騎 -10.939895
|
||||||
|
ㄓㄨㄥ 終 -99.000000
|
||||||
|
ㄐㄧˋ 記 -99.000000
|
||||||
|
ㄐㄧˋ 寄 -99.000000
|
||||||
|
ㄐㄧㄣ 斤 -99.000000
|
||||||
|
ㄐㄧˋ 繼 -9.715317
|
||||||
|
ㄐㄧˋ 計 -7.926683
|
||||||
|
ㄐㄧˋ 暨 -8.373022
|
||||||
|
ㄓㄨㄥ 鐘 -9.877580
|
||||||
|
ㄐㄧㄣ 禁 -10.711079
|
||||||
|
ㄍㄨㄥ 公 -7.877973
|
||||||
|
ㄍㄨㄥ 工 -7.822167
|
||||||
|
ㄍㄨㄥ 攻 -99.000000
|
||||||
|
ㄍㄨㄥ 功 -99.000000
|
||||||
|
ㄍㄨㄥ 宮 -99.000000
|
||||||
|
ㄓㄨㄥ 鍾 -9.685671
|
||||||
|
ㄐㄧˋ 繫 -10.425662
|
||||||
|
ㄍㄨㄥ 弓 -99.000000
|
||||||
|
ㄍㄨㄥ 恭 -99.000000
|
||||||
|
ㄐㄧˋ 劑 -8.888722
|
||||||
|
ㄐㄧˋ 祭 -10.204425
|
||||||
|
ㄐㄧㄣ 浸 -11.378321
|
||||||
|
ㄓㄨㄥ 盅 -99.000000
|
||||||
|
ㄐㄧˋ 忌 -99.000000
|
||||||
|
ㄐㄧˋ 技 -8.450826
|
||||||
|
ㄐㄧㄣ 筋 -11.074890
|
||||||
|
ㄍㄨㄥ 躬 -99.000000
|
||||||
|
ㄐㄧˋ 冀 -12.045357
|
||||||
|
ㄓㄨㄥ 忠 -99.000000
|
||||||
|
ㄐㄧˋ 妓 -99.000000
|
||||||
|
ㄐㄧˋ 濟 -9.517568
|
||||||
|
ㄐㄧˋ 薊 -12.021587
|
||||||
|
ㄐㄧㄣ 巾 -99.000000
|
||||||
|
ㄐㄧㄣ 襟 -12.784206
|
||||||
|
ㄋㄧㄢˊ 年 -6.086515
|
||||||
|
ㄐㄧㄤˇ 講 -9.164384
|
||||||
|
ㄐㄧㄤˇ 獎 -8.690941
|
||||||
|
ㄐㄧㄤˇ 蔣 -10.127828
|
||||||
|
ㄋㄧㄢˊ 黏 -11.336864
|
||||||
|
ㄋㄧㄢˊ 粘 -11.285740
|
||||||
|
ㄐㄧㄤˇ 槳 -12.492933
|
||||||
|
ㄍㄨㄥ-ㄙ 公司 -6.299461
|
||||||
|
ㄎㄜ-ㄐㄧˋ 科技 -6.736613
|
||||||
|
ㄐㄧˋ-ㄍㄨㄥ 濟公 -13.336653
|
||||||
|
ㄐㄧㄤˇ-ㄐㄧㄣ 獎金 -10.344678
|
||||||
|
ㄋㄧㄢˊ-ㄓㄨㄥ 年終 -11.668947
|
||||||
|
ㄋㄧㄢˊ-ㄓㄨㄥ 年中 -11.373044
|
||||||
|
ㄍㄠ-ㄎㄜ-ㄐㄧˋ 高科技 -9.842421
|
||||||
|
ㄓㄜˋ-ㄧㄤˋ 這樣 -6.000000 // Non-LibTaBE
|
||||||
|
ㄋㄧˇ-ㄓㄜˋ 你這 -9.000000 // Non-LibTaBE
|
||||||
|
ㄎㄜ-ㄎㄜ 顆顆 -8.000000 // Non-LibTaBE
|
||||||
|
ㄐㄧㄠˋ 教 -3.676169
|
||||||
|
ㄐㄧㄠˋ 較 -3.24869962
|
||||||
|
ㄐㄧㄠˋ-ㄩˋ 教育 -3.32220565
|
||||||
|
ㄩˋ 育 -3.30192952
|
||||||
|
|
||||||
|
"""#
|
|
@ -0,0 +1,119 @@
|
||||||
|
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||||
|
// ====================
|
||||||
|
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||||
|
// ... with NTL restriction stating that:
|
||||||
|
// 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 defined in MIT License.
|
||||||
|
|
||||||
|
import InputMethodKit
|
||||||
|
|
||||||
|
class FakeClient: NSObject, IMKTextInput {
|
||||||
|
var attributedString: NSMutableAttributedString = .init(string: "")
|
||||||
|
var cursor = 0 {
|
||||||
|
didSet {
|
||||||
|
cursor = max(0, min(cursor, attributedString.length))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedRangeStored: NSRange = .notFound
|
||||||
|
var markedRangeStored: NSRange = .notFound
|
||||||
|
var markedText: NSAttributedString = .init(string: "")
|
||||||
|
|
||||||
|
func toString() -> String {
|
||||||
|
attributedString.string
|
||||||
|
}
|
||||||
|
|
||||||
|
func clear() {
|
||||||
|
cursor = 0
|
||||||
|
attributedString = .init()
|
||||||
|
}
|
||||||
|
|
||||||
|
func insertText(_ string: Any!, replacementRange: NSRange) {
|
||||||
|
guard let string = string as? String else { return }
|
||||||
|
var insertionPoint = replacementRange.location
|
||||||
|
if insertionPoint == NSNotFound {
|
||||||
|
insertionPoint = cursor
|
||||||
|
}
|
||||||
|
cursor = insertionPoint
|
||||||
|
attributedString.insert(.init(string: string), at: cursor)
|
||||||
|
cursor += string.utf16.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func setMarkedText(_ string: Any!, selectionRange _: NSRange, replacementRange: NSRange) {
|
||||||
|
markedText = string as? NSAttributedString ?? .init(string: string as? String ?? "")
|
||||||
|
var insertionPoint = replacementRange.location
|
||||||
|
if insertionPoint == NSNotFound {
|
||||||
|
insertionPoint = cursor
|
||||||
|
}
|
||||||
|
cursor = insertionPoint
|
||||||
|
}
|
||||||
|
|
||||||
|
func selectedRange() -> NSRange {
|
||||||
|
NSIntersectionRange(selectedRangeStored, .init(location: 0, length: attributedString.length))
|
||||||
|
}
|
||||||
|
|
||||||
|
func markedRange() -> NSRange {
|
||||||
|
NSIntersectionRange(markedRangeStored, .init(location: 0, length: attributedString.length))
|
||||||
|
}
|
||||||
|
|
||||||
|
func attributedSubstring(from range: NSRange) -> NSAttributedString! {
|
||||||
|
let usableRange = NSIntersectionRange(range, .init(location: 0, length: attributedString.length))
|
||||||
|
return attributedString.attributedSubstring(from: usableRange)
|
||||||
|
}
|
||||||
|
|
||||||
|
func length() -> Int {
|
||||||
|
attributedString.length
|
||||||
|
}
|
||||||
|
|
||||||
|
func characterIndex(for _: NSPoint, tracking _: IMKLocationToOffsetMappingMode, inMarkedRange _: UnsafeMutablePointer<ObjCBool>!) -> Int {
|
||||||
|
cursor
|
||||||
|
}
|
||||||
|
|
||||||
|
func attributes(forCharacterIndex _: Int, lineHeightRectangle _: UnsafeMutablePointer<NSRect>!) -> [AnyHashable: Any]! {
|
||||||
|
[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func validAttributesForMarkedText() -> [Any]! {
|
||||||
|
[]
|
||||||
|
}
|
||||||
|
|
||||||
|
func overrideKeyboard(withKeyboardNamed keyboardUniqueName: String!) {
|
||||||
|
_ = keyboardUniqueName
|
||||||
|
}
|
||||||
|
|
||||||
|
func selectMode(_ modeIdentifier: String!) {
|
||||||
|
_ = modeIdentifier
|
||||||
|
}
|
||||||
|
|
||||||
|
func supportsUnicode() -> Bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
func bundleIdentifier() -> String {
|
||||||
|
"org.atelierInmu.vChewing.MainAssembly.UnitTests.MockedClient"
|
||||||
|
}
|
||||||
|
|
||||||
|
func windowLevel() -> CGWindowLevel {
|
||||||
|
CGShieldingWindowLevel()
|
||||||
|
}
|
||||||
|
|
||||||
|
func supportsProperty(_: TSMDocumentPropertyTag) -> Bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
func uniqueClientIdentifierString() -> String {
|
||||||
|
bundleIdentifier()
|
||||||
|
}
|
||||||
|
|
||||||
|
func string(from range: NSRange, actualRange: NSRangePointer!) -> String! {
|
||||||
|
let actualNSRange = actualRange.move()
|
||||||
|
var usableRange = NSIntersectionRange(actualNSRange, range)
|
||||||
|
usableRange = NSIntersectionRange(usableRange, .init(location: 0, length: attributedString.length))
|
||||||
|
return attributedString.attributedSubstring(from: usableRange).string
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstRect(forCharacterRange _: NSRange, actualRange _: NSRangePointer!) -> NSRect {
|
||||||
|
.zero
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||||
|
// ====================
|
||||||
|
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||||
|
// ... with NTL restriction stating that:
|
||||||
|
// 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 defined in MIT License.
|
||||||
|
|
||||||
|
import AppKit
|
||||||
|
|
||||||
|
public extension NSEvent {
|
||||||
|
struct KeyEventData {
|
||||||
|
public var type: EventType = .keyDown
|
||||||
|
public var flags: ModifierFlags
|
||||||
|
public var chars: String
|
||||||
|
public var charsSansModifiers: String
|
||||||
|
public var keyCode: UInt16
|
||||||
|
|
||||||
|
public init(type: EventType = .keyDown, flags: ModifierFlags = [], chars: String, charsSansModifiers: String? = nil, keyCode: UInt16? = nil) {
|
||||||
|
self.type = type
|
||||||
|
self.flags = flags
|
||||||
|
self.chars = chars
|
||||||
|
self.charsSansModifiers = charsSansModifiers ?? chars
|
||||||
|
self.keyCode = keyCode ?? mapKeyCodesANSIForTests[chars] ?? 65535
|
||||||
|
}
|
||||||
|
|
||||||
|
public func toEvents(paired: Bool = false) -> [NSEvent] {
|
||||||
|
NSEvent.keyEvents(data: self, paired: paired)
|
||||||
|
}
|
||||||
|
|
||||||
|
public var asPairedEvents: [NSEvent] {
|
||||||
|
NSEvent.keyEvents(data: self, paired: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
public var asEvent: NSEvent? {
|
||||||
|
NSEvent.keyEvent(data: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static func keyEvents(data: KeyEventData, paired: Bool = false) -> [NSEvent] {
|
||||||
|
var resultArray = [NSEvent]()
|
||||||
|
if let eventA: NSEvent = Self.keyEvent(data: data) {
|
||||||
|
resultArray.append(eventA)
|
||||||
|
if paired, eventA.type == .keyDown,
|
||||||
|
let eventB = eventA.reinitiate(with: .keyUp, characters: nil, charactersIgnoringModifiers: nil)
|
||||||
|
{
|
||||||
|
resultArray.append(eventB)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resultArray
|
||||||
|
}
|
||||||
|
|
||||||
|
static func keyEvent(data: KeyEventData) -> NSEvent? {
|
||||||
|
Self.keyEventSimple(type: data.type, flags: data.flags, chars: data.chars, charsSansModifiers: data.charsSansModifiers, keyCode: data.keyCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func keyEventSimple(type: EventType, flags: ModifierFlags, chars: String, charsSansModifiers: String? = nil, keyCode: UInt16) -> NSEvent? {
|
||||||
|
Self.keyEvent(
|
||||||
|
with: type, location: .zero, modifierFlags: flags, timestamp: .init(),
|
||||||
|
windowNumber: 0, context: nil, characters: chars,
|
||||||
|
charactersIgnoringModifiers: charsSansModifiers ?? chars, isARepeat: false, keyCode: keyCode
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +0,0 @@
|
||||||
@testable import MainAssembly
|
|
||||||
import XCTest
|
|
||||||
|
|
||||||
final class MainAssemblyTests: XCTestCase {
|
|
||||||
override func setUpWithError() throws {
|
|
||||||
UserDefaults.unitTests = .init(suiteName: "org.atelierInmu.vChewing.MainAssembly.UnitTests")
|
|
||||||
UserDefaults.pendingUnitTests = true
|
|
||||||
}
|
|
||||||
|
|
||||||
override func tearDownWithError() throws {
|
|
||||||
UserDefaults.unitTests?.removeSuite(named: "org.atelierInmu.vChewing.MainAssembly.UnitTests")
|
|
||||||
}
|
|
||||||
|
|
||||||
func testExample() throws {}
|
|
||||||
}
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||||
|
// ====================
|
||||||
|
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||||
|
// ... with NTL restriction stating that:
|
||||||
|
// 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 defined in MIT License.
|
||||||
|
|
||||||
|
import CocoaExtension
|
||||||
|
import InputMethodKit
|
||||||
|
import LangModelAssembly
|
||||||
|
@testable import MainAssembly
|
||||||
|
import Shared
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
let testClient = FakeClient()
|
||||||
|
|
||||||
|
public extension SessionCtl {
|
||||||
|
override func client() -> (IMKTextInput & NSObjectProtocol)! { testClient }
|
||||||
|
}
|
||||||
|
|
||||||
|
func vCTestLog(_ str: String) {
|
||||||
|
print("[VCLOG] \(str)")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 威注音輸入法的控制模組單元測試。
|
||||||
|
/// - Remark: 歡迎來到威注音輸入法的控制模組單元測試。
|
||||||
|
///
|
||||||
|
/// 不似其他同類產品的單元測試,威注音輸入法的單元測試
|
||||||
|
/// 會盡量模擬使用者的日常打字擊鍵行為與使用方法。
|
||||||
|
/// 單元測試的內容可能算不上豐富,但會隨著今後來自各位
|
||||||
|
/// 使用者所提報的故障、而繼續逐漸擴大測試範圍。
|
||||||
|
///
|
||||||
|
/// 該單元測試使用獨立的語彙資料,因此會在選字時的候選字
|
||||||
|
/// 順序等方面與威注音輸入法實際使用時的體驗有差異。
|
||||||
|
class MainAssemblyTests: XCTestCase {
|
||||||
|
let testUOM = vChewingLM.LMUserOverride(dataURL: .init(fileURLWithPath: "/dev/null"))
|
||||||
|
var testLM = vChewingLM.LMInstantiator.construct { $0.loadTestData() }
|
||||||
|
static let testServer = IMKServer(name: "org.atelierInmu.vChewing.MainAssembly.UnitTests_Connection", bundleIdentifier: "org.atelierInmu.vChewing.MainAssembly.UnitTests")
|
||||||
|
|
||||||
|
static var _testHandler: InputHandler?
|
||||||
|
var testHandler: InputHandler {
|
||||||
|
let result = Self._testHandler ?? InputHandler(lm: testLM, uom: testUOM, pref: PrefMgr.shared)
|
||||||
|
if Self._testHandler == nil { Self._testHandler = result }
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
static var _testSession: SessionCtl?
|
||||||
|
var testSession: SessionCtl {
|
||||||
|
guard let session = Self._testSession ?? SessionCtl(server: Self.testServer, delegate: testHandler, client: testClient) else {
|
||||||
|
fatalError("Session failed from booting!")
|
||||||
|
}
|
||||||
|
if Self._testSession == nil { Self._testSession = session }
|
||||||
|
return session
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Utilities
|
||||||
|
|
||||||
|
let dataArrowLeft = NSEvent.KeyEventData(chars: NSEvent.SpecialKey.leftArrow.unicodeScalar.description, keyCode: KeyCode.kLeftArrow.rawValue)
|
||||||
|
let dataArrowDown = NSEvent.KeyEventData(chars: NSEvent.SpecialKey.downArrow.unicodeScalar.description, keyCode: KeyCode.kDownArrow.rawValue)
|
||||||
|
let dataEnterReturn = NSEvent.KeyEventData(chars: NSEvent.SpecialKey.carriageReturn.unicodeScalar.description, keyCode: KeyCode.kLineFeed.rawValue)
|
||||||
|
let dataTab = NSEvent.KeyEventData(chars: NSEvent.SpecialKey.tab.unicodeScalar.description, keyCode: KeyCode.kTab.rawValue)
|
||||||
|
|
||||||
|
func clearTestUOM() {
|
||||||
|
testUOM.clearData(withURL: URL(fileURLWithPath: "/dev/null"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func typeSentenceOrCandidates(_ sequence: String) {
|
||||||
|
if !([.ofEmpty, .ofInputting].contains(testSession.state.type) || testSession.state.isCandidateContainer) { return }
|
||||||
|
let typingSequence: [NSEvent] = sequence.compactMap { charRAW in
|
||||||
|
var finalArray = [NSEvent]()
|
||||||
|
let char = charRAW.description
|
||||||
|
let keyEventData = NSEvent.KeyEventData(chars: char)
|
||||||
|
finalArray.append(contentsOf: keyEventData.asPairedEvents)
|
||||||
|
return finalArray
|
||||||
|
}.flatMap { $0 }
|
||||||
|
typingSequence.forEach { theEvent in
|
||||||
|
let dismissed = !testSession.handle(theEvent, client: testClient)
|
||||||
|
if theEvent.type == .keyDown { XCTAssertFalse(dismissed) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Preparing Unit Tests.
|
||||||
|
|
||||||
|
override func setUpWithError() throws {
|
||||||
|
UserDefaults.unitTests = .init(suiteName: "org.atelierInmu.vChewing.MainAssembly.UnitTests")
|
||||||
|
UserDef.resetAll()
|
||||||
|
UserDefaults.pendingUnitTests = true
|
||||||
|
testSession.activateServer(testClient)
|
||||||
|
testSession.isActivated = true
|
||||||
|
testSession.inputHandler = testHandler
|
||||||
|
testHandler.delegate = testSession
|
||||||
|
testSession.syncBaseLMPrefs()
|
||||||
|
testClient.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tearDownWithError() throws {
|
||||||
|
testSession.switchState(IMEState.ofAbortion())
|
||||||
|
UserDefaults.unitTests?.removeSuite(named: "org.atelierInmu.vChewing.MainAssembly.UnitTests")
|
||||||
|
UserDef.resetAll()
|
||||||
|
testClient.clear()
|
||||||
|
testSession.deactivateServer(testClient)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,154 @@
|
||||||
|
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||||
|
// ====================
|
||||||
|
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||||
|
// ... with NTL restriction stating that:
|
||||||
|
// 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 defined in MIT License.
|
||||||
|
|
||||||
|
import CocoaExtension
|
||||||
|
import InputMethodKit
|
||||||
|
import LangModelAssembly
|
||||||
|
@testable import MainAssembly
|
||||||
|
import Shared
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
extension MainAssemblyTests {
|
||||||
|
func test001_ClientTest_BundleIdentifier() throws {
|
||||||
|
guard let identifier = testSession.client()?.bundleIdentifier() else {
|
||||||
|
fatalError("致命錯誤:客體唯一標幟碼無效。")
|
||||||
|
}
|
||||||
|
vCTestLog("測試客體唯一標幟碼:\(identifier)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func test002_ClientTest_TextInsertion() throws {
|
||||||
|
testClient.clear()
|
||||||
|
let testString = UUID().uuidString
|
||||||
|
testSession.client().insertText(testString, replacementRange: .notFound)
|
||||||
|
XCTAssertEqual(testClient.attributedString.string, testString)
|
||||||
|
testClient.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Input Handler Tests.
|
||||||
|
|
||||||
|
/// 測試基本的打字組句(不是ㄅ半注音)。
|
||||||
|
func test101_InputHandler_BasicSentenceComposition() throws {
|
||||||
|
PrefMgr.shared.useSCPCTypingMode = false
|
||||||
|
clearTestUOM()
|
||||||
|
vCTestLog("測試組句:高科技公司的年中獎金")
|
||||||
|
testSession.resetInputHandler(forceComposerCleanup: true)
|
||||||
|
typeSentenceOrCandidates("el dk ru4ej/ n 2k7su065j/ ru;3rup ")
|
||||||
|
let resultText1 = testSession.state.displayedText
|
||||||
|
vCTestLog("- // 組字結果:\(resultText1)")
|
||||||
|
XCTAssertEqual(resultText1, "高科技公司的年中獎金")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 測試基本的逐字選字(ㄅ半注音)。
|
||||||
|
func test102_InputHandler_BasicSCPCTyping() throws {
|
||||||
|
PrefMgr.shared.useSCPCTypingMode = true
|
||||||
|
clearTestUOM()
|
||||||
|
vCTestLog("測試逐字選字:高科技公司的年中獎金")
|
||||||
|
testSession.resetInputHandler(forceComposerCleanup: true)
|
||||||
|
typeSentenceOrCandidates("el dk ru44ej/ 2n ")
|
||||||
|
dataArrowDown.asPairedEvents.forEach { theEvent in
|
||||||
|
let dismissed = !testSession.handle(theEvent, client: testClient)
|
||||||
|
if theEvent.type == .keyDown { XCTAssertFalse(dismissed) }
|
||||||
|
}
|
||||||
|
typeSentenceOrCandidates("12k7su065j/ ru;3rup ")
|
||||||
|
XCTAssert(testSession.candidateUI?.visible ?? false)
|
||||||
|
dataEnterReturn.asPairedEvents.forEach { theEvent in
|
||||||
|
let dismissed = !testSession.handle(theEvent, client: testClient)
|
||||||
|
if theEvent.type == .keyDown { XCTAssertFalse(dismissed) }
|
||||||
|
}
|
||||||
|
let resultText1 = testClient.toString()
|
||||||
|
vCTestLog("- // 組字結果:\(resultText1)")
|
||||||
|
XCTAssertEqual(resultText1, "高科技公司的年中獎金")
|
||||||
|
testClient.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 測試就地輪替候選字。
|
||||||
|
func test103_InputHandler_RevolvingCandidates() throws {
|
||||||
|
PrefMgr.shared.useSCPCTypingMode = false
|
||||||
|
PrefMgr.shared.useRearCursorMode = false
|
||||||
|
clearTestUOM()
|
||||||
|
|
||||||
|
testSession.resetInputHandler(forceComposerCleanup: true)
|
||||||
|
typeSentenceOrCandidates("el dk ru4ej/ n 2k7su065j/ ru;3rup ")
|
||||||
|
|
||||||
|
// Testing Inline Candidate Revolver.
|
||||||
|
|
||||||
|
vCTestLog("測試就地輪替候選字:高科技公司的年中獎金 -> 高科技公司的年終獎金")
|
||||||
|
|
||||||
|
[dataArrowLeft, dataArrowLeft].map(\.asPairedEvents).flatMap { $0 }.forEach { theEvent in
|
||||||
|
let dismissed = !testSession.handle(theEvent, client: testClient)
|
||||||
|
if theEvent.type == .keyDown { XCTAssertFalse(dismissed) }
|
||||||
|
}
|
||||||
|
|
||||||
|
dataTab.asPairedEvents.forEach { theEvent in
|
||||||
|
let dismissed = !testSession.handle(theEvent, client: testClient)
|
||||||
|
if theEvent.type == .keyDown { XCTAssertFalse(dismissed) }
|
||||||
|
}
|
||||||
|
let resultText2 = testSession.state.displayedText
|
||||||
|
vCTestLog("- // 組字結果:\(resultText2)")
|
||||||
|
XCTAssertEqual(resultText2, "高科技公司的年終獎金")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 測試藉由選字窗選字、且同時測試半衰記憶模組在此情況下的記憶資料生成與適用情況。
|
||||||
|
/// - Remark: 這裡順便測試一下「在選字窗選字後自動推進游標」這個有被預設啟用的功能。
|
||||||
|
func test104_InputHandler_ManualCandidateSelectionAndUOM() throws {
|
||||||
|
PrefMgr.shared.useSCPCTypingMode = false
|
||||||
|
PrefMgr.shared.useRearCursorMode = false
|
||||||
|
PrefMgr.shared.moveCursorAfterSelectingCandidate = true
|
||||||
|
clearTestUOM()
|
||||||
|
|
||||||
|
var sequenceChars = "el dk ru4ej/ n 2k7su065j/ ru;3rup "
|
||||||
|
|
||||||
|
testSession.resetInputHandler(forceComposerCleanup: true)
|
||||||
|
typeSentenceOrCandidates(sequenceChars)
|
||||||
|
|
||||||
|
// Testing Manual Candidate Selection, UOM Observation, and Post-Candidate-Selection Cursor Jumping.
|
||||||
|
|
||||||
|
vCTestLog("測試選字窗選字:高科技公司的年終獎金 -> 高科技公司的年中獎金")
|
||||||
|
let keyOne = NSEvent.KeyEventData(chars: "1")
|
||||||
|
[dataArrowDown, keyOne].map(\.asPairedEvents).flatMap { $0 }.forEach { theEvent in
|
||||||
|
let dismissed = !testSession.handle(theEvent, client: testClient)
|
||||||
|
if theEvent.type == .keyDown { XCTAssertFalse(dismissed) }
|
||||||
|
}
|
||||||
|
let resultText3 = testSession.state.displayedText
|
||||||
|
vCTestLog("- // 組字結果:\(resultText3)")
|
||||||
|
XCTAssertEqual(resultText3, "高科技公司的年中獎金")
|
||||||
|
XCTAssertEqual(testHandler.compositor.cursor, 10)
|
||||||
|
|
||||||
|
// Continuing UOM Tests (in the Current Context).
|
||||||
|
|
||||||
|
vCTestLog("測試半衰記憶的適用範圍:「年終」的記憶應僅對下述給定上下文情形生效。")
|
||||||
|
vCTestLog("- 該給定上下文情形為「((ㄍㄨㄥ-ㄙ,公司),(ㄉㄜ˙,的),ㄋㄧㄢˊ-ㄓㄨㄥ)」。")
|
||||||
|
clearTestUOM()
|
||||||
|
let keyTwo = NSEvent.KeyEventData(chars: "2")
|
||||||
|
[dataArrowLeft, dataArrowLeft, dataArrowDown, keyTwo].map(\.asPairedEvents).flatMap { $0 }.forEach { theEvent in
|
||||||
|
let dismissed = !testSession.handle(theEvent, client: testClient)
|
||||||
|
if theEvent.type == .keyDown { XCTAssertFalse(dismissed) }
|
||||||
|
}
|
||||||
|
let resultText4 = testSession.state.displayedText
|
||||||
|
vCTestLog("- // 組字結果:\(resultText4)")
|
||||||
|
XCTAssertEqual(resultText4, "高科技公司的年終獎金")
|
||||||
|
|
||||||
|
vCTestLog("- 清空組字區,重新打剛才那句話來測試。")
|
||||||
|
testSession.switchState(IMEState.ofAbortion())
|
||||||
|
typeSentenceOrCandidates(sequenceChars)
|
||||||
|
let resultText5 = testSession.state.displayedText
|
||||||
|
vCTestLog("- // 組字結果:\(resultText5)")
|
||||||
|
XCTAssertEqual(resultText5, "高科技公司的年終獎金")
|
||||||
|
vCTestLog("- 已成功證實「年終」的記憶對該給定上下文情形生效。")
|
||||||
|
|
||||||
|
vCTestLog("- 清空組字區,重新打另一句話來測試。")
|
||||||
|
testSession.switchState(IMEState.ofAbortion())
|
||||||
|
|
||||||
|
sequenceChars = "ru4ej/ 2k7su065j/ ru;3rup "
|
||||||
|
typeSentenceOrCandidates(sequenceChars)
|
||||||
|
let resultText6 = testSession.state.displayedText
|
||||||
|
vCTestLog("- // 組字結果:\(resultText6)")
|
||||||
|
XCTAssertEqual(resultText6, "濟公的年中獎金")
|
||||||
|
vCTestLog("- 已成功證實「年終」的記憶不會對除了給定上下文以外的情形生效。")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue