feat: add deterministic Diagram IR v2 authoring
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { normalizeDiagramIR, validateDiagramIR } from "./diagram-ir.js";
|
||||
|
||||
test("IR v2 supports pages, containers, geometry, semantics, and waypoints", () => {
|
||||
const ir = validateDiagramIR({
|
||||
version: 2,
|
||||
title: "Platform",
|
||||
pages: [{
|
||||
id: "system",
|
||||
title: "System",
|
||||
layout: { type: "layered", direction: "horizontal", gridSize: 10 },
|
||||
nodes: [
|
||||
{ id: "zone", label: "Application zone", kind: "container", geometry: { x: 20, y: 20, width: 520, height: 240 } },
|
||||
{ id: "web", label: "Web", kind: "service", parentId: "zone", properties: { tier: "edge" } },
|
||||
{ id: "api", label: "API", kind: "service", parentId: "zone" },
|
||||
],
|
||||
edges: [{ id: "request", source: "web", target: "api", kind: "request", waypoints: [{ x: 200, y: 100 }] }],
|
||||
}],
|
||||
provenance: { source: "test" },
|
||||
});
|
||||
|
||||
const normalized = normalizeDiagramIR(ir);
|
||||
assert.equal(normalized.version, 2);
|
||||
assert.equal(normalized.pages.length, 1);
|
||||
assert.equal(normalized.pages[0].nodes[0].kind, "container");
|
||||
assert.deepEqual(normalized.pages[0].edges[0].waypoints, [{ x: 200, y: 100 }]);
|
||||
});
|
||||
|
||||
test("IR v2 rejects unknown parents, parent cycles, unsupported fields, and reserved IDs", () => {
|
||||
assert.throws(() => validateDiagramIR({
|
||||
version: 2,
|
||||
pages: [{
|
||||
id: "page",
|
||||
title: "Page",
|
||||
nodes: [{ id: "0", label: "Reserved", parentId: "missing" }],
|
||||
edges: [],
|
||||
}],
|
||||
}), /reserved id.*unknown parent/si);
|
||||
|
||||
assert.throws(() => validateDiagramIR({
|
||||
version: 2,
|
||||
pages: [{
|
||||
id: "page",
|
||||
title: "Page",
|
||||
nodes: [
|
||||
{ id: "a", label: "A", parentId: "b" },
|
||||
{ id: "b", label: "B", parentId: "a" },
|
||||
],
|
||||
edges: [],
|
||||
}],
|
||||
}), /parent cycle/i);
|
||||
|
||||
assert.throws(() => validateDiagramIR({
|
||||
version: 2,
|
||||
pages: [{
|
||||
id: "page",
|
||||
title: "Page",
|
||||
nodes: [
|
||||
{ id: "parent", label: "Not a container" },
|
||||
{ id: "child", label: "Child", parentId: "parent" },
|
||||
],
|
||||
edges: [],
|
||||
}],
|
||||
}), /parent.*not a container/i);
|
||||
|
||||
assert.throws(() => validateDiagramIR({
|
||||
version: 2,
|
||||
unexpected: true,
|
||||
pages: [{ id: "page", title: "Page", nodes: [{ id: "a", label: "A", typo: true }], edges: [] }],
|
||||
}), /unsupported field/i);
|
||||
});
|
||||
|
||||
test("IR v1 normalizes into a single v2 page without changing stable IDs", () => {
|
||||
const v1 = validateDiagramIR({
|
||||
version: 1,
|
||||
title: "Legacy",
|
||||
direction: "horizontal",
|
||||
nodes: [{ id: "client", label: "Client" }, { id: "api", label: "API" }],
|
||||
edges: [{ id: "call", source: "client", target: "api" }],
|
||||
});
|
||||
const normalized = normalizeDiagramIR(v1);
|
||||
assert.equal(normalized.pages[0].title, "Legacy");
|
||||
assert.deepEqual(normalized.pages[0].nodes.map((node) => node.id), ["client", "api"]);
|
||||
assert.equal(normalized.pages[0].edges[0].id, "call");
|
||||
assert.throws(() => validateDiagramIR({ version: 1, title: 42, nodes: [{ id: "node", label: "Node" }] }), /title must be a string/i);
|
||||
});
|
||||
Reference in New Issue
Block a user