ai-chatbot/tests/routes/flow-board-special-layout.test.ts

168 lines
6.2 KiB
TypeScript

import { expect, test } from "@playwright/test";
import {
arrangeSpecialMind,
specialNodeRect,
specialSubtreeBounds,
type SpecialLayoutNode,
} from "@/lib/flow-board/mind-special-layout";
const node = (
x: number,
y: number,
width = 100,
height = 40,
children: SpecialLayoutNode[] = []
): SpecialLayoutNode => ({
x,
y,
width,
height,
hGap: 0,
vGap: 0,
children,
origin: {},
});
const tree = () =>
node(0, 0, 120, 60, [
node(200, -100, 100, 40, [
node(400, -150, 180, 40),
node(400, -50, 110, 40),
]),
node(200, 0, 130, 40),
node(200, 100, 200, 40),
node(200, 200, 100, 40),
]);
test("fishbone places whole subtrees above/below a stable spine without overlap", () => {
const root = tree();
arrangeSpecialMind(root, "fishbone");
expect(specialNodeRect(root)).toEqual({ x: 0, y: 0, width: 120, height: 60 });
const a = specialSubtreeBounds(root.children[0]),
b = specialSubtreeBounds(root.children[1]),
c = specialSubtreeBounds(root.children[2]);
expect(a.y + a.height).toBeLessThan(30);
expect(b.y).toBeGreaterThan(30);
expect(c.x).toBeGreaterThan(a.x + a.width);
for (const branch of root.children)
expect(specialNodeRect(branch).x).toBeGreaterThan(120);
});
test("timeline milestones alternate on distinct increasing positions", () => {
const root = tree();
arrangeSpecialMind(root, "timeline");
const rects = root.children.map(specialNodeRect);
for (let i = 1; i < rects.length; i++)
expect(rects[i].x).toBeGreaterThan(rects[i - 1].x);
expect(rects[0].y).toBeLessThan(rects[1].y);
expect(rects[2].y).toBeLessThan(rects[3].y);
});
test("long titles and descendants reserve column space and brace preserves native tree layout", () => {
const root = tree();
root.children[0].children[0].width = 900;
arrangeSpecialMind(root, "timeline");
const first = specialSubtreeBounds(root.children[0]);
expect(specialNodeRect(root.children[2]).x).toBeGreaterThan(
first.x + first.width
);
const brace = tree(),
before = JSON.stringify(brace);
arrangeSpecialMind(brace, "brace");
expect(JSON.stringify(brace)).toBe(before);
});
test("root summaries remain outside the packed branch columns", () => {
const root = tree();
const summary = node(300, 10, 150, 40);
summary.origin = { start: 0, end: 1 };
root.children.push(summary);
arrangeSpecialMind(root, "fishbone");
const right = Math.max(
...root.children.slice(0, 4).map((child) => {
const rect = specialSubtreeBounds(child);
return rect.x + rect.width;
})
);
expect(specialNodeRect(summary).x).toBeGreaterThan(right);
});
test("vertical timeline alternates whole subtrees, keeps row order and mirrors descendants", () => {
const root = tree();
root.children[1].children.push(node(400, 0, 700, 120));
arrangeSpecialMind(root, "verticalTimeline");
const bounds = root.children.map(specialSubtreeBounds);
expect(bounds[0].x).toBeGreaterThan(60);
expect(bounds[1].x + bounds[1].width).toBeLessThan(60);
expect(specialNodeRect(root.children[1].children[0]).x).toBeLessThan(
specialNodeRect(root.children[1]).x
);
for (let i = 1; i < bounds.length; i++)
expect(bounds[i].y).toBeGreaterThan(bounds[i - 1].y + bounds[i - 1].height);
expect(specialNodeRect(root)).toEqual({ x: 0, y: 0, width: 120, height: 60 });
});
test("tree table creates aligned columns and lets parents span their descendant rows", () => {
const root = tree();
arrangeSpecialMind(root, "treeTable");
const [first, second] = root.children;
const [firstChild, secondChild] = first.children;
expect(root.tableCell).toMatchObject({ depth: 0, x: 0, y: 0 });
expect(root.tableCell!.width).toBeGreaterThan(root.tableCell!.height);
expect(first.tableCell).toMatchObject({ depth: 1, branch: 0 });
expect(first.tableCell!.height).toBe(
firstChild.tableCell!.height + secondChild.tableCell!.height
);
expect(firstChild.tableCell!.x).toBe(secondChild.tableCell!.x);
expect(firstChild.tableCell!.width).toBe(secondChild.tableCell!.width);
expect(second.tableCell!.y).toBeGreaterThanOrEqual(
secondChild.tableCell!.y + secondChild.tableCell!.height
);
expect(specialNodeRect(first).x).toBeGreaterThanOrEqual(first.tableCell!.x);
expect(specialNodeRect(first).x + specialNodeRect(first).width).toBeLessThanOrEqual(
first.tableCell!.x + first.tableCell!.width
);
});
test("side-title tree table spans the root beside every leaf row", () => {
const root = tree();
arrangeSpecialMind(root, "treeTableSide");
const [first, second] = root.children;
const leaves = root.children.flatMap((branch) =>
branch.children.length ? branch.children : [branch]
);
expect(root.tableCell).toMatchObject({ depth: 0, x: 0, y: 0 });
expect(root.tableCell!.height).toBe(78 * leaves.length);
expect(first.tableCell!.x).toBe(root.tableCell!.x + root.tableCell!.width);
expect(first.tableCell!.y).toBe(root.tableCell!.y);
expect(second.tableCell!.y).toBeGreaterThan(first.tableCell!.y);
expect(first.tableCell!.height).toBe(156);
});
test("spreadsheet uses XMind columns and stacks entries that share a labelled cell", () => {
const mondayA = node(400, 0, 100, 40);
mondayA.origin.xmind = { labels: ["Monday"] };
const mondayB = node(400, 50, 100, 40);
mondayB.origin.xmind = { labels: ["Monday"] };
const friday = node(400, 100, 100, 40);
friday.origin.xmind = { labels: ["Friday"] };
const happy = node(200, 0, 120, 40, [mondayA, mondayB, friday]);
const calmTuesday = node(400, 0, 100, 40);
calmTuesday.origin.xmind = { labels: ["Tuesday"] };
const calm = node(200, 100, 120, 40, [calmTuesday]);
const root = node(0, 0, 150, 60, [happy, calm]);
root.origin.spreadsheetColumns = ["Monday", "Tuesday", "Friday"];
arrangeSpecialMind(root, "spreadsheet");
expect(root.tableGrid?.columns).toEqual(["Monday", "Tuesday", "Friday"]);
expect(root.tableCell!.width).toBe(root.tableGrid!.width);
expect(happy.tableCell!.x).toBe(root.tableGrid!.x);
expect(mondayA.tableCell!.x).toBe(mondayB.tableCell!.x);
expect(mondayB.tableCell!.y).toBeGreaterThan(mondayA.tableCell!.y);
expect(friday.tableCell!.x).toBeGreaterThan(mondayA.tableCell!.x);
expect(calmTuesday.tableCell!.x).toBeGreaterThan(mondayA.tableCell!.x);
expect(calm.tableCell!.y).toBeGreaterThan(
happy.tableCell!.y + happy.tableCell!.height - 1
);
});