Files
vue-tui/packages/runtime-tests/integration/composables/terminal-size.sequential.test.tsx
T
Yunfei He a78fd67cea test(runtime): lock vt220 fn-keys, terminal-size fallbacks, animation offsets, arrow meta (#114)
Round-2 test-only locks (behaviors already at parity with Ink):
- parse-keypress (Ink parse-keypress.ts): Ctrl+F1–F4 (\x1b[1;5P/Q/R/S → f1–f4 ctrl),
  unmapped ctrl (\x1b[1;5I/X → name '' ctrl), Shift+F1 (\x1b[1;2P → f1 shift).
- terminal-size (Ink terminal-resize.tsx): 0-columns → positive fallback;
  resize-listener returns to baseline on unmount; env.LINES rows fallback (a .sequential
  file — mutates process.env/stdout; deletes absent env vars in teardown to avoid pollution).
- use-animation (Ink use-animation.tsx, a .sequential file with deterministic fake timers):
  newly mounted/activated same-interval animations don't inherit elapsed time
  (firstFrame - secondFrame === 1); a re-render with an unchanged interval doesn't reset
  the frame (forced via an unrelated reactive dep — a same-value assign is a Vue no-op);
  reset is a stable reference across re-renders (collected in the render fn).
- use-input: plain arrows assert key.meta === false (Ink's `&& !key.meta` gate).

Codex-reviewed; the same-value-no-reset (was vacuous) and env teardown (left "undefined")
were tightened per its notes.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 02:49:31 +08:00

87 lines
3.3 KiB
TypeScript

// Sequential: mutates process-global state — process.env.COLUMNS/LINES and
// process.stdout/process.stderr columns+rows. The terminal-size package (used by
// resolveSize's fallback) reads these globals directly, so a concurrent sibling
// would perturb the result. Tests restore every mutated prop in a finally block.
import { PassThrough } from "node:stream";
import process from "node:process";
import { defineComponent } from "vue";
import { expect, test } from "vite-plus/test";
import { createApp, Text, useTerminalSize } from "@vue-tui/runtime";
function makeTtyStream(columns: number): NodeJS.WriteStream {
const s = new PassThrough() as unknown as NodeJS.WriteStream;
// Deliberately no `rows` — forces resolveSize() into the terminal-size fallback.
Object.assign(s, { columns, isTTY: true });
return s;
}
function makeFakeStdin(): NodeJS.ReadStream {
const s = new PassThrough() as unknown as NodeJS.ReadStream;
Object.assign(s, {
isTTY: true,
setRawMode() {
return s;
},
setEncoding() {
return s;
},
});
(s as unknown as { ref: () => void }).ref = () => {};
(s as unknown as { unref: () => void }).unref = () => {};
return s;
}
// Mirrors Ink terminal-resize.tsx:110-152 ("falls back to terminal-size rows
// when stdout.rows is missing"). With the mount stdout reporting columns 0 and
// no rows, resolveSize() calls terminal-size, which — after we zero out the real
// process.stdout/stderr dimensions — resolves rows from process.env.LINES.
test.sequential("useTerminalSize falls back to terminal-size rows from env.LINES when stdout.rows is missing", async () => {
const stdout = makeTtyStream(0);
const stderr = makeTtyStream(0);
const stdin = makeFakeStdin();
const originalColumns = process.env.COLUMNS;
const originalLines = process.env.LINES;
const originalStdoutColumns = process.stdout.columns;
const originalStdoutRows = process.stdout.rows;
const originalStderrColumns = process.stderr.columns;
const originalStderrRows = process.stderr.rows;
let capturedRows = -1;
const App = defineComponent(() => {
const { rows } = useTerminalSize();
capturedRows = rows.value;
return () => <Text>{String(rows.value)}</Text>;
});
const app = createApp(App);
try {
// terminal-size prefers process.stdout/stderr dimensions, then env.
// Zero the real streams so env.COLUMNS/LINES is the winning source.
process.env.COLUMNS = "123";
process.env.LINES = "45";
process.stdout.columns = 0;
process.stdout.rows = 0;
process.stderr.columns = 0;
process.stderr.rows = 0;
app.mount({ stdout, stdin, stderr, debug: true, exitOnCtrlC: false });
await new Promise<void>((r) => setTimeout(r, 60));
expect(capturedRows).toBe(45);
} finally {
app.unmount();
// Restore env precisely: a var that was ABSENT must be DELETED, not set to
// the string "undefined" (which would pollute later tests' CI/size detection).
if (originalColumns === undefined) delete process.env.COLUMNS;
else process.env.COLUMNS = originalColumns;
if (originalLines === undefined) delete process.env.LINES;
else process.env.LINES = originalLines;
process.stdout.columns = originalStdoutColumns;
process.stdout.rows = originalStdoutRows;
process.stderr.columns = originalStderrColumns;
process.stderr.rows = originalStderrRows;
}
});