import { describe, expect, test } from "bun:test"; import { renderToStaticMarkup } from "react-dom/server"; import { DiffView, buildDiffRows, diffLines, diffStats, diffWords, failedLoadParts, } from "./diff-view"; import { RevisionHistory, previousContent, type Revision } from "./revision-history"; const BEFORE = "## How we work\n\n- Production changes need a second reviewer.\n- Prefer small PRs."; const AFTER = `${BEFORE}\n- Always open pull requests as drafts.`; describe("diffLines", () => { test("marks an appended rule as the only added line", () => { const lines = diffLines(BEFORE, AFTER); expect(lines.filter((line) => line.kind !== "context")).toEqual([ { kind: "added", text: "- Always open pull requests as drafts." }, ]); expect(diffStats(lines)).toEqual({ added: 1, removed: 0 }); }); test("puts removed lines before added ones in a changed block", () => { const lines = diffLines("a\nold one\nold two\nz", "a\nnew one\nz"); expect(lines).toEqual([ { kind: "context", text: "a" }, { kind: "removed", text: "old one" }, { kind: "removed", text: "old two" }, { kind: "added", text: "new one" }, { kind: "context", text: "z" }, ]); }); test("everything is added when there was nothing before", () => { expect(diffLines("", "one\ntwo")).toEqual([ { kind: "added", text: "one" }, { kind: "added", text: "two" }, ]); }); }); describe("diffWords", () => { test("reconstructs both sides and groups a multi-word change", () => { const before = "Backups replicate to eu-west-1."; const after = "Backups replicate to eu-central-1 since the 1 Sep storage migration."; const segments = diffWords(before, after); const side = (drop: "added" | "removed") => segments .filter((segment) => segment.kind !== drop) .map((segment) => segment.text) .join(""); expect(side("added")).toBe(before); expect(side("removed")).toBe(after); expect(segments.find((segment) => segment.kind === "added")?.text).toBe( "eu-central-1 since the 1 Sep storage migration", ); expect(segments.find((segment) => segment.kind === "removed")?.text).toBe("eu-west-1"); }); }); describe("buildDiffRows", () => { const long = Array.from({ length: 20 }, (_, index) => `line ${index + 1}`); const changed = long.map((line, index) => (index === 10 ? "line eleven" : line)); test("keeps three lines around a change and collapses the rest", () => { const rows = buildDiffRows(diffLines(long.join("\n"), changed.join("\n"))); const collapsed = rows.filter((row) => row.type === "collapsed"); expect(collapsed).toHaveLength(2); expect(collapsed[0]?.type === "collapsed" && collapsed[0].lines).toHaveLength(7); expect(collapsed[1]?.type === "collapsed" && collapsed[1].lines).toHaveLength(6); const shown = rows.filter((row) => row.type === "line"); // 3 before + removed + added + 3 after. expect(shown).toHaveLength(8); }); test("shows everything with an infinite context", () => { const rows = buildDiffRows(diffLines(long.join("\n"), changed.join("\n")), Infinity); expect(rows.every((row) => row.type === "line")).toBe(true); }); test("pairs one removed and one added line with word highlights", () => { const rows = buildDiffRows(diffLines("keep\nwait 30 minutes", "keep\nwait 60 minutes")); const removed = rows.find((row) => row.type === "line" && row.line.kind === "removed"); expect(removed?.type === "line" && removed.words).toEqual([ { kind: "same", text: "wait " }, { kind: "removed", text: "30" }, { kind: "same", text: " minutes" }, ]); }); }); describe("DiffView", () => { test("labels changes for screen readers, not only with colour", () => { const html = renderToStaticMarkup( , ); expect(html).toContain("Added: "); expect(html).toContain("Always open pull requests as drafts."); // Markdown reads as text: no "##" or "- " markers. expect(html).not.toContain("## How we work"); expect(html).toContain("How we work"); }); test("says so when nothing changed", () => { const html = renderToStaticMarkup(); expect(html).toContain("No changes. This version matches the one before it."); }); test("a failed load says what to do, with the reference behind Technical details", () => { const cause = Object.assign( new Error("Opengeni API 500: internal error Reference: req_diff_1."), { status: 500 }, ); const html = renderToStaticMarkup( , ); expect(html).toContain("Couldn't load the changes."); expect(html).toContain("Opengeni couldn't finish the request. Try again in a moment."); expect(html).toContain("Technical details"); expect(html).toContain("req_diff_1"); expect(html).not.toContain("Opengeni API 500"); const neutral = renderToStaticMarkup( , ); expect(neutral).toContain("Technical details"); expect(neutral).toContain("req_diff_2"); expect(neutral).not.toContain("API 500: internal error"); }); }); describe("failedLoadParts", () => { test("replaces a raw API error string passed as the detail", () => { const parts = failedLoadParts({ detail: "Opengeni API 404: knowledge entry not found Reference: req_404.", }); expect(parts.detail).toBe("It may have been removed. Reload the page and try again."); expect(parts.details).toContainEqual({ label: "Status", value: "HTTP 404" }); expect(parts.details).toContainEqual({ label: "Reference", value: "req_404", copyable: true, }); }); test("keeps a detail the caller wrote", () => { expect(failedLoadParts({ detail: "Check your connection and try again." })).toEqual({ detail: "Check your connection and try again.", details: [], }); }); }); describe("RevisionHistory", () => { const revisions: Revision[] = [ { id: "2", author: "Bendik Hansen", createdLabel: "3 days ago", summary: "Added: Prefer small PRs.", content: BEFORE, }, { id: "1", author: "Maria Chen", createdLabel: "18 Sep", summary: "Created the instructions", content: "## How we work", }, ]; test("each version is compared with the one saved before it", () => { expect(previousContent(revisions, 0)).toBe("## How we work"); expect(previousContent(revisions, 1)).toBe(""); }); test("the newest is current; older versions offer Restore", () => { const html = renderToStaticMarkup( undefined} />, ); expect(html).toContain("Current"); expect(html).toContain("Restore this version: Created the instructions"); expect(html).not.toContain("Restore this version: Added: Prefer small PRs."); }); test("explains when restoring isn't allowed", () => { const html = renderToStaticMarkup( undefined} restoreDisabledReason="Only workspace admins can restore instructions." />, ); expect(html).toContain("Only workspace admins can restore instructions."); // Still focusable so the reason can be read: aria-disabled plus a described-by reason. expect(html).toMatch( /]*aria-label="Restore this version[^"]*"[^>]*aria-disabled="true"[^>]*aria-describedby="[^"]+"/, ); expect(html).not.toMatch(/]*disabled=""[^>]*aria-label="Restore this version/); }); });