7d03a110e1
Correctness fixes found by max-effort review of the branch diff: - frame-writer.sync() now updates lastFrame alongside log-update's previousOutput. Previously the two dedup layers desynced after a sync() (the clearTerminal path), silently dropping a legitimately-changed frame and emitting an empty BSU/ESU pair. Adds a regression test. - scheduler: the queuePostFlushCb callback now bails if scheduled was reset by cancel(), so a stale callback can't commit on a torn-down tree or re-arm an uncancellable trailing timer. - scheduler.flush() now collects multiple concurrent waiters instead of overwriting a single resolver — fixes a hang when two waitUntilRenderFlush() calls await the same pending commit. - render teardown nulls mountedClear so a post-unmount app.clear() can't write to a torn-down stream. - test-streams getContentWrites imports bsu/esu instead of hardcoding the escape literals. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
86 lines
2.3 KiB
TypeScript
86 lines
2.3 KiB
TypeScript
import { PassThrough, Writable } from "node:stream";
|
|
import { bsu, esu } from "../../../runtime/src/io/write-synchronized.ts";
|
|
|
|
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 !== bsu && w !== esu);
|
|
}
|