189 lines
6.6 KiB
TypeScript
189 lines
6.6 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { createGridAlgorithm } from "../../src/layout/algorithms/grid";
|
|
import type { LayoutNode, LayoutInput } from "../../src/layout/types";
|
|
|
|
function node(id: string, width = 100, height = 60): LayoutNode {
|
|
return { id, width, height };
|
|
}
|
|
|
|
function input(nodes: LayoutNode[]): LayoutInput {
|
|
return { nodes, edges: [] };
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Grid layout: row-based
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("grid layout (row-based)", () => {
|
|
it("[2ROW] with 4 nodes produces 2x2 grid", () => {
|
|
const algo = createGridAlgorithm({ rows: 2, gap: 40 });
|
|
const nodes = [node("A"), node("B"), node("C"), node("D")];
|
|
const result = algo.layout(input(nodes));
|
|
|
|
// cols = ceil(4/2) = 2
|
|
// Row 0: A(0,0), B(0,1)
|
|
// Row 1: C(1,0), D(1,1)
|
|
const posA = result.positions.get("A")!;
|
|
const posB = result.positions.get("B")!;
|
|
const posC = result.positions.get("C")!;
|
|
const posD = result.positions.get("D")!;
|
|
|
|
// cellWidth = 100+40 = 140, cellHeight = 60+40 = 100
|
|
expect(posA).toEqual({ x: 0, y: 0 });
|
|
expect(posB).toEqual({ x: 140, y: 0 });
|
|
expect(posC).toEqual({ x: 0, y: 100 });
|
|
expect(posD).toEqual({ x: 140, y: 100 });
|
|
});
|
|
|
|
it("[2ROW] with 1 node places at origin", () => {
|
|
const algo = createGridAlgorithm({ rows: 2, gap: 40 });
|
|
const result = algo.layout(input([node("A")]));
|
|
|
|
expect(result.positions.get("A")).toEqual({ x: 0, y: 0 });
|
|
});
|
|
|
|
it("[3ROW] with 7 nodes distributes correctly", () => {
|
|
const algo = createGridAlgorithm({ rows: 3, gap: 20 });
|
|
const nodes = Array.from({ length: 7 }, (_, i) => node(`N${i}`, 80, 50));
|
|
const result = algo.layout(input(nodes));
|
|
|
|
// cols = ceil(7/3) = 3
|
|
// Row 0: N0, N1, N2
|
|
// Row 1: N3, N4, N5
|
|
// Row 2: N6
|
|
expect(result.positions.size).toBe(7);
|
|
|
|
const posN0 = result.positions.get("N0")!;
|
|
const posN3 = result.positions.get("N3")!;
|
|
const posN6 = result.positions.get("N6")!;
|
|
|
|
// cellWidth = 80+20 = 100, cellHeight = 50+20 = 70
|
|
expect(posN0).toEqual({ x: 0, y: 0 });
|
|
expect(posN3).toEqual({ x: 0, y: 70 });
|
|
expect(posN6).toEqual({ x: 0, y: 140 });
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Grid layout: column-based
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("grid layout (column-based)", () => {
|
|
it("[3COL] with 9 nodes produces 3x3 grid", () => {
|
|
const algo = createGridAlgorithm({ columns: 3, gap: 40 });
|
|
const nodes = Array.from({ length: 9 }, (_, i) => node(`N${i}`));
|
|
const result = algo.layout(input(nodes));
|
|
|
|
// rows = ceil(9/3) = 3
|
|
// Row 0: N0, N1, N2
|
|
// Row 1: N3, N4, N5
|
|
// Row 2: N6, N7, N8
|
|
expect(result.positions.size).toBe(9);
|
|
|
|
const posN0 = result.positions.get("N0")!;
|
|
const posN4 = result.positions.get("N4")!;
|
|
const posN8 = result.positions.get("N8")!;
|
|
|
|
expect(posN0).toEqual({ x: 0, y: 0 });
|
|
expect(posN4).toEqual({ x: 140, y: 100 }); // (row=1, col=1)
|
|
expect(posN8).toEqual({ x: 280, y: 200 }); // (row=2, col=2)
|
|
});
|
|
|
|
it("[2COL] with 5 nodes: 3 rows x 2 columns", () => {
|
|
const algo = createGridAlgorithm({ columns: 2, gap: 40 });
|
|
const nodes = Array.from({ length: 5 }, (_, i) => node(`N${i}`));
|
|
const result = algo.layout(input(nodes));
|
|
|
|
// rows = ceil(5/2) = 3
|
|
// Row 0: N0, N1
|
|
// Row 1: N2, N3
|
|
// Row 2: N4
|
|
expect(result.positions.size).toBe(5);
|
|
|
|
const posN4 = result.positions.get("N4")!;
|
|
expect(posN4).toEqual({ x: 0, y: 200 }); // (row=2, col=0)
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Grid layout: mixed node sizes
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("grid layout with different node sizes", () => {
|
|
it("uses max dimensions for uniform cell sizing", () => {
|
|
const algo = createGridAlgorithm({ columns: 2, gap: 20 });
|
|
const nodes = [
|
|
node("small", 60, 30),
|
|
node("wide", 150, 30),
|
|
node("tall", 60, 100),
|
|
node("big", 150, 100),
|
|
];
|
|
const result = algo.layout(input(nodes));
|
|
|
|
// maxWidth = 150, maxHeight = 100
|
|
// cellWidth = 150+20 = 170, cellHeight = 100+20 = 120
|
|
|
|
const posSmall = result.positions.get("small")!;
|
|
const posWide = result.positions.get("wide")!;
|
|
const posTall = result.positions.get("tall")!;
|
|
const posBig = result.positions.get("big")!;
|
|
|
|
// "small" at (0,0): centered in cell → x = (150-60)/2 = 45, y = (100-30)/2 = 35
|
|
expect(posSmall).toEqual({ x: 45, y: 35 });
|
|
|
|
// "wide" at (0,1): x = 170 + (150-150)/2 = 170, y = (100-30)/2 = 35
|
|
expect(posWide).toEqual({ x: 170, y: 35 });
|
|
|
|
// "tall" at (1,0): x = (150-60)/2 = 45, y = 120 + (100-100)/2 = 120
|
|
expect(posTall).toEqual({ x: 45, y: 120 });
|
|
|
|
// "big" at (1,1): x = 170 + 0 = 170, y = 120 + 0 = 120
|
|
expect(posBig).toEqual({ x: 170, y: 120 });
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Grid layout: determinism
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("grid layout determinism", () => {
|
|
it("produces identical output for identical input", () => {
|
|
const algo = createGridAlgorithm({ rows: 2, gap: 40 });
|
|
const nodes = [node("A"), node("B"), node("C"), node("D"), node("E")];
|
|
|
|
const result1 = algo.layout(input(nodes));
|
|
const result2 = algo.layout(input(nodes));
|
|
|
|
expect(result1.positions.size).toBe(result2.positions.size);
|
|
for (const [id, pos1] of result1.positions) {
|
|
const pos2 = result2.positions.get(id)!;
|
|
expect(pos1.x).toBe(pos2.x);
|
|
expect(pos1.y).toBe(pos2.y);
|
|
}
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Grid layout: default (no rows/columns specified)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe("grid layout default (sqrt columns)", () => {
|
|
it("uses sqrt(n) columns when neither rows nor columns given", () => {
|
|
const algo = createGridAlgorithm({ gap: 40 });
|
|
const nodes = Array.from({ length: 9 }, (_, i) => node(`N${i}`));
|
|
const result = algo.layout(input(nodes));
|
|
|
|
// sqrt(9) = 3 cols, 3 rows
|
|
expect(result.positions.size).toBe(9);
|
|
|
|
// N0 at (0,0), N3 at (1,0), N6 at (2,0)
|
|
const posN0 = result.positions.get("N0")!;
|
|
const posN3 = result.positions.get("N3")!;
|
|
const posN6 = result.positions.get("N6")!;
|
|
|
|
expect(posN0.y).toBe(0);
|
|
expect(posN3.y).toBe(100); // row 1
|
|
expect(posN6.y).toBe(200); // row 2
|
|
});
|
|
});
|