feat(typing): improve key hit accuracy and bump to 1.6.1
Add gap-filling hit regions, release-to-commit with slide-to-reselect, touch intent offset, and full-pinyin next-key bias; cut release 1.6.1 (build 45).
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
// PinyinNextKeyResolverTests.swift
|
||||
// OSGKeyboard · Ext unit tests
|
||||
//
|
||||
// Phase 4: legal next-key sets and weighted ambiguous hit resolution.
|
||||
|
||||
import XCTest
|
||||
@testable import OSGKeyboardShared
|
||||
|
||||
final class PinyinNextKeyResolverTests: XCTestCase {
|
||||
func testZhongPrefixAllowsG() {
|
||||
let keys = PinyinNextKeyResolver.validNextKeys(
|
||||
rawInput: "zhon",
|
||||
schema: .fullPinyin,
|
||||
language: .chinese,
|
||||
page: .letters
|
||||
)
|
||||
XCTAssertEqual(keys, ["g"])
|
||||
}
|
||||
|
||||
func testCompleteZhongAllowsNewSyllableInitials() {
|
||||
let keys = PinyinNextKeyResolver.validNextKeys(
|
||||
rawInput: "zhong",
|
||||
schema: .fullPinyin,
|
||||
language: .chinese,
|
||||
page: .letters
|
||||
)
|
||||
XCTAssertNotNil(keys)
|
||||
XCTAssertTrue(keys?.contains("g") == true) // zhongguo
|
||||
XCTAssertTrue(keys?.contains("w") == true)
|
||||
}
|
||||
|
||||
func testMultiSyllableTrailingGAllowsU() {
|
||||
let keys = PinyinNextKeyResolver.validNextKeys(
|
||||
rawInput: "zhongg",
|
||||
schema: .fullPinyin,
|
||||
language: .chinese,
|
||||
page: .letters
|
||||
)
|
||||
XCTAssertTrue(keys?.contains("u") == true) // gu / guo
|
||||
}
|
||||
|
||||
func testNiAllowsExtensionAndNewInitial() {
|
||||
let keys = PinyinNextKeyResolver.validNextKeys(
|
||||
rawInput: "ni",
|
||||
schema: .fullPinyin,
|
||||
language: .chinese,
|
||||
page: .letters
|
||||
)
|
||||
XCTAssertTrue(keys?.contains("a") == true) // nia…
|
||||
XCTAssertTrue(keys?.contains("h") == true) // nihao
|
||||
}
|
||||
|
||||
func testEmptyRawDisablesBias() {
|
||||
let keys = PinyinNextKeyResolver.validNextKeys(
|
||||
rawInput: "",
|
||||
schema: .fullPinyin,
|
||||
language: .chinese,
|
||||
page: .letters
|
||||
)
|
||||
XCTAssertNil(keys)
|
||||
}
|
||||
|
||||
func testEnglishDisablesBias() {
|
||||
let keys = PinyinNextKeyResolver.validNextKeys(
|
||||
rawInput: "ni",
|
||||
schema: .fullPinyin,
|
||||
language: .english,
|
||||
page: .letters
|
||||
)
|
||||
XCTAssertNil(keys)
|
||||
}
|
||||
|
||||
func testDoublePinyinDisablesBias() {
|
||||
let keys = PinyinNextKeyResolver.validNextKeys(
|
||||
rawInput: "nihk",
|
||||
schema: .microsoftDoublePinyin,
|
||||
language: .chinese,
|
||||
page: .letters
|
||||
)
|
||||
XCTAssertNil(keys)
|
||||
}
|
||||
|
||||
func testNumbersPageDisablesBias() {
|
||||
let keys = PinyinNextKeyResolver.validNextKeys(
|
||||
rawInput: "ni",
|
||||
schema: .fullPinyin,
|
||||
language: .chinese,
|
||||
page: .numbers
|
||||
)
|
||||
XCTAssertNil(keys)
|
||||
}
|
||||
|
||||
func testHitWeightsBoostLegalLetters() {
|
||||
let keys = [
|
||||
TypingKeyHitTarget(
|
||||
id: "grid.0.0",
|
||||
label: "G",
|
||||
visualFrame: .zero,
|
||||
behavior: .commitOnRelease
|
||||
),
|
||||
TypingKeyHitTarget(
|
||||
id: "grid.0.1",
|
||||
label: "H",
|
||||
visualFrame: .zero,
|
||||
behavior: .commitOnRelease
|
||||
),
|
||||
TypingKeyHitTarget(
|
||||
id: "grid.0.2",
|
||||
label: "⌫",
|
||||
visualFrame: .zero,
|
||||
behavior: .deleteRepeat
|
||||
)
|
||||
]
|
||||
let weights = PinyinNextKeyResolver.hitWeights(for: keys, validNext: ["g"])
|
||||
XCTAssertEqual(weights["grid.0.0"], KeyHitBiasMetrics.legalBoost)
|
||||
XCTAssertEqual(weights["grid.0.1"], KeyHitBiasMetrics.illegalShrink)
|
||||
XCTAssertNil(weights["grid.0.2"])
|
||||
}
|
||||
|
||||
func testWeightedNearestPrefersLegalKeyInGap() {
|
||||
let left = TypingKeyHitTarget(
|
||||
id: "L",
|
||||
label: "F",
|
||||
visualFrame: CGRect(x: 0, y: 0, width: 40, height: 50),
|
||||
behavior: .commitOnRelease
|
||||
)
|
||||
let right = TypingKeyHitTarget(
|
||||
id: "R",
|
||||
label: "G",
|
||||
visualFrame: CGRect(x: 46, y: 0, width: 40, height: 50),
|
||||
behavior: .commitOnRelease
|
||||
)
|
||||
let plane = left.visualFrame.union(right.visualFrame)
|
||||
// Shared expanded edge (half of 6pt gap) — both frames contain x=43.
|
||||
let point = CGPoint(x: 43, y: 25)
|
||||
let weighted = KeyHitTesting.hitTarget(
|
||||
at: point,
|
||||
targets: [left, right],
|
||||
keyPlaneBounds: plane,
|
||||
horizontalGap: 6,
|
||||
verticalGap: 7,
|
||||
edgeExpansion: 0,
|
||||
hitWeights: [
|
||||
"L": KeyHitBiasMetrics.illegalShrink,
|
||||
"R": KeyHitBiasMetrics.legalBoost
|
||||
]
|
||||
)
|
||||
XCTAssertEqual(weighted?.id, "R")
|
||||
}
|
||||
|
||||
func testClearSingleHitIgnoresBias() {
|
||||
// Point clearly inside F — must not jump to boosted G.
|
||||
let left = TypingKeyHitTarget(
|
||||
id: "L",
|
||||
label: "F",
|
||||
visualFrame: CGRect(x: 0, y: 0, width: 40, height: 50),
|
||||
behavior: .commitOnRelease
|
||||
)
|
||||
let right = TypingKeyHitTarget(
|
||||
id: "R",
|
||||
label: "G",
|
||||
visualFrame: CGRect(x: 46, y: 0, width: 40, height: 50),
|
||||
behavior: .commitOnRelease
|
||||
)
|
||||
let plane = left.visualFrame.union(right.visualFrame)
|
||||
let point = CGPoint(x: 20, y: 25)
|
||||
let hit = KeyHitTesting.hitTarget(
|
||||
at: point,
|
||||
targets: [left, right],
|
||||
keyPlaneBounds: plane,
|
||||
horizontalGap: 6,
|
||||
verticalGap: 7,
|
||||
edgeExpansion: 0,
|
||||
hitWeights: [
|
||||
"L": KeyHitBiasMetrics.illegalShrink,
|
||||
"R": KeyHitBiasMetrics.legalBoost
|
||||
]
|
||||
)
|
||||
XCTAssertEqual(hit?.id, "L")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user