This repository has been archived on 2026-10-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
maestro/web/src/structuredDiff.test.ts
T
Miguel Palhas a50f098c8a feat(maestro): render the structured view in the web client (#140)
Adds a transcript surface for a Structured-view session alongside the
existing xterm attach view: a WS /api/structured/:id connection
(structured.ts) mounted by ConnectionPool whenever a session's `view`
is `"Structured"`, and pure per-row/rail renderers (structuredRender.ts,
structuredDiff.ts) covering all eight transcript row kinds.

Hierarchy follows the design reference (artifact 88ecd690): the
approval is the only lifted, card-like element; a user turn is a
tinted block with an accent rail; agent prose is plain text; tool
calls are compact bordered rows expanding to output or a coloured,
horizontally-scrolling diff computed from ACP's full old/new text.
The client renders `reduced_state` and rows exactly as received, with
no raw-event folding or turn tracking of its own.

Extracted attach.ts's pane-header title building into paneTitle.ts and
its connection interface into pane.ts (PaneConnection) so both
connection kinds share one contract and one header.
2026-09-05 06:10:40 +01:00

104 lines
3.8 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { computeDiffLines, diffStats, type DiffLine } from "./structuredDiff.js";
function texts(lines: DiffLine[]): Array<[string, string]> {
return lines.map((l) => [l.kind, l.text]);
}
describe("computeDiffLines", () => {
it("renders a brand new file (no oldText) as all additions", () => {
const lines = computeDiffLines(null, "a\nb\n");
expect(texts(lines)).toEqual([
["add", "a"],
["add", "b"],
]);
});
it("renders a full deletion as all removals", () => {
const lines = computeDiffLines("a\nb\n", "");
expect(texts(lines)).toEqual([
["del", "a"],
["del", "b"],
]);
});
it("shows a small replace as context around del/add", () => {
const lines = computeDiffLines("one\ntwo\nthree\n", "one\ntwx\nthree\n");
expect(texts(lines)).toEqual([
["ctx", "one"],
["del", "two"],
["add", "twx"],
["ctx", "three"],
]);
});
it("leaves identical text as pure context, uncollapsed when short", () => {
const lines = computeDiffLines("a\nb\nc\n", "a\nb\nc\n");
expect(texts(lines)).toEqual([
["ctx", "a"],
["ctx", "b"],
["ctx", "c"],
]);
});
it("collapses a long unchanged interior run, keeping 3 lines of context each side", () => {
const common = Array.from({ length: 20 }, (_, i) => `line${i}`);
const oldText = [...common.slice(0, 10), "OLD", ...common.slice(10)].join("\n");
const newText = [...common.slice(0, 10), "NEW", ...common.slice(10)].join("\n");
const lines = computeDiffLines(oldText, newText);
// The leading run keeps only its last 3 lines (adjacent to the change),
// behind a marker for the rest; the trailing run mirrors that.
expect(lines[0].kind).toBe("hunk");
expect(lines[1]).toEqual({ kind: "ctx", text: "line7" });
expect(lines[2]).toEqual({ kind: "ctx", text: "line8" });
expect(lines[3]).toEqual({ kind: "ctx", text: "line9" });
expect(lines[4].kind).toBe("del");
expect(lines[5].kind).toBe("add");
expect(lines[6]).toEqual({ kind: "ctx", text: "line10" });
expect(lines.filter((l) => l.kind === "hunk")).toHaveLength(2);
});
it("produces one hunk for the long middle run, none for the short edges", () => {
// Edge runs (2 lines) fit within their one-sided 3-line context budget;
// only the interior run (10 lines, budgeted 3+3=6) needs to collapse.
const middle = Array.from({ length: 10 }, (_, i) => `c${i}`);
const oldText = ["lead0", "lead1", "OLD1", ...middle, "OLD2", "trail0", "trail1"].join("\n");
const newText = ["lead0", "lead1", "NEW1", ...middle, "NEW2", "trail0", "trail1"].join("\n");
const lines = computeDiffLines(oldText, newText);
const dels = lines.filter((l) => l.kind === "del").map((l) => l.text);
const adds = lines.filter((l) => l.kind === "add").map((l) => l.text);
expect(dels).toEqual(["OLD1", "OLD2"]);
expect(adds).toEqual(["NEW1", "NEW2"]);
expect(lines.filter((l) => l.kind === "hunk")).toHaveLength(1);
expect(texts(lines).slice(0, 2)).toEqual([
["ctx", "lead0"],
["ctx", "lead1"],
]);
expect(texts(lines).slice(-2)).toEqual([
["ctx", "trail0"],
["ctx", "trail1"],
]);
});
it("handles an old file with no trailing newline", () => {
const lines = computeDiffLines("a\nb", "a\nb\nc");
expect(texts(lines)).toEqual([
["ctx", "a"],
["ctx", "b"],
["add", "c"],
]);
});
});
describe("diffStats", () => {
it("counts add/del lines, ignoring context and hunk markers", () => {
const lines = computeDiffLines("a\nb\nc\n", "a\nx\ny\nc\n");
expect(diffStats(lines)).toEqual({ added: 2, removed: 1 });
});
it("is zero for identical text", () => {
expect(diffStats(computeDiffLines("same\n", "same\n"))).toEqual({ added: 0, removed: 0 });
});
});