Closes #246. The transcript, composer and approval controls move from structured.ts/structuredRender.ts into components under web/src/components/structured/, against docs/STRUCTURED-VIEW.md: eight row kinds, both projections, the single-active approval nonce, and diff rendering with its own line counts. The pure layers stay shared — structuredDiff.ts computes the diff, structuredRender.ts still sanitizes markdown — and model.ts holds the client-side decisions (activity grouping, notice classification, offered decisions, `/` completion) so they are testable without a DOM. Like TerminalPane, the pane draws the body only; pane chrome reads the connection through onStatusChange. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
51 lines
2.1 KiB
TypeScript
51 lines
2.1 KiB
TypeScript
// @vitest-environment happy-dom
|
||
import { cleanup, render, screen } from "@testing-library/react";
|
||
import { afterEach, describe, expect, it } from "vitest";
|
||
|
||
import { DiffView } from "./DiffView.js";
|
||
import { toolDiffMeta } from "./model.js";
|
||
|
||
afterEach(cleanup);
|
||
|
||
function lines(kind: string): string[] {
|
||
return [...document.querySelectorAll(`.st-ln-${kind}`)].map((el) => el.textContent ?? "");
|
||
}
|
||
|
||
describe("DiffView", () => {
|
||
it("marks insertions and deletions with their column prefixes", () => {
|
||
render(<DiffView diff={{ path: "src/a.ts", oldText: "one\ntwo\n", newText: "one\nTWO\nthree\n" }} />);
|
||
expect(lines("del")).toEqual(["- two"]);
|
||
expect(lines("add")).toEqual(["+ TWO", "+ three"]);
|
||
expect(lines("ctx")).toEqual([" one"]);
|
||
expect(screen.getByText("src/a.ts")).toBeTruthy();
|
||
});
|
||
|
||
it("carries the +added −removed counts the tool head shows", () => {
|
||
render(<DiffView diff={{ path: "a.ts", oldText: "one\ntwo\n", newText: "one\nTWO\nthree\n" }} />);
|
||
const body = document.querySelector<HTMLElement>(".st-diff")!;
|
||
expect(body.dataset.added).toBe("2");
|
||
expect(body.dataset.removed).toBe("1");
|
||
expect(toolDiffMeta([{ type: "diff", path: "a.ts", oldText: "one\ntwo\n", newText: "one\nTWO\nthree\n" }]))
|
||
.toBe("+2 −1");
|
||
});
|
||
|
||
it("renders every line of a new file as added", () => {
|
||
render(<DiffView diff={{ path: "new.ts", newText: "alpha\nbeta\n" }} />);
|
||
expect(lines("add")).toEqual(["+ alpha", "+ beta"]);
|
||
expect(lines("del")).toEqual([]);
|
||
});
|
||
|
||
it("collapses a long unchanged run to a hunk summary", () => {
|
||
const filler = Array.from({ length: 20 }, (_, i) => `line ${i}`).join("\n");
|
||
render(<DiffView diff={{ path: "a.ts", oldText: `head\n${filler}\n`, newText: `HEAD\n${filler}\n` }} />);
|
||
expect(lines("hunk")).toEqual(["⋯ 17 unchanged lines ⋯"]);
|
||
});
|
||
|
||
it("copies the whole diff, prefixes included", () => {
|
||
render(<DiffView diff={{ path: "a.ts", oldText: "one\n", newText: "two\n" }} />);
|
||
const body = document.querySelector<HTMLElement>(".st-diff")!;
|
||
expect(body.getAttribute("aria-label")).toBe("Copy diff");
|
||
expect(body.textContent).toBe("- one+ two");
|
||
});
|
||
});
|