feat: add deterministic Diagram IR v2 authoring
This commit is contained in:
@@ -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 });
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user