Files
orca/tests/e2e/terminal-ime-byte-reader.ts
T
NeilandOrca 17cfc968cf Revert the terminal IME composition-ownership change (#13282)
* Revert "test(ime): restore coverage the composition-ownership change removed (#13168)"

This reverts commit 25a8c517e1.

* Revert "refactor(terminal): return IME composition ownership to xterm (#13128)"

This reverts commit 17b3dff3c4.

* test(ime): keep the architecture-neutral Korean trace coverage

The recorded IBus/fcitx5 and Windows MS-Korean traces from #13168 assert PTY
byte order, not composition ownership, so they still hold once the terminal
composition layer is restored. The mobile accessory-order test pinned the new
handleLiveInputChange signature and does not.

Co-authored-by: Orca <help@stably.ai>

* fix(terminal): keep the macOS Backslash bypass through the revert

The restored native-text forwarder only claims keys for input sources in its
hardcoded CJK allowlist, so third-party IMEs off that list (Qingg, #10896) still
get a raw backslash. #13128 added this bypass as a partial replacement; keep it
rather than trade the open issue back.

Scoped to the bare backslash key. The rest of shouldBypassXtermForMacNativeText
bypassed all unmodified non-ASCII text, which would race the restored forwarder.

Co-authored-by: Orca <help@stably.ai>

* fix(mobile): move the mirror-step ref write out of render

The restored hook assigned runMirrorStepRef during render, which is not
replay-safe — React can discard render work, so the mutation can leak from UI
that never commits. Its only read is inside the held-commit timer, which fires
long after commit, and the ref has a safe default, so an effect is soon enough.

Surfaced by the changed-lines React Doctor gate: the rule postdates this code,
so restoring the file re-introduced it as a new violation.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-08-08 19:06:50 -07:00

88 lines
2.9 KiB
TypeScript

import { randomUUID } from 'node:crypto'
import { rmSync, writeFileSync } from 'node:fs'
import path from 'node:path'
import type { Page } from '@stablyai/playwright-test'
import { expect } from '@stablyai/playwright-test'
import { getTerminalContent, sendToTerminal, waitForTerminalOutput } from './helpers/terminal'
export type TerminalImeByteReader = {
expectedLineCount: number
readyMarker: string
resultPrefix: string
scriptPath: string
}
export function createTerminalImeByteReader(
testRepoPath: string,
expectedLineCount: number
): TerminalImeByteReader {
const runId = randomUUID().replaceAll('-', '')
const readyMarker = `ORCA_IME_READER_READY_${runId}`
const resultPrefix = `ORCA_IME_BYTES_${runId}`
const scriptPath = path.join(testRepoPath, `.orca-ime-byte-reader-${runId}.cjs`)
const source = `
const expectedLineCount = ${expectedLineCount}
const readyMarker = ${JSON.stringify(readyMarker)}
const resultPrefix = ${JSON.stringify(resultPrefix)}
let pending = Buffer.alloc(0)
let receivedLineCount = 0
process.stdout.write(readyMarker + '\\n')
process.stdin.on('data', (chunk) => {
pending = Buffer.concat([pending, Buffer.from(chunk)])
let newlineIndex = pending.indexOf(0x0a)
while (newlineIndex >= 0) {
const line = pending.subarray(0, newlineIndex + 1)
pending = pending.subarray(newlineIndex + 1)
receivedLineCount += 1
process.stdout.write(resultPrefix + ':' + receivedLineCount + ':' + line.toString('hex') + '\\n')
if (receivedLineCount === expectedLineCount) {
process.exit(0)
}
newlineIndex = pending.indexOf(0x0a)
}
})
`
writeFileSync(scriptPath, source)
return { expectedLineCount, readyMarker, resultPrefix, scriptPath }
}
export async function startTerminalImeByteReader(
page: Page,
ptyId: string,
reader: TerminalImeByteReader
): Promise<void> {
await sendToTerminal(page, ptyId, `node ${JSON.stringify(reader.scriptPath)}\r`)
await waitForTerminalOutput(page, reader.readyMarker, 10_000, 20_000)
}
export async function waitForTerminalImeBytes(
page: Page,
reader: TerminalImeByteReader,
timeoutMs = 15_000
): Promise<string[]> {
let results: string[] = []
await expect
.poll(
async () => {
const terminal = await getTerminalContent(page, 100_000)
const resultPattern = new RegExp(`${reader.resultPrefix}:(\\d+):([0-9a-f]+)`, 'g')
const bySequence = new Map<number, string>()
for (const match of terminal.matchAll(resultPattern)) {
bySequence.set(Number(match[1]), match[2])
}
results = [...bySequence.entries()]
.sort(([left], [right]) => left - right)
.map(([, hex]) => hex)
return results.length
},
{ timeout: timeoutMs, message: 'IME byte reader did not receive every expected line' }
)
.toBe(reader.expectedLineCount)
return results
}
export function removeTerminalImeByteReader(reader: TerminalImeByteReader): void {
rmSync(reader.scriptPath, { force: true })
}