checkpoint before checking out main

This commit is contained in:
Rocky
2026-08-25 13:42:00 +08:00
parent 57844ce615
commit dccafe6f9e
105 changed files with 4302 additions and 1323 deletions
@@ -84,6 +84,69 @@ final class CandidatePanelExpandTests: XCTestCase {
XCTAssertFalse(typing.isCandidatePanelExpanded)
XCTAssertTrue(typing.composition.candidates.isEmpty)
}
func testEndingDocumentPresentationClearsChineseStateWithoutTeardown() {
let engine = StubRimeEngine()
let typing = TypingSessionController(engine: { engine })
_ = typing.beginDocumentPresentation()
_ = typing.handleKey("n")
XCTAssertFalse(typing.composition.candidates.isEmpty)
typing.endDocumentPresentation()
XCTAssertTrue(typing.composition.candidates.isEmpty)
XCTAssertEqual(engine.teardownCallCount, 0)
XCTAssertEqual(engine.clearCompositionCallCount, 1)
}
func testCandidateSelectionRejectsReplacedSnapshot() {
let engine = StubRimeEngine()
let typing = TypingSessionController(engine: { engine })
_ = typing.handleKey("n")
guard let staleCandidateID = typing.composition.candidates.first?.id else {
return XCTFail("expected candidate")
}
let staleRevision = typing.candidateRevision
_ = typing.handleKey("i")
let output = typing.selectCandidate(
id: staleCandidateID,
candidateRevision: staleRevision
)
XCTAssertEqual(output, .none)
XCTAssertFalse(typing.composition.candidates.isEmpty)
}
func testPrepareContinuesWhenHostBecomesHeavyBeforeTaskStarts() async {
let hostState = HostHeavyState()
let engine = StubRimeEngine()
let prepared = expectation(description: "engine prepared")
engine.onPrepare = { prepared.fulfill() }
let typing = TypingSessionController(
engine: { engine },
hostHeavyProvider: { hostState.isHeavy }
)
typing.enterTypingMode()
hostState.isHeavy = true
await Task.yield()
XCTAssertTrue(typing.isPreparingEngine)
XCTAssertEqual(engine.prepareCallCount, 0)
hostState.isHeavy = false
await fulfillment(of: [prepared], timeout: 1)
await Task.yield()
XCTAssertEqual(engine.prepareCallCount, 1)
XCTAssertTrue(typing.engineReady)
XCTAssertFalse(typing.isPreparingEngine)
}
}
@MainActor
private final class HostHeavyState {
var isHeavy = false
}
// Minimal engine that returns a two-candidate snapshot after any letter.
@@ -94,9 +157,17 @@ private final class StubRimeEngine: RimeEngineBridging {
var schema: TypingInputSchema = .fullPinyin
/// Optional override applied on the next processBackspace.
var nextComposition: TypingComposition?
var teardownCallCount = 0
var clearCompositionCallCount = 0
var prepareCallCount = 0
var onPrepare: (() -> Void)?
func prepare() async throws {}
func prepare() async throws {
prepareCallCount += 1
onPrepare?()
}
func teardown() {
teardownCallCount += 1
composition = .empty
}
@@ -116,8 +187,8 @@ private final class StubRimeEngine: RimeEngineBridging {
composition = TypingComposition(
preedit: String(character),
candidates: [
TypingCandidate(text: ""),
TypingCandidate(text: "")
TypingCandidate(id: "first", text: ""),
TypingCandidate(id: "second", text: "")
]
)
return nil
@@ -158,6 +229,7 @@ private final class StubRimeEngine: RimeEngineBridging {
}
func clearComposition() {
clearCompositionCallCount += 1
composition = .empty
}
}
+142 -1
View File
@@ -179,6 +179,79 @@ final class EnglishTypingTests: XCTestCase {
XCTAssertEqual(typing.composition.preedit, "h")
}
@MainActor
func testNewDocumentPresentationRehydratesEnglishContext() {
var preceding = "hel"
let typing = makeTypingSession()
typing.precedingTextProvider = { preceding }
typing.followingTextProvider = { "" }
typing.enterTypingMode()
let firstPresentation = typing.beginDocumentPresentation()
typing.synchronizeEnglishDocumentContext(
caretMoved: true,
presentationID: firstPresentation
)
XCTAssertEqual(typing.composition.preedit.lowercased(), "hel")
XCTAssertFalse(typing.composition.candidates.isEmpty)
typing.endDocumentPresentation()
XCTAssertTrue(typing.composition.preedit.isEmpty)
XCTAssertTrue(typing.composition.candidates.isEmpty)
preceding = "wor"
let secondPresentation = typing.beginDocumentPresentation()
typing.synchronizeEnglishDocumentContext(
caretMoved: true,
presentationID: firstPresentation
)
XCTAssertTrue(typing.composition.preedit.isEmpty)
XCTAssertTrue(typing.composition.candidates.isEmpty)
typing.synchronizeEnglishDocumentContext(
caretMoved: true,
presentationID: secondPresentation
)
XCTAssertEqual(typing.composition.preedit.lowercased(), "wor")
XCTAssertFalse(typing.composition.candidates.isEmpty)
}
@MainActor
func testEndingDocumentPresentationClearsLocalEnglishShadow() {
var preceding = ""
let typing = makeTypingSession()
typing.precedingTextProvider = { preceding }
typing.followingTextProvider = { "" }
typing.enterTypingMode()
_ = typing.beginDocumentPresentation()
_ = typing.handleKey("h")
XCTAssertEqual(typing.composition.preedit.lowercased(), "h")
typing.endDocumentPresentation()
preceding = "new"
let nextPresentation = typing.beginDocumentPresentation()
typing.synchronizeEnglishDocumentContext(
caretMoved: true,
presentationID: nextPresentation
)
XCTAssertEqual(typing.composition.preedit.lowercased(), "new")
}
@MainActor
func testDisablingSuggestionsImmediatelyClearsEnglishCandidates() {
let typing = makeTypingSession()
typing.enterTypingMode()
_ = typing.handleKey("h")
XCTAssertFalse(typing.composition.candidates.isEmpty)
typing.suggestionsEnabled = false
XCTAssertTrue(typing.composition.preedit.isEmpty)
XCTAssertTrue(typing.composition.candidates.isEmpty)
}
func testEnglishTypingHotwordsFilterOutChineseTerms() {
var dictionary = PersonalDictionary()
_ = dictionary.upsertManual(term: "张三")
@@ -541,6 +614,70 @@ final class EnglishTypingTests: XCTestCase {
}
}
@MainActor
func testRepeatedRareEnglishCommitBecomesDictionarySuggestion() {
let suite = "english.frequent.term.test.\(UUID().uuidString)"
let defaults = UserDefaults(suiteName: suite)!
defaults.removePersistentDomain(forName: suite)
let frequentStore = FrequentTermStore(defaults: defaults)
let typing = makeTypingSession(frequentTermStore: frequentStore)
typing.enterTypingMode()
for _ in 0..<2 {
for character in "OpenAI" {
_ = typing.handleKey(String(character))
}
let output = typing.handleSpace()
XCTAssertEqual(output.deleteCount, 0)
}
let storedData = defaults.data(forKey: FrequentTermStore.defaultsKey)
let storedTerms = storedData.flatMap { try? JSONDecoder().decode([FrequentTerm].self, from: $0) }
XCTAssertEqual(storedTerms?.first?.commitCount, 2)
let suggestion = frequentStore.suggestions(excludingPersonalTerms: []).first
XCTAssertEqual(suggestion?.term, "OpenAI")
XCTAssertEqual(suggestion?.commitCount, 2)
}
@MainActor
func testCommonEnglishCommitDoesNotBecomeDictionarySuggestion() {
let suite = "english.frequent.common.test.\(UUID().uuidString)"
let defaults = UserDefaults(suiteName: suite)!
defaults.removePersistentDomain(forName: suite)
let frequentStore = FrequentTermStore(defaults: defaults)
let typing = makeTypingSession(frequentTermStore: frequentStore)
typing.enterTypingMode()
for _ in 0..<2 {
for character in "the" {
_ = typing.handleKey(String(character))
}
_ = typing.handleSpace()
}
XCTAssertTrue(frequentStore.suggestions(excludingPersonalTerms: []).isEmpty)
}
@MainActor
func testSecureEnglishCommitDoesNotBecomeDictionarySuggestion() {
let suite = "english.frequent.secure.test.\(UUID().uuidString)"
let defaults = UserDefaults(suiteName: suite)!
defaults.removePersistentDomain(forName: suite)
let frequentStore = FrequentTermStore(defaults: defaults)
let typing = makeTypingSession(frequentTermStore: frequentStore)
typing.enterTypingMode()
typing.suggestionsEnabled = false
for _ in 0..<2 {
for character in "OpenAI" {
_ = typing.handleKey(String(character))
}
_ = typing.handleSpace()
}
XCTAssertTrue(frequentStore.suggestions(excludingPersonalTerms: []).isEmpty)
}
func testQWERTYNeighborsIncludeDiagonals() {
let aroundG = EnglishQWERTYProximity.neighbors(of: "g", includingSelf: true)
XCTAssertTrue(aroundG.contains("t"))
@@ -570,6 +707,7 @@ final class EnglishTypingTests: XCTestCase {
@MainActor
private func makeTypingSession(
learningStore: EnglishLearningStore? = nil,
frequentTermStore: FrequentTermStore? = nil,
language: TypingInputLanguage = .english
) -> TypingSessionController {
let store = learningStore ?? EnglishLearningStore(
@@ -577,7 +715,10 @@ final class EnglishTypingTests: XCTestCase {
)
let typing = TypingSessionController(
engine: { NoopRimeEngine() },
learningStore: store
learningStore: store,
frequentTermStore: frequentTermStore ?? FrequentTermStore(
defaults: UserDefaults(suiteName: "frequent.tests.\(UUID().uuidString)")!
)
)
typing.suggestionsEnabled = true
_ = typing.setLanguage(language)
@@ -36,6 +36,7 @@ final class KeyboardStateTests: XCTestCase {
XCTAssertEqual(s.level, 0)
XCTAssertFalse(s.onDeviceSupported)
XCTAssertFalse(s.undoAvailable)
XCTAssertFalse(s.hasFullAccess)
}
func testPhaseTransitionsIdleToRequestingPermissionsAndBack() {
@@ -145,6 +146,20 @@ final class KeyboardStateTests: XCTestCase {
XCTAssertEqual(state.clipboardOverlay, .none)
}
func testClipboardFullAccessHintTracksCaptureAvailability() {
let state = KeyboardState()
state.clipboardHistoryEnabled = true
XCTAssertTrue(state.needsClipboardFullAccessHint)
state.hasFullAccess = true
XCTAssertFalse(state.needsClipboardFullAccessHint)
state.hasFullAccess = false
state.clipboardHistoryEnabled = false
XCTAssertFalse(state.needsClipboardFullAccessHint)
}
func testResolvedSkillCopyPublishesWhenIDsStayStable() {
let state = KeyboardState()
state.enabledClipboardSkillIDs = ["official.rewrite"]