mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-11 07:56:50 +02:00
460 lines
13 KiB
TypeScript
460 lines
13 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import type { MosaicNode } from "react-mosaic-component";
|
|
import { analyzeLayoutStats, insertWindow } from "./layout-utils";
|
|
import type { LayoutConfig } from "@/types/app";
|
|
|
|
describe("analyzeLayoutStats", () => {
|
|
describe("empty and single window layouts", () => {
|
|
it("should return zeros for null layout", () => {
|
|
const result = analyzeLayoutStats(null);
|
|
expect(result).toEqual({
|
|
rowSplits: 0,
|
|
columnSplits: 0,
|
|
depth: 0,
|
|
windowCount: 0,
|
|
});
|
|
});
|
|
|
|
it("should return single window stats for leaf node", () => {
|
|
const result = analyzeLayoutStats("window-1");
|
|
expect(result).toEqual({
|
|
rowSplits: 0,
|
|
columnSplits: 0,
|
|
depth: 0,
|
|
windowCount: 1,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("horizontal splits", () => {
|
|
it("should count single horizontal split", () => {
|
|
const layout: MosaicNode<string> = {
|
|
direction: "row",
|
|
first: "window-1",
|
|
second: "window-2",
|
|
splitPercentage: 50,
|
|
};
|
|
const result = analyzeLayoutStats(layout);
|
|
expect(result).toEqual({
|
|
rowSplits: 1,
|
|
columnSplits: 0,
|
|
depth: 1,
|
|
windowCount: 2,
|
|
});
|
|
});
|
|
|
|
it("should count nested horizontal splits", () => {
|
|
const layout: MosaicNode<string> = {
|
|
direction: "row",
|
|
first: {
|
|
direction: "row",
|
|
first: "window-1",
|
|
second: "window-2",
|
|
splitPercentage: 50,
|
|
},
|
|
second: "window-3",
|
|
splitPercentage: 50,
|
|
};
|
|
const result = analyzeLayoutStats(layout);
|
|
expect(result).toEqual({
|
|
rowSplits: 2,
|
|
columnSplits: 0,
|
|
depth: 2,
|
|
windowCount: 3,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("vertical splits", () => {
|
|
it("should count single vertical split", () => {
|
|
const layout: MosaicNode<string> = {
|
|
direction: "column",
|
|
first: "window-1",
|
|
second: "window-2",
|
|
splitPercentage: 50,
|
|
};
|
|
const result = analyzeLayoutStats(layout);
|
|
expect(result).toEqual({
|
|
rowSplits: 0,
|
|
columnSplits: 1,
|
|
depth: 1,
|
|
windowCount: 2,
|
|
});
|
|
});
|
|
|
|
it("should count nested vertical splits", () => {
|
|
const layout: MosaicNode<string> = {
|
|
direction: "column",
|
|
first: {
|
|
direction: "column",
|
|
first: "window-1",
|
|
second: "window-2",
|
|
splitPercentage: 50,
|
|
},
|
|
second: "window-3",
|
|
splitPercentage: 50,
|
|
};
|
|
const result = analyzeLayoutStats(layout);
|
|
expect(result).toEqual({
|
|
rowSplits: 0,
|
|
columnSplits: 2,
|
|
depth: 2,
|
|
windowCount: 3,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("mixed splits", () => {
|
|
it("should count mixed horizontal and vertical splits", () => {
|
|
const layout: MosaicNode<string> = {
|
|
direction: "row",
|
|
first: {
|
|
direction: "column",
|
|
first: "window-1",
|
|
second: "window-2",
|
|
splitPercentage: 50,
|
|
},
|
|
second: "window-3",
|
|
splitPercentage: 50,
|
|
};
|
|
const result = analyzeLayoutStats(layout);
|
|
expect(result).toEqual({
|
|
rowSplits: 1,
|
|
columnSplits: 1,
|
|
depth: 2,
|
|
windowCount: 3,
|
|
});
|
|
});
|
|
|
|
it("should handle complex nested mixed splits", () => {
|
|
// Layout: row split with column split on left and row split on right
|
|
const layout: MosaicNode<string> = {
|
|
direction: "row",
|
|
first: {
|
|
direction: "column",
|
|
first: "window-1",
|
|
second: "window-2",
|
|
splitPercentage: 50,
|
|
},
|
|
second: {
|
|
direction: "row",
|
|
first: "window-3",
|
|
second: "window-4",
|
|
splitPercentage: 50,
|
|
},
|
|
splitPercentage: 50,
|
|
};
|
|
const result = analyzeLayoutStats(layout);
|
|
expect(result).toEqual({
|
|
rowSplits: 2,
|
|
columnSplits: 1,
|
|
depth: 2,
|
|
windowCount: 4,
|
|
});
|
|
});
|
|
|
|
it("should handle quad layout (2x2 grid)", () => {
|
|
// Quad layout: row split, each side has column split
|
|
const layout: MosaicNode<string> = {
|
|
direction: "row",
|
|
first: {
|
|
direction: "column",
|
|
first: "window-1",
|
|
second: "window-2",
|
|
splitPercentage: 50,
|
|
},
|
|
second: {
|
|
direction: "column",
|
|
first: "window-3",
|
|
second: "window-4",
|
|
splitPercentage: 50,
|
|
},
|
|
splitPercentage: 50,
|
|
};
|
|
const result = analyzeLayoutStats(layout);
|
|
expect(result).toEqual({
|
|
rowSplits: 1,
|
|
columnSplits: 2,
|
|
depth: 2,
|
|
windowCount: 4,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("depth calculation", () => {
|
|
it("should calculate correct depth for unbalanced tree", () => {
|
|
// Deep on one side, shallow on other
|
|
const layout: MosaicNode<string> = {
|
|
direction: "row",
|
|
first: {
|
|
direction: "row",
|
|
first: {
|
|
direction: "row",
|
|
first: "window-1",
|
|
second: "window-2",
|
|
splitPercentage: 50,
|
|
},
|
|
second: "window-3",
|
|
splitPercentage: 50,
|
|
},
|
|
second: "window-4",
|
|
splitPercentage: 50,
|
|
};
|
|
const result = analyzeLayoutStats(layout);
|
|
expect(result.depth).toBe(3);
|
|
expect(result.windowCount).toBe(4);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("insertWindow", () => {
|
|
describe("first window insertion", () => {
|
|
it("should return window ID for null layout", () => {
|
|
const config: LayoutConfig = {
|
|
insertionMode: "smart",
|
|
splitPercentage: 50,
|
|
insertionPosition: "second",
|
|
};
|
|
const result = insertWindow(null, "new-window", config);
|
|
expect(result).toBe("new-window");
|
|
});
|
|
});
|
|
|
|
describe("row mode insertion", () => {
|
|
it("should create horizontal split in row mode", () => {
|
|
const config: LayoutConfig = {
|
|
insertionMode: "row",
|
|
splitPercentage: 50,
|
|
insertionPosition: "second",
|
|
};
|
|
const result = insertWindow("window-1", "window-2", config);
|
|
expect(result).toEqual({
|
|
direction: "row",
|
|
first: "window-1",
|
|
second: "window-2",
|
|
splitPercentage: 50,
|
|
});
|
|
});
|
|
|
|
it("should respect custom split percentage in row mode", () => {
|
|
const config: LayoutConfig = {
|
|
insertionMode: "row",
|
|
splitPercentage: 70,
|
|
insertionPosition: "second",
|
|
};
|
|
const result = insertWindow("window-1", "window-2", config);
|
|
expect(result).toEqual({
|
|
direction: "row",
|
|
first: "window-1",
|
|
second: "window-2",
|
|
splitPercentage: 70,
|
|
});
|
|
});
|
|
|
|
it("should place new window on left when position is first", () => {
|
|
const config: LayoutConfig = {
|
|
insertionMode: "row",
|
|
splitPercentage: 50,
|
|
insertionPosition: "first",
|
|
};
|
|
const result = insertWindow("window-1", "window-2", config);
|
|
expect(result).toEqual({
|
|
direction: "row",
|
|
first: "window-2", // New window on left
|
|
second: "window-1",
|
|
splitPercentage: 50,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("column mode insertion", () => {
|
|
it("should create vertical split in column mode", () => {
|
|
const config: LayoutConfig = {
|
|
insertionMode: "column",
|
|
splitPercentage: 50,
|
|
insertionPosition: "second",
|
|
};
|
|
const result = insertWindow("window-1", "window-2", config);
|
|
expect(result).toEqual({
|
|
direction: "column",
|
|
first: "window-1",
|
|
second: "window-2",
|
|
splitPercentage: 50,
|
|
});
|
|
});
|
|
|
|
it("should respect custom split percentage in column mode", () => {
|
|
const config: LayoutConfig = {
|
|
insertionMode: "column",
|
|
splitPercentage: 30,
|
|
insertionPosition: "second",
|
|
};
|
|
const result = insertWindow("window-1", "window-2", config);
|
|
expect(result).toEqual({
|
|
direction: "column",
|
|
first: "window-1",
|
|
second: "window-2",
|
|
splitPercentage: 30,
|
|
});
|
|
});
|
|
|
|
it("should place new window on top when position is first", () => {
|
|
const config: LayoutConfig = {
|
|
insertionMode: "column",
|
|
splitPercentage: 50,
|
|
insertionPosition: "first",
|
|
};
|
|
const result = insertWindow("window-1", "window-2", config);
|
|
expect(result).toEqual({
|
|
direction: "column",
|
|
first: "window-2", // New window on top
|
|
second: "window-1",
|
|
splitPercentage: 50,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("smart mode insertion", () => {
|
|
it("should use smart direction for single window", () => {
|
|
const config: LayoutConfig = {
|
|
insertionMode: "smart",
|
|
splitPercentage: 50,
|
|
insertionPosition: "second",
|
|
};
|
|
const result = insertWindow("window-1", "window-2", config);
|
|
expect(result).toEqual({
|
|
direction: "row", // Smart defaults to row for first split
|
|
first: "window-1",
|
|
second: "window-2",
|
|
splitPercentage: 50,
|
|
});
|
|
});
|
|
|
|
it("should balance horizontal splits by adding vertical", () => {
|
|
// Existing layout: 2 horizontal splits
|
|
const existingLayout: MosaicNode<string> = {
|
|
direction: "row",
|
|
first: {
|
|
direction: "row",
|
|
first: "window-1",
|
|
second: "window-2",
|
|
splitPercentage: 50,
|
|
},
|
|
second: "window-3",
|
|
splitPercentage: 50,
|
|
};
|
|
const config: LayoutConfig = {
|
|
insertionMode: "smart",
|
|
splitPercentage: 50,
|
|
insertionPosition: "second",
|
|
};
|
|
const result = insertWindow(existingLayout, "window-4", config);
|
|
|
|
// NEW IMPROVED BEHAVIOR: Splits shallowest leaf (window-3 at depth 1)
|
|
// Parent direction is row, so rotates to column for the split
|
|
expect(result).toHaveProperty("direction", "row");
|
|
|
|
// window-3 should be replaced with [column: window-3 | window-4]
|
|
const secondNode = (result as any).second;
|
|
expect(secondNode).toHaveProperty("direction", "column");
|
|
expect(secondNode).toHaveProperty("first", "window-3");
|
|
expect(secondNode).toHaveProperty("second", "window-4");
|
|
});
|
|
|
|
it("should balance vertical splits by adding horizontal", () => {
|
|
// Existing layout: 2 vertical splits
|
|
const existingLayout: MosaicNode<string> = {
|
|
direction: "column",
|
|
first: {
|
|
direction: "column",
|
|
first: "window-1",
|
|
second: "window-2",
|
|
splitPercentage: 50,
|
|
},
|
|
second: "window-3",
|
|
splitPercentage: 50,
|
|
};
|
|
const config: LayoutConfig = {
|
|
insertionMode: "smart",
|
|
splitPercentage: 50,
|
|
insertionPosition: "second",
|
|
};
|
|
const result = insertWindow(existingLayout, "window-4", config);
|
|
|
|
// NEW IMPROVED BEHAVIOR: Splits shallowest leaf (window-3 at depth 1)
|
|
// Parent direction is column, so rotates to row for the split
|
|
expect(result).toHaveProperty("direction", "column");
|
|
|
|
// window-3 should be replaced with [row: window-3 | window-4]
|
|
const secondNode = (result as any).second;
|
|
expect(secondNode).toHaveProperty("direction", "row");
|
|
expect(secondNode).toHaveProperty("first", "window-3");
|
|
expect(secondNode).toHaveProperty("second", "window-4");
|
|
});
|
|
});
|
|
|
|
describe("complex layout insertion", () => {
|
|
it("should handle insertion into complex nested layout", () => {
|
|
// Quad layout (1 row, 2 column splits)
|
|
const quadLayout: MosaicNode<string> = {
|
|
direction: "row",
|
|
first: {
|
|
direction: "column",
|
|
first: "window-1",
|
|
second: "window-2",
|
|
splitPercentage: 50,
|
|
},
|
|
second: {
|
|
direction: "column",
|
|
first: "window-3",
|
|
second: "window-4",
|
|
splitPercentage: 50,
|
|
},
|
|
splitPercentage: 50,
|
|
};
|
|
const config: LayoutConfig = {
|
|
insertionMode: "smart",
|
|
splitPercentage: 50,
|
|
insertionPosition: "second",
|
|
};
|
|
const result = insertWindow(quadLayout, "window-5", config);
|
|
|
|
// NEW IMPROVED BEHAVIOR: All 4 windows at depth 2 (equal depth)
|
|
// Algorithm picks first shallowest leaf (window-1)
|
|
// Parent is column, so rotates to row for the split
|
|
expect(result).toHaveProperty("direction", "row");
|
|
|
|
const firstNode = (result as any).first;
|
|
expect(firstNode).toHaveProperty("direction", "column");
|
|
|
|
// window-1 should be replaced with [row: window-1 | window-5]
|
|
const window1Split = firstNode.first;
|
|
expect(window1Split).toHaveProperty("direction", "row");
|
|
expect(window1Split).toHaveProperty("first", "window-1");
|
|
expect(window1Split).toHaveProperty("second", "window-5");
|
|
});
|
|
});
|
|
|
|
describe("split percentage edge cases", () => {
|
|
it("should handle minimum split percentage", () => {
|
|
const config: LayoutConfig = {
|
|
insertionMode: "row",
|
|
splitPercentage: 10,
|
|
insertionPosition: "second",
|
|
};
|
|
const result = insertWindow("window-1", "window-2", config);
|
|
expect(result).toHaveProperty("splitPercentage", 10);
|
|
});
|
|
|
|
it("should handle maximum split percentage", () => {
|
|
const config: LayoutConfig = {
|
|
insertionMode: "row",
|
|
splitPercentage: 90,
|
|
insertionPosition: "second",
|
|
};
|
|
const result = insertWindow("window-1", "window-2", config);
|
|
expect(result).toHaveProperty("splitPercentage", 90);
|
|
});
|
|
});
|
|
});
|