import pako from "pako" import { describe, expect, it } from "vitest" import { diffDiagrams, EMPTY_SUMMARY, isSameDocument } from "@/lib/diagram-diff" const doc = (cells: string) => `${cells}` const box = (id: string, value = id) => `` const edge = (id: string, source: string, target: string) => `` const pageOf = (name: string, cells: string, attrs = "") => `${cells}` const file = (...pages: string[]) => `${pages.join("")}` const packedPage = (name: string, cells: string) => { const model = `${cells}` const packed = Buffer.from( pako.deflateRaw(encodeURIComponent(model)), ).toString("base64") return `${packed}` } describe("diffDiagrams", () => { it("counts added shapes and connectors on an empty canvas", () => { const { summary, touchedIds } = diffDiagrams( "", doc(box("a") + box("b") + edge("e", "a", "b")), ) expect(summary.shapesAdded).toBe(2) expect(summary.edgesAdded).toBe(1) expect(touchedIds.sort()).toEqual(["a", "b", "e"]) }) it("separates added, changed and removed cells", () => { const before = doc(box("a") + box("b") + edge("e", "a", "b")) const after = doc(box("a", "renamed") + box("c") + edge("e", "a", "c")) const { summary, touchedIds } = diffDiagrams(before, after) expect(summary).toEqual({ shapesAdded: 1, shapesRemoved: 1, shapesChanged: 1, edgesAdded: 0, edgesRemoved: 0, edgesChanged: 1, }) expect(touchedIds.sort()).toEqual(["a", "c", "e"]) }) it("ignores how the same cell is written", () => { // As the model writes it, and as draw.io saves it const model = `` const saved = `` expect(diffDiagrams(model, saved).summary).toEqual(EMPTY_SUMMARY) }) it("reports nothing for identical diagrams", () => { const xml = doc(box("a")) const { summary, touchedIds } = diffDiagrams(xml, xml) expect(summary).toEqual(EMPTY_SUMMARY) expect(touchedIds).toEqual([]) }) it("ignores values draw.io drops when it saves", () => { // The model writes defaults and long numbers; draw.io leaves them out const model = doc( ``, ) const saved = doc( ``, ) expect(diffDiagrams(model, saved).summary).toEqual(EMPTY_SUMMARY) expect(isSameDocument(model, saved)).toBe(true) }) it("reads styles as draw.io does: a later key wins", () => { const styled = (style: string) => doc( ``, ) const blueThenRed = styled("fillColor=#00f;fillColor=#f00") expect( isSameDocument( styled("fillColor=#f00;fillColor=#00f"), blueThenRed, ), ).toBe(false) expect( isSameDocument( styled("fillColor=#f00;rounded=1"), blueThenRed.replace( "fillColor=#00f;fillColor=#f00", "rounded=1;fillColor=#f00", ), ), ).toBe(true) }) it("reads the id from wrappers", () => { const wrapped = `` const { summary, touchedIds } = diffDiagrams(doc(""), doc(wrapped)) expect(summary.shapesAdded).toBe(1) expect(touchedIds).toEqual(["u1"]) }) it("only looks at the first page", () => { const twoPages = file(pageOf("p1", box("a")), pageOf("p2", box("z"))) expect(diffDiagrams("", twoPages).summary.shapesAdded).toBe(1) }) it("reads compressed pages", () => { const xml = file(packedPage("p1", box("a") + box("b"))) expect(diffDiagrams("", xml).summary.shapesAdded).toBe(2) expect(diffDiagrams(xml, xml).fromScratch).toBe(false) }) it("treats unparsable XML as empty", () => { expect( diffDiagrams("", doc(box("a"))).fromScratch, ).toBe(true) }) }) describe("isSameDocument", () => { const a = file(pageOf("One", box("a")), pageOf("Two", box("b"))) it("is true for the same pages written differently", () => { const b = file( pageOf("One", box("a"), ' dx="300" dy="120" grid="1"'), packedPage("Two", box("b")), ) expect(isSameDocument(a, b)).toBe(true) }) it("sees a change on the second page", () => { const b = file(pageOf("One", box("a")), pageOf("Two", box("b", "x"))) expect(diffDiagrams(a, b).summary).toEqual(diffDiagrams(a, a).summary) expect(isSameDocument(a, b)).toBe(false) }) it("sees added pages, renamed pages and changed page settings", () => { expect(isSameDocument(a, file(pageOf("One", box("a"))))).toBe(false) expect( isSameDocument( a, file(pageOf("One", box("a")), pageOf("Renamed", box("b"))), ), ).toBe(false) expect( isSameDocument( file(pageOf("One", box("a"), ' background="#ffffff"')), file(pageOf("One", box("a"), ' background="#000000"')), ), ).toBe(false) }) it("compares a setting written on one side with draw.io's default", () => { const withAttrs = (attrs: string) => file(pageOf("One", box("a"), attrs)) const plain = withAttrs("") // Defaults, as draw.io fills them in (paper: Letter or A4) expect( isSameDocument( plain, withAttrs(' math="0" pageWidth="827" pageHeight="1169"'), ), ).toBe(true) expect( isSameDocument( plain, withAttrs(' pageWidth="850" pageHeight="1100"'), ), ).toBe(true) // Real changes expect(isSameDocument(plain, withAttrs(' math="1"'))).toBe(false) expect(isSameDocument(plain, withAttrs(' shadow="1"'))).toBe(false) expect(isSameDocument(plain, withAttrs(' pageWidth="1654"'))).toBe( false, ) expect( isSameDocument( plain, withAttrs( ' backgroundImage="{"src":"x.png"}"', ), ), ).toBe(false) }) it("sees a background set on one side", () => { expect( isSameDocument( file(pageOf("One", box("a"))), file(pageOf("One", box("a"), ' background="#000000"')), ), ).toBe(false) }) it("sees shapes brought to front or sent back", () => { expect( isSameDocument( file(pageOf("One", box("a") + box("b"))), file(pageOf("One", box("b") + box("a"))), ), ).toBe(false) }) it("sees a hidden layer", () => { const hidden = file(pageOf("One", box("a"))).replace( '', '', ) expect(isSameDocument(file(pageOf("One", box("a"))), hidden)).toBe( false, ) }) it("does not care where children of other parents are written", () => { const group = (inner: string) => `${inner}` const child = `` expect( isSameDocument( file(pageOf("One", group(child) + box("a"))), file(pageOf("One", group("") + box("a") + child)), ), ).toBe(true) }) })