MainAssembly // Implementing Unit Tests.

* Further unit tests will be implemented later according to other necessities.
This commit is contained in:
ShikiSuen 2023-08-25 03:16:57 +08:00
parent f81e6837b8
commit 136e8088be
8 changed files with 635 additions and 15 deletions

View File

@ -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,
]

View File

@ -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"
)
)
}
}

View File

@ -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
"""#

View File

@ -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
}
}

View File

@ -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
)
}
}

View File

@ -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 {}
}

View File

@ -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)
}
}

View File

@ -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("- 已成功證實「年終」的記憶不會對除了給定上下文以外的情形生效。")
}
}