- record analytics/usage only in the host app; the extension no longer opens the SQLite queues, so hiding the keyboard cannot hold WAL locks (0xdead10cc) - sync clipboard history writes across processes so deletes made on one side are not reverted by the other - guard memory-relief reentrancy on controller and telemetry sides to break the recursive crash loop - resolve credentials off the main thread and migrate legacy keys once per install, so Keychain reads cannot be caught in the suspend window - gate Volcengine ASR ping completion to once (SIGTRAP on double resume) - treat same-text captures as the same logical copy and dedupe auto actions by content fingerprint within 30s, stopping Universal Clipboard storms - realign version line to 2.1.0 (build 110)
156 lines
5.6 KiB
Swift
156 lines
5.6 KiB
Swift
// EnglishKeyboardDeviceUITests.swift
|
|
// OSGKeyboardUITests
|
|
//
|
|
// Notes-like host + the real keyboard extension on device or simulator.
|
|
// Skips only when OSGKeyboard is not enabled as the current keyboard.
|
|
|
|
import XCTest
|
|
|
|
@MainActor
|
|
final class EnglishKeyboardDeviceUITests: XCTestCase {
|
|
func testPiPColdStartsFromUserTap() throws {
|
|
#if targetEnvironment(simulator)
|
|
throw XCTSkip("PiP is unavailable in the iOS Simulator.")
|
|
#else
|
|
continueAfterFailure = false
|
|
let app = XCUIApplication()
|
|
app.launchArguments = ["--pip-device-ui-test"]
|
|
let requestedCycles = ProcessInfo.processInfo.environment["PIP_STRESS_COUNT"]
|
|
.flatMap(Int.init) ?? 30
|
|
let cycles = min(max(requestedCycles, 1), 100)
|
|
|
|
for cycle in 1...cycles {
|
|
app.terminate()
|
|
app.launch()
|
|
|
|
let startButton = app.buttons["pip.start"]
|
|
XCTAssertTrue(
|
|
startButton.waitForExistence(timeout: 10),
|
|
"Cycle \(cycle): PiP start button did not appear after cold launch."
|
|
)
|
|
startButton.tap()
|
|
|
|
let ready = app.descendants(matching: .any)["pip.status.ready"]
|
|
XCTAssertTrue(
|
|
ready.waitForExistence(timeout: 10),
|
|
"Cycle \(cycle): PiP did not become active after a real user tap."
|
|
)
|
|
}
|
|
|
|
app.terminate()
|
|
#endif
|
|
}
|
|
|
|
func testOSGKeyboardAppearsOnNotesHost() throws {
|
|
let app = XCUIApplication()
|
|
app.launchArguments = [
|
|
"--whats-new-host",
|
|
"--whats-new-lang=en",
|
|
"--whats-new-scenario=edit"
|
|
]
|
|
app.launch()
|
|
|
|
let textView = app.textViews["notes.host.textView"]
|
|
XCTAssertTrue(
|
|
textView.waitForExistence(timeout: 12),
|
|
"Notes host text view should appear"
|
|
)
|
|
if !textView.exists {
|
|
return
|
|
}
|
|
textView.tap()
|
|
|
|
let surfaceInHost = app.descendants(matching: .any)["assistant.surface"]
|
|
if surfaceInHost.waitForExistence(timeout: 8) {
|
|
return
|
|
}
|
|
|
|
let keyboard = XCUIApplication(bundleIdentifier: "com.osgkeyboard.ios.keyboard")
|
|
let surfaceInExtension = keyboard.descendants(matching: .any)["assistant.surface"]
|
|
let appeared = keyboard.wait(for: .runningForeground, timeout: 8)
|
|
|| keyboard.windows.firstMatch.waitForExistence(timeout: 8)
|
|
|| surfaceInExtension.waitForExistence(timeout: 8)
|
|
if !appeared {
|
|
throw XCTSkip(
|
|
"OSGKeyboard extension is not the active keyboard on this device. Enable it in Settings ▸ Keyboard, then re-run."
|
|
)
|
|
}
|
|
|
|
XCTAssertTrue(
|
|
keyboard.windows.firstMatch.exists || surfaceInExtension.exists,
|
|
"OSGKeyboard extension window should be on screen"
|
|
)
|
|
}
|
|
|
|
/// Chinese → English used to take the whole extension down. Memory relief
|
|
/// fired while the typing surface was being torn down, and every teardown
|
|
/// step recorded a milestone that asked for relief again — the nesting
|
|
/// overflowed the main thread's stack.
|
|
func testSwitchingToEnglishAfterChineseKeepsTheKeyboardAlive() throws {
|
|
let app = XCUIApplication()
|
|
app.launchArguments = [
|
|
"--whats-new-host",
|
|
"--whats-new-lang=en",
|
|
"--whats-new-scenario=edit"
|
|
]
|
|
app.launch()
|
|
|
|
let textView = app.textViews["notes.host.textView"]
|
|
XCTAssertTrue(
|
|
textView.waitForExistence(timeout: 12),
|
|
"Notes host text view should appear"
|
|
)
|
|
if !textView.exists {
|
|
return
|
|
}
|
|
textView.tap()
|
|
|
|
let keyboard = XCUIApplication(bundleIdentifier: "com.osgkeyboard.ios.keyboard")
|
|
guard let chineseTab = inputTab("chinese", host: app, keyboard: keyboard) else {
|
|
throw XCTSkip(
|
|
"OSGKeyboard extension is not the active keyboard on this device. Enable it in Settings ▸ Keyboard, then re-run."
|
|
)
|
|
}
|
|
chineseTab.tap()
|
|
|
|
// Loading Rime pushes the footprint past our relief thresholds, which is
|
|
// what arms the shed that runs on the way back out to English.
|
|
XCTAssertTrue(
|
|
keyboard.buttons["n"].waitForExistence(timeout: 12),
|
|
"Chinese typing surface should come up"
|
|
)
|
|
|
|
guard let englishTab = inputTab("english", host: app, keyboard: keyboard) else {
|
|
XCTFail("English tab should be reachable from the Chinese surface")
|
|
return
|
|
}
|
|
englishTab.tap()
|
|
|
|
XCTAssertTrue(
|
|
keyboard.buttons["n"].waitForExistence(timeout: 12),
|
|
"English surface should be usable instead of the extension being killed"
|
|
)
|
|
XCTAssertTrue(
|
|
keyboard.windows.firstMatch.exists,
|
|
"OSGKeyboard extension should survive the switch to English"
|
|
)
|
|
}
|
|
|
|
/// The top-bar tabs show up in the host's accessibility hierarchy on some OS
|
|
/// versions and in the extension's on others; try both before concluding
|
|
/// that OSGKeyboard is not the active keyboard.
|
|
private func inputTab(
|
|
_ name: String,
|
|
host: XCUIApplication,
|
|
keyboard: XCUIApplication,
|
|
timeout: TimeInterval = 10
|
|
) -> XCUIElement? {
|
|
let identifier = "assistant.tab.\(name)"
|
|
let inHost = host.descendants(matching: .any)[identifier]
|
|
if inHost.waitForExistence(timeout: timeout) { return inHost }
|
|
let inExtension = keyboard.descendants(matching: .any)[identifier]
|
|
if inExtension.waitForExistence(timeout: timeout) { return inExtension }
|
|
return nil
|
|
}
|
|
}
|