mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
The previous shape guessed the character from `event.key` on keydown, re-emitted it, and then tried to suppress a late canonical copy with a 250 ms character-keyed dedupe. Review found three defects in that, all reproducible: the dedupe matched on the character alone with nothing scoping a candidate to the keydown that created it, so the same character typed twice inside the window had its second, real byte swallowed; anything whose committed text differed from `event.key` (Enter, IME punctuation) was delivered twice, because the dedupe could never match it; and the trigger ignored `key === 'Unidentified'`, which is what a soft keyboard reports, so it may never have fired where it was needed. The input event already carries the committed text in `ev.data` — exactly what xterm itself would have forwarded — so nothing has to be guessed. The controller now only decides WHETHER to forward, by asking whether xterm produced canonical data since the keydown that began the keystroke. No character-keyed matching survives, so the first two defects are structurally impossible rather than defended against, and nothing reads `key`/`keyCode`, so the third cannot recur. Three details are load-bearing and each has a test that fails without it: - The "did xterm speak?" snapshot is taken at KEYDOWN, not at the input event. `_keyPress` emits and sets `_keyPressHandled` before `input` fires, so a snapshot read at input time already contains that emission, reads it as silence, and delivers the character twice. - Our `input` listener is registered with `capture: true`. The target is visited twice in the event path, so a capture listener calling `stopPropagation()` stops later BUBBLE listeners on that same target; xterm's `cancel()` runs exactly in the branch where it handled the input, so on bubble we would never observe handled events, and whether we observed them at all would hang off `options.cancelEvents`. Measured in jsdom and headless chromium; the table is in the module header. - Enter is deliberately no longer special-cased. That mapping is what made the committed text differ from the re-emitted value in the first place. The scope is also narrower than the old name suggests, and the browser test now proves it rather than assuming it. For a keydown that reports keyCode 229 xterm ALREADY self-rescues, via `CompositionHelper._handleAnyTextareaChanges()` diffing the helper textarea on a 0 ms timer. A test asserting "we recovered it" there passes while xterm does all the work, so the browser tests assert WHO delivered the byte: zero canonical emissions for the genuinely orphaned case, exactly one delivery for the case xterm rescues itself. Also addresses review notes: the module gains an `@fileoverview` with `@dependency`/`@loadorder` and an entry in the load-order list and module inventory, and the wiring test moves out of the Ctrl+C smart-copy file into its own. The keydown hook deliberately still runs for every key event rather than moving behind the 229 gate: gating it would reinstate exactly the blindness described above, and it is now a single counter assignment.
50 lines
2.1 KiB
TypeScript
50 lines
2.1 KiB
TypeScript
/**
|
|
* The test suites that `npm test` deliberately does NOT run, in one place.
|
|
*
|
|
* Why this file exists: the exclusion list used to live only in
|
|
* config/vitest.ci.config.ts, as literals. Anything excluded there was
|
|
* therefore reachable only by running the everything-config by hand and reading
|
|
* past its failures — and a newly excluded file was reachable by nothing at
|
|
* all, silently, because nothing pointed at it. Both configs now derive their
|
|
* globs from the arrays below, so adding a suite here puts it in exactly one
|
|
* runner and takes it out of exactly one gate.
|
|
*
|
|
* Adding a new test that cannot run in CI: put its glob in the array that
|
|
* describes WHY it cannot, not in whichever one is shortest.
|
|
*/
|
|
|
|
/**
|
|
* Playwright-driven: needs chromium and, in most cases, a live Codeman server
|
|
* on a real port. Deterministic where the environment provides both, which is
|
|
* why these are a runnable suite (`npm run test:browser`) rather than skipped.
|
|
*/
|
|
export const BROWSER_TEST_GLOBS = [
|
|
'test/tab-rail-resize.browser.test.ts',
|
|
'test/session-sidebar-ux.browser.test.ts',
|
|
'test/session-options-responsive.browser.test.ts',
|
|
'test/inline-rename.test.ts',
|
|
'test/opencode-resize.test.ts',
|
|
'test/webgl-fallback.test.ts',
|
|
'test/terminal-copy-shortcut.test.ts',
|
|
'test/terminal-keycode229-recovery.browser.test.ts',
|
|
'test/codex-predictive-echo.test.ts', // also needs a real codex binary
|
|
];
|
|
|
|
/**
|
|
* Wall-clock benchmarks. They assert on durations, so a loaded shared runner
|
|
* fails them for reasons that have nothing to do with the diff under test.
|
|
*/
|
|
export const PERF_TEST_GLOBS = ['test/perf-*.test.ts'];
|
|
|
|
/**
|
|
* Browser + visual regression: chromium AND environment-specific PNG baselines
|
|
* that are generated per machine. Has its own config
|
|
* (test/mobile/vitest.config.ts) because it needs serial execution, a longer
|
|
* timeout and the `pretest:mobile` vendor step — run it with
|
|
* `npm run test:mobile`, not through the configs here.
|
|
*/
|
|
export const MOBILE_TEST_GLOBS = ['test/mobile/**'];
|
|
|
|
/** Everything `npm test` skips. */
|
|
export const NON_CI_TEST_GLOBS = [...MOBILE_TEST_GLOBS, ...PERF_TEST_GLOBS, ...BROWSER_TEST_GLOBS];
|