[Init] Initial commit - NetMesh terminal manager
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
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
This commit is contained in:
222
components/terminal/TerminalTimestampGutter.test.ts
Normal file
222
components/terminal/TerminalTimestampGutter.test.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user