feat: add deterministic Diagram IR v2 authoring

This commit is contained in:
2026-09-03 10:05:29 +00:00
parent 305006c8cb
commit 50a07b90a9
31 changed files with 2108 additions and 98 deletions
+103
View File
@@ -0,0 +1,103 @@
import assert from "node:assert/strict";
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { parseAllPages } from "../../services/drawio-parser/parser.js";
import { run } from "./action.js";
const VALID_SPEC = `version: 1
title: Login flow
direction: horizontal
nodes:
- id: client
label: Client
- id: api
label: API
edges:
- id: login
source: client
target: api
label: HTTPS
`;
test("build converts a YAML Diagram IR into deterministic native Draw.io", () => {
const dir = mkdtempSync(join(tmpdir(), "drawio-build-test-"));
const input = join(dir, "login.yaml");
const output = join(dir, "login.drawio");
try {
writeFileSync(input, VALID_SPEC, "utf8");
const result = run(input, 0, output);
assert.equal(existsSync(output), true);
assert.deepEqual(result.summary, { nodes: 2, edges: 1, pages: 1 });
const firstXml = readFileSync(output, "utf8");
run(input, 0, output);
assert.equal(readFileSync(output, "utf8"), firstXml);
const [page] = parseAllPages(output);
assert.equal(page.pageName, "Login flow");
assert.deepEqual([...page.shapes.keys()].sort(), ["api", "client"]);
assert.equal(page.edges[0].sourceId, "client");
assert.equal(page.edges[0].targetId, "api");
assert.equal(page.shapes.get("client")?.x, 40);
assert.equal(page.shapes.get("api")?.x, 240);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test("build expands default page dimensions to fit generated content", () => {
const dir = mkdtempSync(join(tmpdir(), "drawio-build-page-size-test-"));
const input = join(dir, "wide.yaml");
const output = join(dir, "wide.drawio");
try {
const nodes = Array.from({ length: 10 }, (_, index) => ` - { id: node-${index}, label: Node ${index} }`).join("\n");
writeFileSync(input, `version: 1\ndirection: horizontal\nnodes:\n${nodes}\n`, "utf8");
run(input, 0, output);
const [page] = parseAllPages(output);
const right = Math.max(...[...page.shapes.values()].map((shape) => shape.x2));
assert.ok(page.pageWidth >= right + 40);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test("build rejects duplicate IDs and unknown edge endpoints", () => {
const dir = mkdtempSync(join(tmpdir(), "drawio-build-invalid-test-"));
const input = join(dir, "invalid.yaml");
const output = join(dir, "invalid.drawio");
try {
writeFileSync(input, `version: 1\nnodes:\n - { id: repeated, label: A }\n - { id: repeated, label: B }\nedges:\n - { id: edge, source: repeated, target: missing }\n`, "utf8");
assert.throws(() => run(input, 0, output), /duplicate node id.*unknown target/si);
assert.equal(existsSync(output), false);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test("build serializes every v2 page and honors explicit geometry and waypoints", () => {
const dir = mkdtempSync(join(tmpdir(), "drawio-build-v2-test-"));
const input = join(dir, "platform.yaml");
const output = join(dir, "platform.drawio");
try {
writeFileSync(input, `version: 2\ntitle: Platform\ntheme: zinc\nprovenance: { source: test }\nproperties: { audience: engineering }\nextensions: { owner: drawio-main }\npages:\n - id: system\n title: System\n properties: { view: logical }\n layout: { type: manual }\n nodes:\n - id: client\n label: Client\n kind: actor\n properties: { tier: edge }\n provenance: { source: inventory }\n extensions: { critical: true }\n geometry: { x: 40, y: 80, width: 120, height: 60 }\n - id: api\n label: API\n geometry: { x: 300, y: 80, width: 120, height: 60 }\n edges:\n - id: call\n source: client\n target: api\n kind: request\n properties: { protocol: HTTPS }\n waypoints: [{ x: 230, y: 110 }]\n - id: deployment\n title: Deployment\n nodes:\n - { id: server, label: Server }\n edges: []\n`, "utf8");
const result = run(input, 0, output);
assert.deepEqual(result.summary, { nodes: 3, edges: 1, pages: 2 });
const xml = readFileSync(output, "utf8");
assert.match(xml, /data-theme="zinc"/);
assert.match(xml, /data-provenance="\{"source":"test"\}"/);
assert.match(xml, /data-properties="\{"audience":"engineering"\}"/);
assert.match(xml, /data-kind="actor"/);
assert.match(xml, /data-properties="\{"tier":"edge"\}"/);
assert.match(xml, /data-kind="request"/);
assert.match(xml, /data-properties="\{"protocol":"HTTPS"\}"/);
const pages = parseAllPages(output);
assert.deepEqual(pages.map((page) => page.pageName), ["System", "Deployment"]);
assert.equal(pages[0].shapes.get("api")?.x, 300);
assert.deepEqual(pages[0].edges[0].waypoints, [{ x: 230, y: 110 }]);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});