repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/strata/__tests__/widget.test.ts
Rawimport type { Theme } from "@earendil-works/pi-coding-agent";
import {
getOsc8LinkAtColumn,
stripTerminalSequences,
visibleWidth,
} from "@earendil-works/pi-tui";
import { describe, expect, it } from "vitest";
import {
createStrataWidget,
renderStrataWidget,
STRATA_ICON,
type StrataPhase,
type StrataWidgetState,
} from "../widget.js";
function testTheme() {
let generation = 0;
const calls: Array<{ color: string; text: string }> = [];
const theme = {
bold: (text: string) => `\x1b[1m${text}\x1b[22m`,
fg: (color: string, text: string) => {
calls.push({ color, text });
return `\x1b[${31 + generation}m${text}\x1b[39m`;
},
} as Theme;
return { theme, calls, change: () => generation++ };
}
function readyState(): StrataWidgetState {
return {
phase: "Ready",
source: { kind: "base", ref: "trunk" },
url: "http://127.0.0.1:43210/?token=trusted",
reviewed: 8,
totalHunks: 24,
layers: 4,
comments: 2,
};
}
describe("Strata Pi widget", () => {
it("renders the verified icon, semantic state, counts, action, and full link target", () => {
const { theme } = testTheme();
const lines = renderStrataWidget(readyState(), theme, 160);
const visible = lines.map(stripTerminalSequences);
expect(visible).toEqual([
`▏ ${STRATA_ICON} STRATA ● Ready · base trunk · 8/24 reviewed · 4 layers · 2 comments`,
"▏ http://127.0.0.1:43210 /strata cancel",
]);
expect(lines[1]).toContain("\x1b]8;;http://127.0.0.1:43210/?token=trusted");
expect(visible.join("\n")).not.toContain("full-authentication-token");
});
it("bounds every row and drops secondary details before state and access", () => {
const { theme } = testTheme();
for (const width of [1, 4, 12, 24, 36, 48, 80, 160]) {
const lines = renderStrataWidget(readyState(), theme, width);
expect(lines).toHaveLength(2);
for (const line of lines)
expect(visibleWidth(line)).toBeLessThanOrEqual(width);
}
const narrow = renderStrataWidget(readyState(), theme, 36).map(
stripTerminalSequences,
);
expect(narrow[0]).toContain("Ready");
expect(narrow[0]).not.toContain("comments");
expect(narrow[1]).toContain("http://127.0.0.1:43210");
});
it("renders every lifecycle state with semantic color and its contextual action", () => {
const { theme, calls } = testTheme();
const cases: Array<[StrataPhase, string, string]> = [
["Collecting", "Capturing Git snapshot · /strata cancel", "accent"],
["Planning", "Organizing review layers · /strata cancel", "accent"],
["Ready", "/strata cancel", "success"],
["Answering", "Answering browser question", "accent"],
["Refreshing", "Refreshing snapshot", "accent"],
["Stale", "Refresh in browser", "warning"],
["Feedback sent", "Feedback queued for Pi", "success"],
["Error", "provider failed", "error"],
["Closed", "Run /strata to reopen", "muted"],
];
for (const [phase, action, color] of cases) {
calls.length = 0;
const lines = renderStrataWidget(
{
...readyState(),
phase,
...(phase === "Error" ? { error: "provider failed" } : {}),
...(phase === "Closed" ? { url: undefined } : {}),
},
theme,
160,
).map(stripTerminalSequences);
expect(lines[0]).toContain(phase);
expect(lines[1]).toContain(action);
expect(calls).toContainEqual({ color, text: `● ${phase}` });
}
});
it("drops the collecting and planning cancel hint when width is tight", () => {
const { theme } = testTheme();
for (const phase of ["Collecting", "Planning"] as const) {
const wide = renderStrataWidget({ phase }, theme, 80).map(
stripTerminalSequences,
);
const narrow = renderStrataWidget({ phase }, theme, 28).map(
stripTerminalSequences,
);
expect(wide[1]).toContain("/strata cancel");
expect(narrow[1]).not.toContain("/strata cancel");
}
});
it("neutralizes terminal controls without changing the trusted authenticated target", () => {
const { theme } = testTheme();
const trusted = "http://127.0.0.1:43210/?token=trusted";
const lines = renderStrataWidget(
{
...readyState(),
phase: "Error",
url: trusted,
source: { kind: "base", ref: "bad\x1b]8;;https://evil.example\x07ref" },
error: "failed\r\n\x1b]8;;https://evil.example\x07click\x1b]8;;\x07",
},
theme,
240,
);
const links = lines.flatMap((line) =>
Array.from({ length: visibleWidth(line) }, (_, column) =>
getOsc8LinkAtColumn(line, column),
).filter((value): value is string => value !== undefined),
);
expect(new Set(links)).toEqual(new Set([trusted]));
expect(stripTerminalSequences(lines.join("\n"))).not.toMatch(
/[\r\n].*[\r\n]/,
);
});
it("recomputes theme styling after invalidation", () => {
const { theme, change } = testTheme();
const widget = createStrataWidget(readyState(), theme);
const before = widget.render(120);
change();
widget.invalidate();
const after = widget.render(120);
expect(after).not.toEqual(before);
expect(after.map(stripTerminalSequences)).toEqual(
before.map(stripTerminalSequences),
);
});
});