Expand onboarding and adaptive keyboard intelligence

Add resilient usage analytics, OOBE gateway flows, clipboard semantic ranking, purchase recovery, style learning, and managed current-information search.
This commit is contained in:
Rocky
2026-08-22 16:33:18 +08:00
parent ac374631ae
commit e5a83843db
162 changed files with 23574 additions and 1013 deletions
@@ -0,0 +1,193 @@
// KeyboardUsageModelTests.swift
// OSGKeyboardTests
//
// Unicode classification, fixed insertion sources and wire privacy boundaries.
import Foundation
@testable import OSGKeyboardShared
import XCTest
final class KeyboardUsageModelTests: XCTestCase {
func testHanClassificationCoversSimplifiedTraditionalAndExtensionIdeographs() {
let counts = KeyboardUsageCharacterClassifier.classify("汉漢𠀀")
XCTAssertEqual(counts.chinese, 3)
XCTAssertEqual(counts.english, 0)
XCTAssertEqual(counts.other, 0)
XCTAssertEqual(counts.total, 3)
}
func testLatinLettersAndOtherCharactersUseExtendedGraphemeCounts() {
let counts = KeyboardUsageCharacterClassifier.classify(
"AzéÅ 12,.🙂👨‍👩‍👧‍👦\n"
)
XCTAssertEqual(counts.chinese, 0)
XCTAssertEqual(counts.english, 4)
// Space, two digits, comma, period, two Emoji graphemes and newline.
XCTAssertEqual(counts.other, 8)
XCTAssertEqual(counts.total, 12)
}
func testHanTakesPrecedenceWhenOneGraphemeContainsMultipleScripts() {
let counts = KeyboardUsageCharacterClassifier.classify("\u{FE0F}")
XCTAssertEqual(counts, KeyboardUsageCharacterCounts(chinese: 1))
}
func testOnlyManualKeyboardSourceContributes() {
XCTAssertTrue(
KeyboardTextInsertionSource.manualKeyboard.contributesToKeyboardUsage
)
for source in KeyboardTextInsertionSource.allCases
where source != .manualKeyboard {
XCTAssertFalse(
source.contributesToKeyboardUsage,
"\(source.rawValue) must remain excluded"
)
}
}
func testSummaryEncodingHasOnlyNumericDateVersionAndUUIDFields() throws {
let summary = try makeSummary()
let request = KeyboardUsageUploadRequest(
installationId: analyticsTestUUID(1),
summaries: [summary]
)
let data = try JSONEncoder().encode(request)
let root = try XCTUnwrap(
JSONSerialization.jsonObject(with: data) as? [String: Any]
)
XCTAssertEqual(Set(root.keys), ["installationId", "summaries"])
let encodedSummary = try XCTUnwrap(
(root["summaries"] as? [[String: Any]])?.first
)
XCTAssertEqual(
Set(encodedSummary.keys),
[
"clientSummaryId",
"summaryDate",
"chineseCharacterCount",
"englishCharacterCount",
"otherCharacterCount",
"inputSessionCount",
"chineseOnlySessionCount",
"englishOnlySessionCount",
"mixedLanguageSessionCount",
"otherOnlySessionCount",
"appVersion",
"osVersion"
]
)
for forbidden in [
"text",
"pinyin",
"candidate",
"context",
"hostApp",
"bundleId",
"transcript",
"prompt",
"clipboard",
"properties"
] {
XCTAssertFalse(encodedSummary.keys.contains(forbidden))
}
}
func testSummaryRejectsUnknownFieldsAndInvalidSessionPartition() throws {
var object = try XCTUnwrap(
JSONSerialization.jsonObject(
with: JSONEncoder().encode(try makeSummary())
) as? [String: Any]
)
object["rawText"] = "never allowed"
XCTAssertThrowsError(
try JSONDecoder().decode(
KeyboardUsageSummary.self,
from: JSONSerialization.data(withJSONObject: object)
)
) { error in
guard case KeyboardUsageModelError.unknownField("rawText") = error else {
return XCTFail("Unexpected error: \(error)")
}
}
XCTAssertThrowsError(
try KeyboardUsageSummary(
clientSummaryId: analyticsTestUUID(3),
summaryDate: "2026-08-20",
chineseCharacterCount: 1,
englishCharacterCount: 0,
otherCharacterCount: 0,
inputSessionCount: 2,
chineseOnlySessionCount: 1,
englishOnlySessionCount: 0,
mixedLanguageSessionCount: 0,
otherOnlySessionCount: 0,
appVersion: "2.0.0",
osVersion: "26.0"
)
)
}
func testCounterAndVersionBoundariesAreEnforced() throws {
XCTAssertEqual(
KeyboardUsageCharacterCounts(chinese: Int.max).chinese,
1_000_000
)
XCTAssertEqual(
KeyboardUsageCharacterCounts(english: -1).english,
0
)
XCTAssertNoThrow(
try KeyboardUsageSummary(
clientSummaryId: analyticsTestUUID(4),
summaryDate: "2026-08-20",
chineseCharacterCount: 1_000_000,
englishCharacterCount: 1_000_000,
otherCharacterCount: 1_000_000,
inputSessionCount: 100_000,
chineseOnlySessionCount: 25_000,
englishOnlySessionCount: 25_000,
mixedLanguageSessionCount: 25_000,
otherOnlySessionCount: 25_000,
appVersion: String(repeating: "a", count: 32),
osVersion: "26.0"
)
)
XCTAssertThrowsError(
try KeyboardUsageSummary(
clientSummaryId: analyticsTestUUID(5),
summaryDate: "2026-08-20",
chineseCharacterCount: 1_000_001,
englishCharacterCount: 0,
otherCharacterCount: 0,
inputSessionCount: 1,
chineseOnlySessionCount: 1,
englishOnlySessionCount: 0,
mixedLanguageSessionCount: 0,
otherOnlySessionCount: 0,
appVersion: "2.0.0",
osVersion: "26.0"
)
)
}
private func makeSummary() throws -> KeyboardUsageSummary {
try KeyboardUsageSummary(
clientSummaryId: analyticsTestUUID(2),
summaryDate: "2026-08-20",
chineseCharacterCount: 2,
englishCharacterCount: 1,
otherCharacterCount: 1,
inputSessionCount: 2,
chineseOnlySessionCount: 1,
englishOnlySessionCount: 0,
mixedLanguageSessionCount: 1,
otherOnlySessionCount: 0,
appVersion: "2.0.0",
osVersion: "26.0"
)
}
}