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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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 = { 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); }); }); });