import assert from "node:assert/strict"; import test from "node:test"; import type { DiagramIRV2 } from "../../model/diagram-ir.js"; import { validateDiagramIR } from "../../model/diagram-ir.js"; import { createArchitectureTimelapse } from "./timelapse.js"; const one: DiagramIRV2 = { version: 2, title: "Arch", pages: [{ id: "p", title: "P", nodes: [{ id: "a", label: "A", provenance: { source: "model" } }, { id: "b", label: "B" }], edges: [{ id: "ab", source: "a", target: "b" }] }] }; const two: DiagramIRV2 = { version: 2, title: "Arch", pages: [{ id: "p", title: "P", nodes: [{ id: "a", label: "A2", provenance: { source: "model" } }, { id: "c", label: "C" }], edges: [{ id: "ac", source: "a", target: "c" }] }] }; test("timelapse emits valid immutable frames with stable change classifications", () => { const input = [{ id: "v1", label: "Before", ir: one }, { id: "v2", label: "After", ir: two }]; const before = structuredClone(input); const result = createArchitectureTimelapse(input); assert.deepEqual(result, createArchitectureTimelapse(input)); assert.deepEqual(input, before); result.frames.forEach((frame) => assert.equal(validateDiagramIR(frame.ir), frame.ir)); assert.deepEqual(result.frames[1].changes.nodes, { added: ["p/c"], removed: ["p/b"], modified: ["p/a"], unchanged: [] }); assert.deepEqual(result.frames[1].changes.edges, { added: ["p/ac"], removed: ["p/ab"], modified: [], unchanged: [] }); assert.equal(result.frames[1].ir.pages[0].nodes[0].provenance?.source, "model"); }); test("timelapse rejects empty and invalid snapshots", () => { assert.throws(() => createArchitectureTimelapse([]), /snapshot/i); assert.throws(() => createArchitectureTimelapse([{ id: "bad", label: "Bad", ir: { version: 2, pages: [] } as DiagramIRV2 }]), /non-empty/i); });