9da32b81e9
Stabilize clipboard long-press prepare/resume across paste alerts and cold start, add an in-app release-notes sheet with remote bilingual HTML, localize typing input settings, and bump build to 55.
216 lines
8.1 KiB
Swift
216 lines
8.1 KiB
Swift
// ClipboardCommandResumeTests.swift
|
|
// OSGKeyboardTests
|
|
|
|
import XCTest
|
|
@testable import OSGKeyboardShared
|
|
|
|
final class ClipboardCommandResumeTests: XCTestCase {
|
|
private var defaults: UserDefaults!
|
|
private var suiteName: String!
|
|
|
|
override func setUp() {
|
|
super.setUp()
|
|
suiteName = "ClipboardCommandResumeTests.\(UUID().uuidString)"
|
|
defaults = UserDefaults(suiteName: suiteName)
|
|
}
|
|
|
|
override func tearDown() {
|
|
if let suiteName {
|
|
defaults?.removePersistentDomain(forName: suiteName)
|
|
}
|
|
defaults = nil
|
|
suiteName = nil
|
|
super.tearDown()
|
|
}
|
|
|
|
func testMarkPreferVoiceSurvivesUntilCleared() {
|
|
XCTAssertFalse(ClipboardCommandResume.shouldPreferVoice(defaults: defaults))
|
|
ClipboardCommandResume.markPreferVoice(defaults: defaults)
|
|
XCTAssertTrue(ClipboardCommandResume.shouldPreferVoice(defaults: defaults))
|
|
ClipboardCommandResume.clear(defaults: defaults)
|
|
XCTAssertFalse(ClipboardCommandResume.shouldPreferVoice(defaults: defaults))
|
|
}
|
|
|
|
func testStoreSnapshotRoundTrip() {
|
|
ClipboardCommandResume.storeSnapshot("周末有空一起吃饭吗?", defaults: defaults)
|
|
XCTAssertTrue(ClipboardCommandResume.shouldPreferVoice(defaults: defaults))
|
|
XCTAssertEqual(
|
|
ClipboardCommandResume.pendingSnapshot(defaults: defaults),
|
|
"周末有空一起吃饭吗?"
|
|
)
|
|
}
|
|
|
|
/// Simulates extension jetsam: writer process flushes, reader process is new.
|
|
func testStickySurvivesNewUserDefaultsInstanceAfterSynchronize() {
|
|
let text = "是AI语音输入法,更是好输入法。It is an AI voice input method."
|
|
ClipboardCommandResume.markPreferVoice(defaults: defaults)
|
|
ClipboardCommandResume.storeSnapshot(text, defaults: defaults)
|
|
|
|
// New UserDefaults handle on the same suite ≈ new extension process.
|
|
let reopened = UserDefaults(suiteName: suiteName!)
|
|
XCTAssertNotNil(reopened)
|
|
guard let reopened else { return }
|
|
|
|
XCTAssertTrue(
|
|
ClipboardCommandResume.shouldPreferVoice(defaults: reopened),
|
|
"Recreated process must still prefer voice after paste-alert jetsam"
|
|
)
|
|
XCTAssertEqual(
|
|
ClipboardCommandResume.pendingSnapshot(defaults: reopened),
|
|
text
|
|
)
|
|
|
|
// And the open-surface policy must then force voice over default typing.
|
|
XCTAssertEqual(
|
|
KeyboardOpenSurfacePolicy.resolve(
|
|
locksTypingSurface: false,
|
|
clipboardCommandActive: false,
|
|
stickyPreferVoice: ClipboardCommandResume.shouldPreferVoice(defaults: reopened),
|
|
preferred: .typing
|
|
),
|
|
.voice
|
|
)
|
|
}
|
|
|
|
func testOpenSurfacePolicyForcesVoiceWhenStickyEvenIfDefaultTyping() {
|
|
let resolved = KeyboardOpenSurfacePolicy.resolve(
|
|
locksTypingSurface: false,
|
|
clipboardCommandActive: false,
|
|
stickyPreferVoice: true,
|
|
preferred: .typing
|
|
)
|
|
XCTAssertEqual(resolved, .voice)
|
|
}
|
|
|
|
func testOpenSurfacePolicyHonorsDefaultTypingWhenNoSticky() {
|
|
let resolved = KeyboardOpenSurfacePolicy.resolve(
|
|
locksTypingSurface: false,
|
|
clipboardCommandActive: false,
|
|
stickyPreferVoice: false,
|
|
preferred: .typing
|
|
)
|
|
XCTAssertEqual(resolved, .typing)
|
|
}
|
|
|
|
func testOpenSurfacePolicyClipboardActiveBeatsTypingDefault() {
|
|
let resolved = KeyboardOpenSurfacePolicy.resolve(
|
|
locksTypingSurface: false,
|
|
clipboardCommandActive: true,
|
|
stickyPreferVoice: false,
|
|
preferred: .typing
|
|
)
|
|
XCTAssertEqual(resolved, .voice)
|
|
}
|
|
|
|
func testPasteAlertReopenDecisionMatrix() {
|
|
// Exact bug from device: default typing + sticky after Allow Paste.
|
|
XCTAssertEqual(
|
|
KeyboardOpenSurfacePolicy.resolve(
|
|
locksTypingSurface: false,
|
|
clipboardCommandActive: false,
|
|
stickyPreferVoice: true,
|
|
preferred: .typing
|
|
),
|
|
.voice,
|
|
"Allow Paste reopen must not land on default Chinese typing grid"
|
|
)
|
|
|
|
// Recording lock also forces voice.
|
|
XCTAssertEqual(
|
|
KeyboardOpenSurfacePolicy.resolve(
|
|
locksTypingSurface: true,
|
|
clipboardCommandActive: false,
|
|
stickyPreferVoice: false,
|
|
preferred: .typing
|
|
),
|
|
.voice
|
|
)
|
|
}
|
|
|
|
func testMarkStartIssuedBlocksDuplicateRoundClaim() {
|
|
let id = UUID()
|
|
XCTAssertFalse(ClipboardCommandResume.hasStartIssued(defaults: defaults))
|
|
ClipboardCommandResume.markStartIssued(id, defaults: defaults)
|
|
XCTAssertTrue(ClipboardCommandResume.hasStartIssued(defaults: defaults))
|
|
XCTAssertEqual(ClipboardCommandResume.startIssuedUtteranceId(defaults: defaults), id)
|
|
XCTAssertTrue(ClipboardCommandResume.shouldPreferVoice(defaults: defaults))
|
|
|
|
let reopened = UserDefaults(suiteName: suiteName!)
|
|
XCTAssertEqual(
|
|
ClipboardCommandResume.startIssuedUtteranceId(defaults: reopened),
|
|
id,
|
|
"Recreated extension must see the already-issued start and not send another"
|
|
)
|
|
|
|
ClipboardCommandResume.clear(defaults: defaults)
|
|
XCTAssertFalse(ClipboardCommandResume.hasStartIssued(defaults: defaults))
|
|
XCTAssertNil(ClipboardCommandResume.startIssuedUtteranceId(defaults: reopened))
|
|
}
|
|
|
|
func testClaimThenClearSurvivesTwentyAlternatingRounds() {
|
|
for round in 1...20 {
|
|
let id = UUID()
|
|
ClipboardCommandResume.storeSnapshot("材料-\(round)", defaults: defaults)
|
|
ClipboardCommandResume.markStartIssued(id, defaults: defaults)
|
|
|
|
let reader = UserDefaults(suiteName: suiteName!)
|
|
XCTAssertEqual(
|
|
ClipboardCommandResume.startIssuedUtteranceId(defaults: reader),
|
|
id,
|
|
"round \(round) claim must survive new defaults handle"
|
|
)
|
|
XCTAssertEqual(
|
|
KeyboardOpenSurfacePolicy.resolve(
|
|
locksTypingSurface: false,
|
|
clipboardCommandActive: false,
|
|
stickyPreferVoice: ClipboardCommandResume.shouldPreferVoice(defaults: reader),
|
|
preferred: .typing
|
|
),
|
|
.voice,
|
|
"round \(round) must stay on voice after paste-alert recreate"
|
|
)
|
|
XCTAssertEqual(
|
|
ClipboardPreparingPolicy.restoreAction(
|
|
hasStartIssued: ClipboardCommandResume.hasStartIssued(defaults: reader),
|
|
phase: .idle
|
|
),
|
|
.awaitExistingStart,
|
|
"round \(round) must not pressBegan again"
|
|
)
|
|
|
|
ClipboardCommandResume.clear(defaults: defaults)
|
|
XCTAssertFalse(
|
|
ClipboardCommandResume.hasStartIssued(defaults: reader),
|
|
"round \(round) clear must drop claim for next independent long-press"
|
|
)
|
|
}
|
|
}
|
|
|
|
func testPreparingTimeoutConstantIsPositive() {
|
|
XCTAssertGreaterThan(ClipboardCommandResume.preparingTimeout, 1)
|
|
XCTAssertLessThanOrEqual(ClipboardCommandResume.preparingTimeout, 15)
|
|
}
|
|
|
|
func testColdStartStickyRestoreIsPreferVoiceOnlyWithoutClaim() {
|
|
ClipboardCommandResume.storeSnapshot("冻结材料", defaults: defaults)
|
|
XCTAssertFalse(ClipboardCommandResume.hasStartIssued(defaults: defaults))
|
|
XCTAssertEqual(
|
|
ClipboardPreparingPolicy.restoreAction(
|
|
hasStartIssued: false,
|
|
phase: .idle
|
|
),
|
|
.preferVoiceOnly
|
|
)
|
|
XCTAssertEqual(
|
|
KeyboardOpenSurfacePolicy.resolve(
|
|
locksTypingSurface: false,
|
|
clipboardCommandActive: false,
|
|
stickyPreferVoice: ClipboardCommandResume.shouldPreferVoice(defaults: defaults),
|
|
preferred: .typing
|
|
),
|
|
.voice,
|
|
"Default typing keyboard must still open voice after cold-start sticky"
|
|
)
|
|
}
|
|
}
|