181 lines
4.4 KiB
TypeScript
181 lines
4.4 KiB
TypeScript
import { describe, it, beforeEach, expect } from "vitest";
|
|
import { loadFixture, loadSubgraphFixture, cloneGraph } from "../helpers";
|
|
import { layoutGraph } from "../../src/layout/index";
|
|
import {
|
|
assertNoOverlaps,
|
|
assertNodesInsideGroups,
|
|
assertFiniteCoordinates,
|
|
assertTopologicalOrder,
|
|
assertIdempotent,
|
|
assertGroupMembershipPreserved,
|
|
} from "../invariants";
|
|
import type { LGraph } from "../../src/layout/types";
|
|
|
|
/**
|
|
* Regression tests for the layout algorithm
|
|
* Each fixture is run through all invariants to catch regressions
|
|
*/
|
|
describe("layoutGraph regression tests", () => {
|
|
// Fixtures that currently pass all tests
|
|
const passingFixtures = [
|
|
"simple-dag.json", // 7 nodes, no groups, basic txt2img DAG
|
|
"nested-groups.json", // Nested groups with node inside inner group
|
|
"nested-wrapper.json", // Group Wrapper containing nested groups
|
|
"complex-parallel.json", // 26 nodes, parallel KSampler High/Low, 5 groups
|
|
];
|
|
|
|
for (const fixture of passingFixtures) {
|
|
describe(fixture, () => {
|
|
let graph: LGraph;
|
|
let originalGraph: LGraph;
|
|
|
|
beforeEach(() => {
|
|
graph = loadFixture(fixture);
|
|
originalGraph = cloneGraph(graph); // Capture state before layout
|
|
layoutGraph(graph);
|
|
});
|
|
|
|
it("has no overlapping entities", () => {
|
|
assertNoOverlaps(graph);
|
|
});
|
|
|
|
it("keeps nodes inside their groups", () => {
|
|
assertNodesInsideGroups(graph);
|
|
});
|
|
|
|
it("has finite coordinates", () => {
|
|
assertFiniteCoordinates(graph);
|
|
});
|
|
|
|
it("preserves topological order", () => {
|
|
assertTopologicalOrder(graph);
|
|
});
|
|
|
|
it("is idempotent (stable after multiple runs)", () => {
|
|
assertIdempotent(graph);
|
|
});
|
|
|
|
it("preserves group membership", () => {
|
|
assertGroupMembershipPreserved(originalGraph, graph);
|
|
});
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Subgraph fixture tests (with I/O nodes)
|
|
* Uses loadSubgraphFixture to extract subgraph from workflow JSON
|
|
*/
|
|
describe("subgraph layout tests", () => {
|
|
const subgraphFixtures = [
|
|
"subgraph-io.json", // Subgraph with I/O nodes, 12 nodes, 4 groups
|
|
];
|
|
|
|
for (const fixture of subgraphFixtures) {
|
|
describe(fixture, () => {
|
|
let graph: LGraph;
|
|
let originalGraph: LGraph;
|
|
|
|
beforeEach(() => {
|
|
graph = loadSubgraphFixture(fixture);
|
|
originalGraph = cloneGraph(graph);
|
|
layoutGraph(graph);
|
|
});
|
|
|
|
it("has no overlapping entities (including I/O nodes)", () => {
|
|
assertNoOverlaps(graph);
|
|
});
|
|
|
|
it("keeps nodes inside their groups", () => {
|
|
assertNodesInsideGroups(graph);
|
|
});
|
|
|
|
it("has finite coordinates", () => {
|
|
assertFiniteCoordinates(graph);
|
|
});
|
|
|
|
it("preserves topological order", () => {
|
|
assertTopologicalOrder(graph);
|
|
});
|
|
|
|
it("is idempotent (stable after multiple runs)", () => {
|
|
assertIdempotent(graph);
|
|
});
|
|
|
|
it("preserves group membership", () => {
|
|
assertGroupMembershipPreserved(originalGraph, graph);
|
|
});
|
|
});
|
|
}
|
|
});
|
|
|
|
describe("layout algorithm properties", () => {
|
|
it("handles empty graph", () => {
|
|
const graph: LGraph = {
|
|
_nodes: [],
|
|
_groups: [],
|
|
links: new Map(),
|
|
};
|
|
|
|
const result = layoutGraph(graph);
|
|
|
|
expect(result.nodeCount).toBe(0);
|
|
expect(result.layerCount).toBe(0);
|
|
});
|
|
|
|
it("handles single node", () => {
|
|
const graph: LGraph = {
|
|
_nodes: [
|
|
{
|
|
id: 1,
|
|
type: "Note",
|
|
title: "Test",
|
|
pos: [0, 0],
|
|
size: [200, 100],
|
|
inputs: [],
|
|
outputs: [],
|
|
},
|
|
],
|
|
_groups: [],
|
|
links: new Map(),
|
|
};
|
|
|
|
const result = layoutGraph(graph);
|
|
|
|
expect(result.nodeCount).toBe(1);
|
|
assertFiniteCoordinates(graph);
|
|
});
|
|
|
|
it("handles disconnected nodes", () => {
|
|
const graph: LGraph = {
|
|
_nodes: [
|
|
{
|
|
id: 1,
|
|
type: "Note",
|
|
title: "Note 1",
|
|
pos: [0, 0],
|
|
size: [200, 100],
|
|
inputs: [],
|
|
outputs: [],
|
|
},
|
|
{
|
|
id: 2,
|
|
type: "Note",
|
|
title: "Note 2",
|
|
pos: [500, 500],
|
|
size: [200, 100],
|
|
inputs: [],
|
|
outputs: [],
|
|
},
|
|
],
|
|
_groups: [],
|
|
links: new Map(),
|
|
};
|
|
|
|
layoutGraph(graph);
|
|
|
|
assertNoOverlaps(graph);
|
|
assertFiniteCoordinates(graph);
|
|
});
|
|
});
|