/** * Tests for edit_diagram operations on cells that draw.io wraps in * or (cells with links, tooltips or custom data). * The id sits on the wrapper; the inner mxCell has none. */ import { deflateRawSync } from "node:zlib" import { beforeAll, describe, expect, it, vi } from "vitest" import { installDomPolyfill } from "../src/dom.ts" beforeAll(() => { installDomPolyfill() }) import { applyDiagramOperations } from "../src/diagram-operations.ts" const DOC = `` describe("wrapped cells", () => { it("deletes a UserObject cell with its edges and children", () => { const { result, errors } = applyDiagramOperations(DOC, [ { operation: "delete", cell_id: "a" }, ]) expect(errors).toEqual([]) expect(result).not.toContain('id="a"') expect(result).not.toContain('id="e1"') expect(result).not.toContain('id="child"') expect(result).toContain('id="b"') }) it("cascades to a wrapped edge when deleting a plain cell", () => { const { result, errors } = applyDiagramOperations(DOC, [ { operation: "delete", cell_id: "b" }, { operation: "delete", cell_id: "e1" }, ]) // e1 was already removed by the cascade, so no warning for it expect(errors).toEqual([]) expect(result).not.toContain('id="e1"') expect(result).toContain('id="a"') }) it("warns when deleting a cell that does not exist", () => { const { errors } = applyDiagramOperations(DOC, [ { operation: "delete", cell_id: "missing" }, ]) expect(errors).toHaveLength(1) expect(errors[0]).toMatchObject({ type: "delete", cellId: "missing" }) }) it("updates a UserObject cell", () => { const { result, errors } = applyDiagramOperations(DOC, [ { operation: "update", cell_id: "a", new_xml: ``, }, ]) expect(errors).toEqual([]) expect(result).toContain('label="A2"') expect(result.match(/id="a"/g)).toHaveLength(1) }) it("refuses to add a cell whose id a UserObject already uses", () => { const { errors } = applyDiagramOperations(DOC, [ { operation: "add", cell_id: "a", new_xml: ``, }, ]) expect(errors[0]?.message).toContain("already exists") }) }) describe("cascade delete logging", () => { it("does not write cascade logs to stdout (the JSON-RPC channel)", () => { const plain = `` const spy = vi.spyOn(console, "log").mockImplementation(() => {}) const { result } = applyDiagramOperations(plain, [ { operation: "delete", cell_id: "x" }, ]) expect(result).not.toContain('id="e"') expect(spy).not.toHaveBeenCalled() spy.mockRestore() }) }) describe("pages without a ", () => { const ADD_A = { operation: "add" as const, cell_id: "a", new_xml: ``, } it("treats an empty page as a blank page", () => { const doc = `` const { result, errors } = applyDiagramOperations(doc, [ADD_A]) expect(errors).toEqual([]) expect(result).toContain('') expect(result).toContain('') expect(result).toContain(' { const model = `` const compressed = deflateRawSync( Buffer.from(encodeURIComponent(model)), ).toString("base64") const doc = `${compressed}` const { result, errors } = applyDiagramOperations(doc, [ADD_A]) expect(errors).toEqual([]) expect(result).not.toContain(compressed) expect(result).toContain(' { const doc = `not base64 !!` const { errors } = applyDiagramOperations(doc, [ADD_A]) expect(errors[0]?.cellId).toBe("") expect(errors[0]?.message).toContain("could not be decompressed") }) }) describe("a wrapped mxCell with its wrapper's id", () => { const doc = `` it("deletes the whole wrapper", () => { const { result, errors } = applyDiagramOperations(doc, [ { operation: "delete", cell_id: "u" }, ]) expect(errors).toEqual([]) expect(result).not.toContain("UserObject") }) it("replaces the wrapper on update", () => { const { result, errors } = applyDiagramOperations(doc, [ { operation: "update", cell_id: "u", new_xml: ``, }, ]) expect(errors).toEqual([]) expect(result.match(/