7707322382
* fix(runtime): align debug-mode unmount byte stream to Ink In DEBUG mode (non-interactive) vue-tui's teardown emitted neither a final-frame re-emit nor a trailing newline, while Ink emits both (ink.tsx:749-762 settleThrottle re-emit + ink.tsx:812-819 `debug ? '\n' : lastOutput + '\n'`). So for a debug app that renders "Hello" once, Ink's byte stream is "HelloHello\n" but vue-tui's was just "Hello" — a divergence that matters when porting Ink debug snapshots / CI logs. - Fire the final-frame re-emit `mountedCommit()` for debug too (was interactive-only). - In the non-interactive teardown write, emit a bare "\n" for debug (Ink parity), keeping the non-debug `lastFrame + "\n"` branch byte-identical. Because the @vue-tui/testing render() helper captures debug commits via an internal frame sink, gate the debug commit's two `frameSink?.(...)` forwards on `!teardownStarted` so the teardown re-emit (a stdout byte-parity FLUSH, not a render) does not append a spurious entry to the helper's live `frames[]`. `teardownStarted` is set at the top of teardown() before the re-emit, so this covers EVERY teardown route (unmount / cleanup / exit / Ctrl+C / signal / process.exit). Both `stdout.write` calls stay unconditional, preserving byte parity. Adds a PTY byte-parity test (asserts "HelloHello\r\n") and a testing-helper test covering all teardown routes (frames.length stable, incl. <Static>). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test: drop CI-fragile exitOnCtrlC frames-teardown case The Ctrl+C case timed out in CI (waitUntilExit never resolved — stdin/raw-mode timing is environment-fragile), while passing locally. Ctrl+C routes through the SAME exit-driven teardown path as programmatic useApp().exit() (emitInput → appContext.exit() → teardown()), which the remaining exit() cases already cover, so removing it loses no teardown-route coverage of the !teardownStarted frame-sink gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
50 lines
2.3 KiB
TypeScript
50 lines
2.3 KiB
TypeScript
import { test as it, expect } from "vite-plus/test";
|
|
import { run } from "./helpers/run.ts";
|
|
import stripAnsi from "strip-ansi";
|
|
|
|
it("render only last frame in CI", async () => {
|
|
const output = await run("ci", { env: { CI: "true" }, columns: 0 });
|
|
const clean = stripAnsi(output).replaceAll("\r", "");
|
|
for (let i = 0; i <= 4; i++) {
|
|
expect(clean).not.toContain(`Counter: ${i}`);
|
|
}
|
|
expect(clean).toContain("Counter: 5");
|
|
});
|
|
|
|
it("render all frames if CI=false", async () => {
|
|
const output = await run("ci", { env: { CI: "false" }, columns: 0 });
|
|
const clean = stripAnsi(output).replaceAll("\r", "");
|
|
expect(clean).toContain("Counter:");
|
|
expect(clean).toContain("Counter: 5");
|
|
expect(clean).toContain("#1");
|
|
});
|
|
|
|
it("debug mode in CI", async () => {
|
|
const output = await run("ci-debug", { env: { CI: "true" }, columns: 0 });
|
|
const clean = stripAnsi(output).replaceAll("\r", "");
|
|
expect(clean).toContain("Hello");
|
|
});
|
|
|
|
it("debug after exit", async () => {
|
|
const output = await run("ci-debug-after-exit", { env: { CI: "true" }, columns: 0 });
|
|
const clean = stripAnsi(output).replaceAll("\r", "");
|
|
expect(clean).toContain("Hello");
|
|
expect(clean).toContain("DONE");
|
|
});
|
|
|
|
// Byte-level Ink parity for the DEBUG + non-interactive (CI=true) unmount stream.
|
|
// Ink (ink.tsx @ v7.0.4): the debug onRender writes `fullStaticOutput + output`
|
|
// per render with NO trailing newline (550-558); at unmount settleThrottle leaves
|
|
// shouldRenderFinalFrame=true for debug (throttledOnRender is undefined → 749-762),
|
|
// so a final onRender RE-EMITS the last frame; then finishUnmount writes a single
|
|
// trailing "\n" for the non-interactive+debug case (812-819). For the single
|
|
// static-free "Hello" render (fullStaticOutput="" , lastOutput="Hello") the exact
|
|
// stdout byte stream is "Hello" + "Hello" + "\n". The PTY translates the lone "\n"
|
|
// to "\r\n", so the raw capture must end with exactly "HelloHello\r\n".
|
|
// The ci-debug-bytes fixture uses a function slot so no Vue dev warning is
|
|
// interleaved — the raw stream is exactly the debug frames.
|
|
it("debug mode in CI re-emits the final frame and writes a trailing newline (Ink byte parity)", async () => {
|
|
const output = await run("ci-debug-bytes", { env: { CI: "true" }, columns: 0 });
|
|
expect(output).toBe("HelloHello\r\n");
|
|
});
|