mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 16:03:20 +00:00
test(ime): record the fields the trace schema was widened to hold
The recorder captured isComposing faithfully and then lost it at the POST: JSON.stringify omits an undefined value, so the written trace had no such key at all. null carries it instead. Adds repeat and selectionDirection, and a contract test so a field the recorder learns about cannot again go somewhere the schema cannot hold it. Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -100,6 +100,7 @@ let initial = snapshot()
|
||||
|
||||
function snapshot() {
|
||||
return {
|
||||
selectionDirection: target.selectionDirection ?? 'none',
|
||||
selectionEnd: target.selectionEnd ?? 0,
|
||||
selectionStart: target.selectionStart ?? 0,
|
||||
value: target.value
|
||||
@@ -114,20 +115,24 @@ function record(event) {
|
||||
} else if (event instanceof InputEvent) {
|
||||
entry.data = event.data ?? null
|
||||
entry.inputType = event.inputType
|
||||
// Safari reports undefined rather than false and the distinction is load-bearing,
|
||||
// so it is preserved instead of coerced.
|
||||
entry.isComposing = event.isComposing
|
||||
// Safari reports undefined rather than false and the distinction is load-bearing.
|
||||
// JSON.stringify drops an undefined value outright, so null carries it instead;
|
||||
// transcribe it back to undefined in the fixture.
|
||||
entry.isComposing = event.isComposing ?? null
|
||||
} else if (event instanceof KeyboardEvent) {
|
||||
entry.code = event.code
|
||||
entry.isComposing = event.isComposing
|
||||
entry.key = event.key
|
||||
entry.keyCode = event.keyCode
|
||||
// The accent panel a macOS press-and-hold opens is only recognisable by this.
|
||||
entry.repeat = event.repeat
|
||||
}
|
||||
events.push(entry)
|
||||
countEl.textContent = String(events.length)
|
||||
const detail = [
|
||||
entry.key ? 'key=' + entry.key : '',
|
||||
entry.keyCode === undefined ? '' : 'keyCode=' + entry.keyCode,
|
||||
entry.repeat ? 'repeat' : '',
|
||||
entry.inputType ? entry.inputType : '',
|
||||
entry.data === undefined ? '' : 'data=' + JSON.stringify(entry.data),
|
||||
'isComposing=' + entry.isComposing,
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
// The recorder writes traces that the fixture schema has to be able to hold. The two
|
||||
// live in different languages and different build graphs, so nothing but this checks
|
||||
// that a field one of them learns about ever reaches the other.
|
||||
|
||||
const scriptsDir = import.meta.dirname
|
||||
const projectDir = path.resolve(scriptsDir, '../..')
|
||||
|
||||
const recorderSource = readFileSync(path.join(scriptsDir, 'record-ime-trace.mjs'), 'utf8')
|
||||
const schemaSource = readFileSync(
|
||||
path.join(projectDir, 'src/renderer/src/lib/ime-composition-trace.test-fixtures.ts'),
|
||||
'utf8'
|
||||
)
|
||||
|
||||
function declaredSchemaFields(typeName) {
|
||||
const body = schemaSource.match(new RegExp(`export type ${typeName} = [^{]*\\{([^}]*)\\}`))?.[1]
|
||||
if (!body) {
|
||||
throw new Error(`${typeName} not found in the fixture schema`)
|
||||
}
|
||||
return new Set(Array.from(body.matchAll(/^\s{2}(\w+)\??:/gm), (match) => match[1]))
|
||||
}
|
||||
|
||||
describe('record-ime-trace', () => {
|
||||
it('captures nothing the trace schema cannot express', () => {
|
||||
const captured = new Set(Array.from(recorderSource.matchAll(/entry\.(\w+) =/g), (m) => m[1]))
|
||||
const expressible = new Set([
|
||||
...declaredSchemaFields('ImeTraceKeyEvent'),
|
||||
...declaredSchemaFields('ImeTraceInputEvent'),
|
||||
...declaredSchemaFields('ImeTraceCompositionEvent')
|
||||
])
|
||||
|
||||
expect(captured.size).toBeGreaterThan(0)
|
||||
expect([...captured].filter((field) => !expressible.has(field))).toEqual([])
|
||||
})
|
||||
|
||||
it('records the fields the platform quirks are recognised by', () => {
|
||||
// repeat distinguishes a held key from the first press, which is the only signal
|
||||
// a macOS press-and-hold accent panel produces; selectionDirection is the only way
|
||||
// a backward preedit range survives as anything but a caret.
|
||||
expect(recorderSource).toMatch(/entry\.repeat = event\.repeat/)
|
||||
expect(recorderSource).toMatch(/selectionDirection: target\.selectionDirection/)
|
||||
expect(declaredSchemaFields('ImeTraceKeyEvent')).toContain('repeat')
|
||||
expect(declaredSchemaFields('ImeTraceTargetState')).toContain('selectionDirection')
|
||||
})
|
||||
|
||||
it('carries an undefined isComposing as null rather than dropping the key', () => {
|
||||
// JSON.stringify omits undefined-valued keys outright, so coercing here would
|
||||
// erase the one bit a Safari trace is recorded to preserve.
|
||||
expect(recorderSource).toMatch(/entry\.isComposing = event\.isComposing \?\? null/)
|
||||
expect(JSON.stringify({ isComposing: undefined })).toBe('{}')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user