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/components/structured/diff.test.ts
T
Miguel Palhas 8a05ef2eb7 feat(maestro): wire React app as sole web entry point
Deletes the vanilla shell and mounts App.tsx on #root. main.ts and the
23 views it drove are gone; App.tsx composes the ported components —
sidebar, multi-view, command bar, spawn modal, help and restart
overlays, daemon link, drawer — around the catalog and layout stores.

The pure parts of four deleted modules moved next to the components
that still use them: the spawn-form model and spawnSimilar into
components/spawn, the transcript's markdown/format helpers and the diff
into components/structured, the help overlay's grouping into
components/dialogs. Document-level event names now live in uiEvents.ts.

Deduplicated two pairs the migration left side by side: the sidebar
uses dialogs/KillDialog and common/ContextMenu. It also listens for the
command bar's kill/rename events, which nothing had picked up since
catalog.ts went away.

styles.css stays as the token and base layer, with `#app` renamed to
`#root` and the pane-body rules re-keyed off the classes the React
panes actually carry.

Closes #253
2026-09-09 02:27:42 +01:00

188 lines
6.5 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { computeDiffLines, diffStats, type DiffLine } from "./diff.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("handles an insertion without changing surrounding lines", () => {
expect(texts(computeDiffLines("a\nc\n", "a\nb\nc\n"))).toEqual([
["ctx", "a"],
["add", "b"],
["ctx", "c"],
]);
});
it("handles a deletion without changing surrounding lines", () => {
expect(texts(computeDiffLines("a\nb\nc\n", "a\nc\n"))).toEqual([
["ctx", "a"],
["del", "b"],
["ctx", "c"],
]);
});
it("renders two empty files without invented lines", () => {
expect(computeDiffLines("", "")).toEqual([]);
});
it("shows when a final newline is added", () => {
expect(texts(computeDiffLines("a", "a\n"))).toEqual([
["del", "a"],
["eof", "\\ No newline at end of file"],
["add", "a"],
]);
});
it("shows when a final newline is removed", () => {
expect(texts(computeDiffLines("a\n", "a"))).toEqual([
["del", "a"],
["add", "a"],
["eof", "\\ No newline at end of file"],
]);
});
it("marks an unchanged unterminated final context line", () => {
expect(texts(computeDiffLines("a\nb\nc", "a\nX\nc"))).toEqual([
["ctx", "a"],
["del", "b"],
["add", "X"],
["ctx", "c"],
["eof", "\\ No newline at end of file"],
]);
});
it("marks an unterminated new file only once", () => {
const lines = computeDiffLines(null, "a");
expect(texts(lines)).toEqual([
["add", "a"],
["eof", "\\ No newline at end of file"],
]);
expect(lines.filter((line) => line.kind === "eof")).toHaveLength(1);
});
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("preserves the EOF marker when a long unterminated trailing run collapses", () => {
const trailing = Array.from({ length: 10 }, (_, i) => `t${i}`);
const oldText = ["OLD", ...trailing].join("\n");
const newText = ["NEW", ...trailing].join("\n");
expect(texts(computeDiffLines(oldText, newText))).toEqual([
["del", "OLD"],
["add", "NEW"],
["ctx", "t0"],
["ctx", "t1"],
["ctx", "t2"],
["hunk", "⋯ 7 unchanged lines ⋯"],
["eof", "\\ No newline at end of file"],
]);
});
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(-3)).toEqual([
["ctx", "trail0"],
["ctx", "trail1"],
["eof", "\\ No newline at end of file"],
]);
});
it("handles an old file with no trailing newline", () => {
const lines = computeDiffLines("a\nb", "a\nb\nc");
expect(texts(lines)).toEqual([
["ctx", "a"],
["del", "b"],
["eof", "\\ No newline at end of file"],
["add", "b"],
["add", "c"],
["eof", "\\ No newline at end of file"],
]);
});
it("falls back to a bounded coarse diff for a pathological changed middle", () => {
const oldLines = Array.from({ length: 501 }, (_, index) => `line${index}`);
const newLines = [...oldLines.slice(1), oldLines[0]];
const lines = computeDiffLines(`${oldLines.join("\n")}\n`, `${newLines.join("\n")}\n`);
expect(lines).toHaveLength(1_002);
expect(lines.slice(0, 501).every((line) => line.kind === "del")).toBe(true);
expect(lines.slice(501).every((line) => line.kind === "add")).toBe(true);
});
});
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 });
});
});