/** * TASK-0123 — the diff's OLD-side extractor (`blockPlainText`) + the * word-level diff renderer's input contract (`wordDiff` from @factory/editor). * * The writing diff renders `wordDiff(oldText, newText)` for improve/rewrite/ * translate (summarize shows the raw summary). These tests pin the pure * pieces: the extractor across the content model's shapes, and that the * shared diff util produces same/del/add segments that reconstruct BOTH sides * (so the UI can't silently drop or duplicate text). * * @vitest-environment node (no DOM). */ import { describe, it, expect } from "vitest"; import { blockPlainText } from "@/lib/ai"; import { wordDiff, reconstructOld, reconstructNew } from "@factory/editor"; describe("blockPlainText (the diff's OLD side)", () => { it("reads {text} (the canonical content model)", () => { expect(blockPlainText({ text: "Hello world" })).toBe("Hello world"); }); it("passes a raw string through (legacy/inline path)", () => { expect(blockPlainText("just a string")).toBe("just a string"); }); it("falls back to {title}", () => { expect(blockPlainText({ title: "Page title" })).toBe("Page title"); }); it("prefers {text} over {title}", () => { expect(blockPlainText({ text: "body", title: "head" })).toBe("body"); }); it("returns '' for null/undefined/non-object", () => { expect(blockPlainText(null)).toBe(""); expect(blockPlainText(undefined)).toBe(""); expect(blockPlainText(42)).toBe(""); expect(blockPlainText({ text: 42 })).toBe(""); }); }); describe("wordDiff (the diff renderer's input)", () => { it("marks unchanged text as `same`", () => { expect(wordDiff("hello world", "hello world")).toEqual([{ type: "same", text: "hello world" }]); }); it("produces add/del segments that reconstruct BOTH sides", () => { const old = "the quick brown fox"; const neu = "the slow brown fox"; const segs = wordDiff(old, neu); // At least one del (quick) and one add (slow) — the visible diff. expect(segs.some((s) => s.type === "del")).toBe(true); expect(segs.some((s) => s.type === "add")).toBe(true); // The segments must reconstruct the original AND the proposed text, so // the UI can't drop or duplicate words. expect(reconstructOld(segs)).toBe(old); expect(reconstructNew(segs)).toBe(neu); }); it("treats a full replacement as all-del + all-add", () => { const segs = wordDiff("alpha beta", "gamma delta"); expect(reconstructOld(segs)).toBe("alpha beta"); expect(reconstructNew(segs)).toBe("gamma delta"); expect(segs.some((s) => s.type === "del")).toBe(true); expect(segs.some((s) => s.type === "add")).toBe(true); }); it("returns [] for an empty old text", () => { expect(wordDiff("", "new")).toEqual([{ type: "add", text: "new" }]); }); });