import { describe, expect, it } from "vitest"; import { diffWords } from "./ArticleBodyDiff"; /** Rebuild each side from the token stream — the diff must lose nothing. */ const rebuild = ( tokens: ReturnType, side: "before" | "after", ): string => tokens .filter((t) => side === "before" ? t.op !== "added" : t.op !== "removed", ) .map((t) => t.text) .join(""); describe("diffWords", () => { it("marks only the words that actually changed", () => { const tokens = diffWords( "The carrier shall deliver within 30 days.", "The carrier shall deliver within 45 days.", ); expect(tokens.filter((t) => t.op === "removed").map((t) => t.text)).toEqual([ "30", ]); expect(tokens.filter((t) => t.op === "added").map((t) => t.text)).toEqual([ "45", ]); }); it("reconstructs both sides losslessly, whitespace included", () => { const before = "Payment is due\nwithin ten (10) working days."; const after = "Payment is due\nwithin five (5) working days of invoice."; const tokens = diffWords(before, after); expect(rebuild(tokens, "before")).toBe(before); expect(rebuild(tokens, "after")).toBe(after); }); it("reports nothing changed for identical text", () => { const tokens = diffWords("Same clause.", "Same clause."); expect(tokens.every((t) => t.op === "same")).toBe(true); }); it("handles a body being emptied or written from scratch", () => { expect(rebuild(diffWords("Some clause.", ""), "after")).toBe(""); expect(rebuild(diffWords("", "Brand new clause."), "before")).toBe(""); }); it("falls back to a whole-block replace on pathological input", () => { // Past MAX_TOKENS the LCS table is skipped; the change must still be // reported truthfully rather than silently dropped. const before = Array.from({ length: 2000 }, (_, i) => `a${i}`).join(" "); const after = Array.from({ length: 2000 }, (_, i) => `b${i}`).join(" "); const tokens = diffWords(before, after); expect(rebuild(tokens, "before")).toBe(before); expect(rebuild(tokens, "after")).toBe(after); }); });