Files
vue-tui/packages/runtime-tests/integration/lifecycle/test-streams.ts
T
Yunfei He 1ffb847a65 feat: test parity final push — 64 new tests, BSU/ESU, clear() API
Closes remaining test gaps between vue-tui and Ink:

Features:
- Add synchronized output (BSU/ESU) via DEC private mode 2026
- Add clear() API to TuiApp for erasing rendered output

Bug fixes:
- Cancel scheduler trailing timer on teardown (prevents stale commits)
- Guard teardown writes against ended/destroyed streams
- Reorder teardown to cancel timer before final commit

Tests (64 new, 2 skipped for known feature gaps):
- 7 BSU/ESU shouldSynchronize tests
- 5 borderBackgroundColor tests
- 13 component edge cases (empty text, number child, OSC hyperlink
  wrap-width, bare-text-in-Box validation, transform multi-line,
  leading whitespace, link escape closing)
- 13 text-width/CJK tests (alignment, truncation, overlay edge cases)
- 4 throttle + unmount edge cases
- 10 waitUntilRenderFlush write-callback-level tests + 1 clear() test
- 3 exit re-entrance tests
- 5 PTY #450 regression tests + 4 inline #450 tests

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 14:18:23 +08:00

87 lines
2.2 KiB
TypeScript

import { PassThrough, Writable } from "node:stream";
export interface FakeWritableOptions {
columns?: number;
rows?: number;
}
export function makeFakeWritable(options: FakeWritableOptions = {}): NodeJS.WriteStream {
const s = new PassThrough() as unknown as NodeJS.WriteStream;
Object.assign(s, {
columns: options.columns ?? 100,
rows: options.rows ?? 100,
isTTY: true,
});
return s;
}
export function makeFakeStdin(): { stream: NodeJS.ReadStream } {
const s = new PassThrough() as unknown as NodeJS.ReadStream;
Object.assign(s, {
isTTY: true,
setRawMode(this: NodeJS.ReadStream) {
return this;
},
setEncoding(this: NodeJS.ReadStream) {
return this;
},
});
(s as any).ref = () => {};
(s as any).unref = () => {};
return { stream: s };
}
export function createDelayedWriteCallbackStdout({
shouldDelay,
onDelayElapsed,
delayMs = 150,
}: {
shouldDelay: (chunk: string | Uint8Array) => boolean;
onDelayElapsed: () => void;
delayMs?: number;
}): NodeJS.WriteStream {
let didDelayOnce = false;
const stdout = new Writable({
write(
chunk: string | Uint8Array,
_encoding: BufferEncoding,
callback: (error?: Error) => void,
) {
if (!didDelayOnce && shouldDelay(chunk)) {
didDelayOnce = true;
setTimeout(() => {
onDelayElapsed();
callback();
}, delayMs);
return;
}
callback();
},
}) as unknown as NodeJS.WriteStream;
stdout.columns = 100;
stdout.isTTY = true;
return stdout;
}
export const isWriteBarrierChunk = (chunk: string | Uint8Array): boolean =>
(typeof chunk === "string" && chunk === "") ||
(chunk instanceof Uint8Array && chunk.length === 0);
export function captureWrites(stdout: NodeJS.WriteStream): string[] {
const writes: string[] = [];
const original = stdout.write.bind(stdout);
stdout.write = ((...args: unknown[]) => {
writes.push(String(args[0]));
return (original as Function)(...args);
}) as NodeJS.WriteStream["write"];
return writes;
}
export function getContentWrites(writes: string[]): string[] {
return writes.filter(
(w) => w !== "" && !w.startsWith("\x1b[?25") && w !== "\x1b[?2026h" && w !== "\x1b[?2026l",
);
}