// Where the flow editor's canvas puts each node. Run by `npm test`. import assert from "node:assert/strict"; import { test } from "node:test"; import { layoutFlow, NODE_HEIGHT, NODE_WIDTH, } from "../src/pages/notifications/FlowLayout.ts"; const match = (id) => ({ id, kind: "match", when: [] }); const source = { id: "s", kind: "source", events: ["issues"] }; const flow = (nodes, edges) => ({ id: "f", name: "f", enabled: true, nodes, edges: edges.map(([from, to]) => ({ from, to })), }); const at = (layout, id) => layout.nodes.find((n) => n.id === id); test("a lone source sits top left", () => { const l = layoutFlow(flow([source], [])); assert.equal(l.nodes.length, 1); assert.equal(at(l, "s").depth, 0); assert.equal(l.edges.length, 0); assert.ok(l.width > NODE_WIDTH && l.height > NODE_HEIGHT); }); test("leaves take rows in edge order and a parent sits between its children", () => { const l = layoutFlow( flow( [source, match("a"), match("b"), match("c")], [ ["s", "a"], ["s", "b"], ["a", "c"], ], ), ); // c (under a) is the first leaf, b the second; a follows c, s the middle. assert.ok(at(l, "c").y < at(l, "b").y); assert.equal(at(l, "a").y, at(l, "c").y); assert.equal(at(l, "s").y, (at(l, "a").y + at(l, "b").y) / 2); assert.equal(at(l, "c").depth, 2); assert.ok(at(l, "c").x > at(l, "a").x && at(l, "a").x > at(l, "s").x); }); test("no two nodes overlap", () => { const nodes = [source]; const edges = []; for (let i = 0; i < 6; i += 1) { nodes.push(match(`m${i}`), match(`n${i}`)); edges.push(["s", `m${i}`], [`m${i}`, `n${i}`]); } const l = layoutFlow(flow(nodes, edges)); for (const a of l.nodes) { for (const b of l.nodes) { if (a === b) continue; const apart = a.x + NODE_WIDTH <= b.x || b.x + NODE_WIDTH <= a.x || a.y + NODE_HEIGHT <= b.y || b.y + NODE_HEIGHT <= a.y; assert.ok(apart, `${a.id} overlaps ${b.id}`); } } }); test("an edge runs from the parent's right side to the child's left", () => { const l = layoutFlow(flow([source, match("a")], [["s", "a"]])); const [e] = l.edges; assert.equal(e.x1, at(l, "s").x + NODE_WIDTH); assert.equal(e.x2, at(l, "a").x); assert.equal(e.y1, at(l, "s").y + NODE_HEIGHT / 2); assert.equal(e.y2, at(l, "a").y + NODE_HEIGHT / 2); }); test("a node nothing reaches yet still gets a place, below the tree", () => { const l = layoutFlow(flow([source, match("a"), match("lost")], [["s", "a"]])); assert.equal(l.nodes.length, 3); assert.ok(at(l, "lost").y > at(l, "a").y); assert.equal(l.edges.length, 1); }); test("edges to unknown nodes are left out, and a cycle terminates", () => { const l = layoutFlow( flow( [source, match("a"), match("b")], [ ["s", "a"], ["a", "b"], ["b", "a"], ["s", "ghost"], ], ), ); assert.equal(l.nodes.length, 3); assert.ok(l.edges.every((e) => e.to !== "ghost")); }); test("a node with two parents is placed once", () => { const l = layoutFlow( flow( [source, match("a"), match("b"), match("x")], [ ["s", "a"], ["s", "b"], ["a", "x"], ["b", "x"], ], ), ); assert.equal(l.nodes.length, 4); const spots = new Set(l.nodes.map((n) => `${n.x},${n.y}`)); assert.equal(spots.size, 4, "no two nodes share a spot"); }); test("an empty flow has an empty layout", () => { const l = layoutFlow(flow([], [])); assert.deepEqual(l.nodes, []); assert.ok(l.width >= 0 && l.height >= 0); });