eaf05466b0
Two coupled changes, both about debug mode (which @vue-tui/testing's render() is built on): 1. Debug stdout is now byte-identical to Ink v7.0.4: the debug commit branch re-emits the FULL accumulated <Static> history every frame (not just the per-commit delta), writes every frame unconditionally (no FrameWriter dedup), and drops the synthetic trailing "\n" — matching Ink's `fullStaticOutput + output` (ink.tsx:558, output.ts has no trailing newline). 2. The test frame-capture no longer reverse-engineers frames out of stdout. The runtime exposes an internal, per-app frame sink (INTERNAL_FRAME_SINK, a Symbol from @vue-tui/runtime/internal; the public MountOptions type is untouched). The debug branch hands each committed frame to the sink, mirroring the stdout writes. @vue-tui/testing's render() builds frames[]/lastFrame() from the sink instead of sniffing stdout. Why: an isTTY:true test stdout (which render() needs for the interactive resize listener) lets isTTY-gated escapes — bracket-paste \x1b[?2004h/l from usePaste — land in a stdout-sniffing capture and pollute frames[]. Capturing at the source makes frames[] provably content-only regardless of which composables a test mounts, while public debug stdout stays byte-exact to Ink (escapes still written, not debug-gated). The test surface stays cleanly tiered (Ink's model): render() = content; createApp+debug:false = in-process control sequences; PTY = real terminal. '' floor, verbatim SGR/OSC8, frames[] multi-frame/static semantics, and terminal.resize() are all preserved. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
37 lines
1.6 KiB
TypeScript
37 lines
1.6 KiB
TypeScript
import { defineComponent } from "vue";
|
||
import { expect, test } from "vite-plus/test";
|
||
import { Text, usePaste } from "@vue-tui/runtime";
|
||
import { render } from "@vue-tui/testing";
|
||
|
||
// Headline invariant of the B′ "source-hook frame capture" design: the testing
|
||
// helper's `frames[]` / `lastFrame()` are CONTENT-ONLY. They are fed each
|
||
// committed frame DIRECTLY from the runtime (an internal per-app frame sink),
|
||
// NOT reverse-engineered out of stdout. So terminal-control escapes that the
|
||
// runtime legitimately writes to stdout (to stay byte-faithful to Ink) must NOT
|
||
// leak into `frames[]`.
|
||
//
|
||
// usePaste() enables bracketed-paste mode, which writes the OSC `\x1b[?2004h`
|
||
// enable sequence to stdout (gated on stdout.isTTY, which the testing helper's
|
||
// fake stdout is — render.ts setBracketedPasteMode). Under the OLD
|
||
// stdout-sniffing capture this escape was pushed into `frames[]` as its own
|
||
// frame, polluting the content the test observer sees. Under B′ it goes only to
|
||
// stdout; `frames[]` contains the rendered content alone.
|
||
test("render() frames are content-only — bracketed-paste escape never leaks into frames", async () => {
|
||
const App = defineComponent(() => {
|
||
usePaste(() => {});
|
||
return () => <Text>content</Text>;
|
||
});
|
||
|
||
const { frames, lastFrame } = await render(App);
|
||
|
||
// The rendered content is observable.
|
||
expect(lastFrame()).toBe("content");
|
||
|
||
// The bracketed-paste enable escape was written to stdout (byte-faithful to
|
||
// Ink) but must NEVER appear in any captured frame.
|
||
for (const frame of frames) {
|
||
expect(frame).not.toContain("\x1b[?2004h");
|
||
expect(frame).not.toContain("\x1b[?2004l");
|
||
}
|
||
});
|