import test from "node:test"; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { TERMINAL_TIMESTAMP_GUTTER_HORIZONTAL_PADDING, TERMINAL_TIMESTAMP_GUTTER_MIN_WIDTH, getTerminalTimestampTypography, resolveTerminalTimestampGutterRenderSignature, resolveTerminalTimestampGutterColor, resolveTerminalTimestampGutterWidth, syncTerminalTimestampGutterRows, } from "./TerminalTimestampGutter.tsx"; test("timestamp gutter uses a bright color from the active terminal theme", () => { assert.equal( resolveTerminalTimestampGutterColor({ brightCyan: "#66e8ff", brightYellow: "#ffe066", foreground: "#dddddd", }), "#66e8ff", ); }); test("timestamp gutter falls back within the terminal theme palette", () => { assert.equal( resolveTerminalTimestampGutterColor({ brightYellow: "#ffe066", foreground: "#dddddd", }), "#ffe066", ); assert.equal( resolveTerminalTimestampGutterColor({ foreground: "#dddddd", }), "#dddddd", ); }); test("timestamp gutter width follows measured timestamp text width", () => { assert.equal( resolveTerminalTimestampGutterWidth({ measuredTextWidth: 84, fontSize: 14 }), 84 + TERMINAL_TIMESTAMP_GUTTER_HORIZONTAL_PADDING, ); assert.equal( resolveTerminalTimestampGutterWidth({ measuredTextWidth: 1, fontSize: 14 }), TERMINAL_TIMESTAMP_GUTTER_MIN_WIDTH, ); }); test("timestamp gutter typography follows terminal typography", () => { assert.deepEqual( getTerminalTimestampTypography({ fontFamily: '"JetBrains Mono", monospace', fontSize: 15, fontWeight: 500, }), { fontFamily: '"JetBrains Mono", monospace', fontSize: 15, fontWeight: 500, }, ); }); test("timestamp gutter uses the terminal background", () => { const source = readFileSync(new URL("./TerminalTimestampGutter.tsx", import.meta.url), "utf8"); assert.match(source, /backgroundColor: "var\(--terminal-ui-bg\)"/); assert.doesNotMatch(source, /bg-black\/10/); assert.match(source, /boxShadow: "inset -0\.5px 0 0 color-mix\(in srgb, var\(--terminal-ui-fg\) 8%, transparent\)"/); assert.doesNotMatch(source, /border-r/); }); test("timestamp gutter render signature is stable and changes only for visible inputs", () => { const base = resolveTerminalTimestampGutterRenderSignature({ screenTop: 8, cellHeight: 17, color: "#66e8ff", fontFamily: "JetBrains Mono", fontSize: 14, fontWeight: 500, rows: [ { row: 0, label: "10:00:00" }, { row: 2, label: "10:00:02" }, ], }); assert.equal( resolveTerminalTimestampGutterRenderSignature({ screenTop: 8, cellHeight: 17, color: "#66e8ff", fontFamily: "JetBrains Mono", fontSize: 14, fontWeight: 500, rows: [ { row: 0, label: "10:00:00" }, { row: 2, label: "10:00:02" }, ], }), base, ); assert.notEqual( resolveTerminalTimestampGutterRenderSignature({ screenTop: 8, cellHeight: 17, color: "#66e8ff", fontFamily: "JetBrains Mono", fontSize: 14, fontWeight: 500, rows: [ { row: 0, label: "10:00:00" }, { row: 3, label: "10:00:02" }, ], }), base, ); }); test("timestamp gutter flood throttle advances even when the paint signature is unchanged", () => { const source = readFileSync(new URL("./TerminalTimestampGutter.tsx", import.meta.url), "utf8"); // lastFloodRenderAt must move on every render attempt, before the signature early-return. const renderStart = source.indexOf("const render = () => {"); const signatureReturn = source.indexOf("if (signature === lastRenderSignature) return;", renderStart); const floodAdvance = source.indexOf("lastFloodRenderAt = performance.now();", renderStart); assert.notEqual(renderStart, -1); assert.notEqual(signatureReturn, -1); assert.notEqual(floodAdvance, -1); assert.ok( floodAdvance < signatureReturn, "flood throttle clock must advance before the unchanged-signature early return", ); }); test("timestamp gutter throttles output-driven scroll events under flood pressure", () => { const source = readFileSync(new URL("./TerminalTimestampGutter.tsx", import.meta.url), "utf8"); assert.match(source, /term\.onScroll\?\.\(\(\) => \{/); assert.match(source, /getTerminalOutputPressure\(term\)/); // largeOutput only (time-bounded); longLine must not sticky-throttle user scrolls. assert.match(source, /scheduleRender\(pressure\.largeOutput \? "normal" : "immediate"\)/); assert.doesNotMatch( source, /onScroll[\s\S]{0,200}pressure\.largeOutput \|\| pressure\.longLine/, ); }); test("timestamp gutter reuses row nodes across paints instead of rebuilding the tree", () => { const gutter = { children: [] as Array>, appendChild(node: Record) { this.children.push(node); return node; }, }; const createElement = (tag: string) => { assert.equal(tag, "div"); return { textContent: "", className: "", style: {} as Record, }; }; const previousCreateElement = globalThis.document?.createElement; (globalThis as { document?: { createElement: typeof createElement } }).document = { createElement, }; try { const layout = { screenTop: 0, cellHeight: 16, color: "#66e8ff", fontFamily: "monospace", fontSize: 14, fontWeight: 400, }; syncTerminalTimestampGutterRows( gutter as never, [ { row: 0, label: "10:00:00" }, { row: 1, label: "10:00:01" }, ], layout, ); assert.equal(gutter.children.length, 2); const firstNode = gutter.children[0]; assert.equal(firstNode.textContent, "10:00:00"); syncTerminalTimestampGutterRows( gutter as never, [ { row: 0, label: "10:00:02" }, { row: 2, label: "10:00:03" }, ], layout, ); assert.equal(gutter.children.length, 2); assert.equal(gutter.children[0], firstNode); assert.equal(firstNode.textContent, "10:00:02"); assert.equal(gutter.children[1].textContent, "10:00:03"); syncTerminalTimestampGutterRows( gutter as never, [{ row: 0, label: "10:00:04" }], layout, ); assert.equal(gutter.children.length, 2); assert.equal((gutter.children[1].style as Record).display, "none"); } finally { if (previousCreateElement) { (globalThis as { document: { createElement: typeof previousCreateElement } }).document = { createElement: previousCreateElement, }; } } });