Files
NetMesh/components/terminal/TerminalTimestampGutter.test.ts
zhaolei 3c72efcb7f
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

223 lines
6.6 KiB
TypeScript

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<Record<string, unknown>>,
appendChild(node: Record<string, unknown>) {
this.children.push(node);
return node;
},
};
const createElement = (tag: string) => {
assert.equal(tag, "div");
return {
textContent: "",
className: "",
style: {} as Record<string, string>,
};
};
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<string, string>).display, "none");
} finally {
if (previousCreateElement) {
(globalThis as { document: { createElement: typeof previousCreateElement } }).document = {
createElement: previousCreateElement,
};
}
}
});