commits
tags
import { describe, expect, it } from "vitest";
import type { Flow } from "./api";
import { autoLayout, normalizeFlow, pickStart, remapIds } from "./graph";
const GRAPH: Partial<Flow> = {
nodes: [
{ id: "a", type: "goto", url: "https://x.se" },
{ id: "b", type: "if", branches: [{ condition: { kind: "var_equals", var: "x", text: "1" } }] },
{ id: "c", type: "screenshot", name: "hit" },
{ id: "d", type: "screenshot", name: "miss" },
],
edges: [
{ id: "e1", source: "a", target: "b", sourceHandle: "out", targetHandle: "in" },
{ id: "e2", source: "b", target: "c", sourceHandle: "case0", targetHandle: "in" },
{ id: "e3", source: "b", target: "d", sourceHandle: "else", targetHandle: "in" },
],
};
describe("normalizeFlow", () => {
it("fills every key and keeps nodes/edges", () => {
const f = normalizeFlow(GRAPH);
expect(f.format).toBe("graph");
expect(f.nodes.map((n) => n.id)).toEqual(["a", "b", "c", "d"]);
expect(f.edges).toHaveLength(3);
expect(f.inputs).toEqual([]);
expect(f.input_sets).toEqual({});
});
it("drops edges that reference missing nodes", () => {
const f = normalizeFlow({ nodes: [{ id: "a", type: "wait" }], edges: [{ id: "e", source: "a", target: "ghost" }] });
expect(f.edges).toEqual([]);
});
it("assigns ids to id-less nodes", () => {
const f = normalizeFlow({ nodes: [{ type: "wait" }, { type: "wait" }] });
expect(f.nodes.every((n) => !!n.id)).toBe(true);
expect(f.nodes[0].id).not.toBe(f.nodes[1].id);
});
it("normalizes subflows (id from name, node/edge ids)", () => {
const f = normalizeFlow({
nodes: [],
subflows: [
{ name: "Hämta Bilagor", nodes: [{ type: "wait" }], edges: [] } as never,
{ id: "keep", name: "K", nodes: [], edges: [] },
],
});
expect(f.subflows).toHaveLength(2);
expect(f.subflows![0].id).toBe("h-mta-bilagor");
expect(f.subflows![0].nodes[0].id).toBeTruthy();
expect(f.subflows![1].id).toBe("keep");
});
it("always returns a subflows array", () => {
expect(normalizeFlow({ nodes: [] }).subflows).toEqual([]);
});
});
describe("pickStart", () => {
it("is the node without an incoming edge", () => {
const f = normalizeFlow(GRAPH);
expect(pickStart(f.nodes, f.edges)).toBe("a");
});
it("falls back to the topmost node when every node has an incoming edge", () => {
const nodes = [
{ id: "a", type: "wait", position: { x: 0, y: 10 } },
{ id: "b", type: "wait", position: { x: 0, y: 0 } },
];
const edges = [
{ id: "e1", source: "a", target: "b" },
{ id: "e2", source: "b", target: "a" },
];
expect(pickStart(nodes, edges)).toBe("b");
});
});
describe("autoLayout", () => {
it("stacks depth downward (y) and spreads siblings sideways (x)", () => {
const f = normalizeFlow(GRAPH);
const pos = autoLayout(f.nodes, f.edges);
expect(pos.a).toEqual({ x: 0, y: 0 });
expect(pos.b.y).toBeGreaterThan(pos.a.y); // deeper -> further down
expect(pos.c.y).toBe(pos.d.y); // same depth -> same row
expect(pos.c.x).not.toBe(pos.d.x); // siblings side by side
});
});
describe("remapIds", () => {
it("rewrites node ids and keeps edges consistent", () => {
const { nodes, edges } = remapIds(
[{ id: "a", type: "wait" }, { id: "b", type: "wait" }],
[{ id: "e", source: "a", target: "b" }],
);
expect(nodes[0].id).not.toBe("a");
expect(edges[0].source).toBe(nodes[0].id);
expect(edges[0].target).toBe(nodes[1].id);
});
});