diff --git a/README.md b/README.md index d5b7aa3..a96f5b2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # `` -[![Build Status](https://img.shields.io/github/actions/workflow/status/texodus/regular-layout/build.yaml?event=push&style=for-the-badge)](https://github.com/texodus/regular-layout/actions/workflows/build.yaml) [![npm](https://img.shields.io/npm/v/regular-layout.svg?style=for-the-badge)](https://www.npmjs.com/package/regular-layout) +[![bundlephobia](https://img.shields.io/bundlephobia/minzip/regular-layout?style=for-the-badge)](https://bundlephobia.com/package/regular-layout) +[![Build Status](https://img.shields.io/github/actions/workflow/status/texodus/regular-layout/build.yaml?event=push&style=for-the-badge)](https://github.com/texodus/regular-layout/actions/workflows/build.yaml) A library for resizable & repositionable panel layouts, using [CSS `grid`](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Grid_layout). diff --git a/build.mjs b/build.mjs index bec5f34..387d769 100644 --- a/build.mjs +++ b/build.mjs @@ -1,3 +1,14 @@ +// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +// ░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░█░░░█▀█░█░█░█▀█░█░█░▀█▀░▀▄░░░░░░░░ +// ░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░█░░░█▀█░░█░░█░█░█░█░░█░░░▄▀░░░░░░░ +// ░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░▀▀▀░▀░▀░░▀░░▀▀▀░▀▀▀░░▀░░▀░░░░░░░░░ +// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +// ┃ * Copyright (c) 2026, the Regular Layout Authors. This file is part * ┃ +// ┃ * of the Regular Layout library, distributed under the terms of the * ┃ +// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ +// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + import * as esbuild from "esbuild"; import { execSync } from "node:child_process"; diff --git a/deploy.mjs b/deploy.mjs new file mode 100644 index 0000000..2291a3f --- /dev/null +++ b/deploy.mjs @@ -0,0 +1,73 @@ +// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +// ░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░█░░░█▀█░█░█░█▀█░█░█░▀█▀░▀▄░░░░░░░░ +// ░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░█░░░█▀█░░█░░█░█░█░█░░█░░░▄▀░░░░░░░ +// ░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░▀▀▀░▀░▀░░▀░░▀▀▀░▀▀▀░░▀░░▀░░░░░░░░░ +// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +// ┃ * Copyright (c) 2026, the Regular Layout Authors. This file is part * ┃ +// ┃ * of the Regular Layout library, distributed under the terms of the * ┃ +// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ +// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + +import { execSync } from "node:child_process"; +import { cpSync, mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +function exec(command, options = {}) { + console.log(`> ${command}`); + return execSync(command, { stdio: "inherit", ...options }); +} + +function execOutput(command) { + return execSync(command, { encoding: "utf-8" }).trim(); +} + +try { + console.log("Checking git status..."); + const gitStatus = execOutput("git status --porcelain"); + if (gitStatus) { + console.error("Error: Git staging area is not clean. Please commit or stash your changes."); + console.error(gitStatus); + process.exit(1); + } + + console.log("Building project..."); + exec("pnpm run build"); + + console.log("Preparing deployment files..."); + const currentBranch = execOutput("git rev-parse --abbrev-ref HEAD"); + const currentCommit = execOutput("git rev-parse --short HEAD"); + const tempDir = mkdtempSync(join(tmpdir(), "gh-pages-")); + cpSync("dist", join(tempDir, "dist"), { recursive: true }); + cpSync("examples", tempDir, { recursive: true }); + + console.log("Switching to gh-pages branch..."); + let ghPagesExists = false; + try { + execSync("git show-ref --verify --quiet refs/heads/gh-pages"); + ghPagesExists = true; + } catch (e) { + throw new Error("No gh-pages branch found"); + } + + if (ghPagesExists) { + exec("git checkout gh-pages"); + } else { + throw new Error("No gh-pages branch found"); + } + + console.log("Copying build artifacts..."); + cpSync(tempDir, ".", { recursive: true }); + console.log("Committing changes..."); + exec("git add -A"); + exec(`git commit -m "Deploy from ${currentBranch} @ ${currentCommit}"`); + + console.log(`Returning to ${currentBranch}...`); + exec(`git checkout ${currentBranch}`); + rmSync(tempDir, { recursive: true, force: true }); + console.log("Deployment complete! gh-pages branch updated locally."); +} catch (error) { + console.error("Deployment failed:", error.message); + process.exit(1); +} diff --git a/examples/index.css b/examples/index.css index ea89e41..0230498 100644 --- a/examples/index.css +++ b/examples/index.css @@ -35,7 +35,8 @@ regular-layout-frame::part(tab) { display: flex; flex: 1 1 150px; align-items: center; - padding: 0 12px; + font-size: 10px; + padding: 0 8px; cursor: pointer; max-width: 150px; text-overflow: ellipsis; diff --git a/package.json b/package.json index c3d1486..e1d09d6 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "clean": "rm -rf dist", "test": "playwright test", "example": "npx http-server . -p 8000", + "deploy": "node deploy.mjs", "lint": "biome lint src tests", "format": "biome format --write src tests", "check": "biome check --write src tests" diff --git a/playwright.config.ts b/playwright.config.ts index e02d7d9..288130b 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -18,6 +18,7 @@ export default defineConfig({ }, ], webServer: { + reuseExistingServer: true, command: "npx http-server . -p 8081", url: "http://127.0.0.1:8081", }, diff --git a/src/common/calculate_split.ts b/src/common/calculate_split.ts index a3dcfa3..1ed7ebc 100644 --- a/src/common/calculate_split.ts +++ b/src/common/calculate_split.ts @@ -30,6 +30,81 @@ import { * @returns A new `LayoutPath` reflecting the updated (maybe) `"split-panel"`, * which is enough to draw the overlay. */ +function handle_matching_orientation( + col: number, + row: number, + panel: Layout, + slot: string, + drop_target: LayoutPath, + is_before: boolean, +): LayoutPath { + if (drop_target.path.length === 0) { + const insert_index = is_before ? 0 : 1; + const new_panel = insert_child(panel, slot, [insert_index]); + if (is_before) { + return calculate_intersection(col, row, new_panel, false); + } else { + const new_drop_target = calculate_intersection( + col, + row, + new_panel, + false, + ); + return { + ...new_drop_target, + path: [0], + }; + } + } else { + const path_without_last = drop_target.path.slice(0, -1); + const last_index = drop_target.path[drop_target.path.length - 1]; + const insert_index = is_before ? last_index : last_index + 1; + const new_panel = insert_child(panel, slot, [ + ...path_without_last, + insert_index, + ]); + + if (is_before) { + return calculate_intersection(col, row, new_panel, false); + } else { + const new_drop_target = calculate_intersection( + col, + row, + new_panel, + false, + ); + return { + ...new_drop_target, + path: [...path_without_last, last_index], + }; + } + } +} + +function handle_cross_orientation( + col: number, + row: number, + panel: Layout, + slot: string, + drop_target: LayoutPath, + insert_index: number, + new_orientation: "horizontal" | "vertical", +): LayoutPath { + const original_path = drop_target.path; + const new_panel = insert_child( + panel, + slot, + [...original_path, insert_index], + new_orientation, + ); + const new_drop_target = calculate_intersection(col, row, new_panel, false); + return { + ...new_drop_target, + slot, + path: [...original_path, insert_index], + }; +} + export function calculate_split( col: number, row: number, @@ -37,148 +112,63 @@ export function calculate_split( slot: string, drop_target: LayoutPath, ): LayoutPath { - if ( + const is_column_edge = drop_target.column_offset < SPLIT_EDGE_TOLERANCE || - drop_target.column_offset > 1 - SPLIT_EDGE_TOLERANCE - ) { + drop_target.column_offset > 1 - SPLIT_EDGE_TOLERANCE; + const is_row_edge = + drop_target.row_offset < SPLIT_EDGE_TOLERANCE || + drop_target.row_offset > 1 - SPLIT_EDGE_TOLERANCE; + + if (is_column_edge) { + const is_before = drop_target.column_offset < SPLIT_EDGE_TOLERANCE; if (drop_target.orientation === "horizontal") { - const is_before = drop_target.column_offset < SPLIT_EDGE_TOLERANCE; - if (drop_target.path.length === 0) { - const insert_index = is_before ? 0 : 1; - const new_panel = insert_child(panel, slot, [insert_index]); - // When inserting before, point to new panel; when after, keep original - if (is_before) { - drop_target = calculate_intersection(col, row, new_panel, false); - } else { - const new_drop_target = calculate_intersection( - col, - row, - new_panel, - false, - ); - drop_target = { - ...new_drop_target, - path: [0], - }; - } - } else { - const path_without_last = drop_target.path.slice(0, -1); - const last_index = drop_target.path[drop_target.path.length - 1]; - const insert_index = is_before ? last_index : last_index + 1; - const new_panel = insert_child(panel, slot, [ - ...path_without_last, - insert_index, - ]); - // When inserting before, point to new panel; when after, keep original - if (is_before) { - drop_target = calculate_intersection(col, row, new_panel, false); - } else { - // Keep the original panel but update view_window from new layout - const new_drop_target = calculate_intersection( - col, - row, - new_panel, - false, - ); - drop_target = { - ...new_drop_target, - path: [...path_without_last, last_index], - }; - } - } + drop_target = handle_matching_orientation( + col, + row, + panel, + slot, + drop_target, + is_before, + ); } else { - const insert_index = - drop_target.column_offset < SPLIT_EDGE_TOLERANCE ? 0 : 1; - const original_path = drop_target.path; - const new_panel = insert_child( + const insert_index = is_before ? 0 : 1; + drop_target = handle_cross_orientation( + col, + row, panel, slot, - [...original_path, insert_index], + drop_target, + insert_index, "horizontal", ); - drop_target = calculate_intersection(col, row, new_panel, false); - // Override to point to the newly inserted panel - drop_target = { - ...drop_target, - slot, - path: [...original_path, insert_index], - }; } - if (drop_target) { - drop_target.is_edge = true; - } - } else if ( - drop_target.row_offset < SPLIT_EDGE_TOLERANCE || - drop_target.row_offset > 1 - SPLIT_EDGE_TOLERANCE - ) { + drop_target.is_edge = true; + } else if (is_row_edge) { + const is_before = drop_target.row_offset < SPLIT_EDGE_TOLERANCE; if (drop_target.orientation === "vertical") { - const is_before = drop_target.row_offset < SPLIT_EDGE_TOLERANCE; - if (drop_target.path.length === 0) { - const insert_index = is_before ? 0 : 1; - const new_panel = insert_child(panel, slot, [insert_index]); - // When inserting before, point to new panel; when after, keep original - if (is_before) { - drop_target = calculate_intersection(col, row, new_panel, false); - } else { - const new_drop_target = calculate_intersection( - col, - row, - new_panel, - false, - ); - drop_target = { - ...new_drop_target, - path: [0], - }; - } - } else { - const path_without_last = drop_target.path.slice(0, -1); - const last_index = drop_target.path[drop_target.path.length - 1]; - const insert_index = is_before ? last_index : last_index + 1; - const new_panel = insert_child(panel, slot, [ - ...path_without_last, - insert_index, - ]); - // When inserting before, point to new panel; when after, keep original - if (is_before) { - drop_target = calculate_intersection(col, row, new_panel, false); - } else { - // Keep the original panel but update view_window from new layout - const new_drop_target = calculate_intersection( - col, - row, - new_panel, - false, - ); - drop_target = { - ...new_drop_target, - path: [...path_without_last, last_index], - }; - } - } + drop_target = handle_matching_orientation( + col, + row, + panel, + slot, + drop_target, + is_before, + ); } else { - const insert_index = - drop_target.row_offset < SPLIT_EDGE_TOLERANCE ? 0 : 1; - const original_path = drop_target.path; - const new_panel = insert_child( + const insert_index = is_before ? 0 : 1; + drop_target = handle_cross_orientation( + col, + row, panel, slot, - [...original_path, insert_index], + drop_target, + insert_index, "vertical", ); - drop_target = calculate_intersection(col, row, new_panel, false); - // Override to point to the newly inserted panel - drop_target = { - ...drop_target, - slot, - path: [...original_path, insert_index], - }; } - if (drop_target) { - drop_target.is_edge = true; - } + drop_target.is_edge = true; } return drop_target; diff --git a/src/regular-layout-frame.ts b/src/regular-layout-frame.ts index 1432a26..ffdc4c2 100644 --- a/src/regular-layout-frame.ts +++ b/src/regular-layout-frame.ts @@ -9,7 +9,7 @@ // ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ -import type { LayoutPath } from "./common/layout_config.ts"; +import type { Layout, LayoutPath, TabLayout } from "./common/layout_config.ts"; import type { RegularLayoutEvent } from "./extensions.ts"; import type { RegularLayout } from "./regular-layout.ts"; @@ -62,11 +62,6 @@ export class RegularLayoutFrame extends HTMLElement { this._container_sheet.replaceSync(CSS); this._shadowRoot = this.attachShadow({ mode: "open" }); this._shadowRoot.adoptedStyleSheets = [this._container_sheet]; - this.drawTabs = this.drawTabs.bind(this); - this.onPointerDown = this.onPointerDown.bind(this); - this.onPointerMove = this.onPointerMove.bind(this); - this.onPointerUp = this.onPointerUp.bind(this); - this.onPointerLost = this.onPointerLost.bind(this); } connectedCallback() { @@ -88,61 +83,39 @@ export class RegularLayoutFrame extends HTMLElement { this._layout.removeEventListener("regular-layout-update", this.drawTabs); } - private drawTabs(event: RegularLayoutEvent) { + private drawTabs = (event: RegularLayoutEvent) => { const slot = this.getAttribute("slot"); + const new_panel = event.detail; if (slot) { - const result = this._layout.getPanel(slot, event.detail); + const result = this._layout.getPanel(slot, new_panel); this._header.textContent = ""; - if (!result) { - return; - } - - for (let e = 0; e < (result?.child?.length || 0); e++) { - const elem = result?.child[e]; - const div = document.createElement("div"); - this._tab_to_index_map.set(div, e); - // div.dataset.index = `${e}`; - div.textContent = elem || ""; - div.setAttribute( - "part", - e === (result?.selected || 0) ? "tab active-tab" : "tab", - ); - - const x = e; - if (e !== (result?.selected || 0)) { - div.addEventListener("pointerdown", (pointerEvent: PointerEvent) => { - result.selected = x; - this._layout.restore(event.detail); - pointerEvent.preventDefault(); - pointerEvent.stopImmediatePropagation(); - pointerEvent.stopPropagation(); - }); + if (result) { + for (let e = 0; e < result.child.length; e++) { + const tab = this.createTab(new_panel, result, e); + this._header.appendChild(tab); } - - this._header.appendChild(div); } } - } + }; private onPointerDown = (event: PointerEvent): void => { - this._drag_state = this._layout.calculateIntersect( - event.clientX, - event.clientY, - ); + const elem = event.target as HTMLDivElement; + if (elem.part.contains("tab")) { + this._drag_state = this._layout.calculateIntersect( + event.clientX, + event.clientY, + ); - if (this._drag_state) { - const elem = event.target as HTMLDivElement; - if (elem.part.contains("tab")) { + if (this._drag_state) { + // event.preventDefault(); + // event.stopImmediatePropagation(); + this._header.setPointerCapture(event.pointerId); const last_index = this._drag_state.path.length - 1; const selected = this._tab_to_index_map.get(elem); if (selected) { this._drag_state.path[last_index] = selected; } } - - this._header.setPointerCapture(event.pointerId); - // event.preventDefault(); - // event.stopImmediatePropagation(); } }; @@ -182,4 +155,26 @@ export class RegularLayoutFrame extends HTMLElement { this._drag_state = null; this._drag_moved = false; }; + + private createTab = ( + layout: Layout, + result: TabLayout, + index: number, + ): HTMLDivElement => { + const selected = result.selected || 0; + const tab = document.createElement("div"); + this._tab_to_index_map.set(tab, index); + tab.textContent = result.child[index] || ""; + if (index === selected) { + tab.setAttribute("part", "tab active-tab"); + } else { + tab.setAttribute("part", "tab"); + tab.addEventListener("pointerdown", (_: PointerEvent) => { + result.selected = index; + this._layout.restore(layout); + }); + } + + return tab; + }; } diff --git a/src/regular-layout.ts b/src/regular-layout.ts index c9fb20f..80e2268 100644 --- a/src/regular-layout.ts +++ b/src/regular-layout.ts @@ -114,12 +114,12 @@ export class RegularLayout extends HTMLElement { * "absolute" positions the panel absolutely, "interactive" updates the * actual layout in real-time. Defaults to "absolute". */ - setOverlayState( + setOverlayState = ( x: number, y: number, - { slot }: LayoutPath, + { slot }: LayoutPath, mode: "grid" | "absolute" | "interactive" = OVERLAY_DEFAULT, - ) { + ) => { let panel = this._panel; if (mode === "absolute") { panel = remove_child(panel, slot); @@ -150,7 +150,7 @@ export class RegularLayout extends HTMLElement { const event = new CustomEvent("regular-layout-update", { detail: panel }); this.dispatchEvent(event); - } + }; /** * Clears the overlay state and commits the panel placement. @@ -161,12 +161,12 @@ export class RegularLayout extends HTMLElement { * @param mode - Overlay rendering mode that was used, must match the mode * passed to `setOverlayState`. Defaults to "absolute". */ - clearOverlayState( + clearOverlayState = ( x: number, y: number, - drag_target: LayoutPath, + drag_target: LayoutPath, mode: "grid" | "absolute" | "interactive" = OVERLAY_DEFAULT, - ) { + ) => { let panel = this._panel; if (mode === "absolute") { panel = remove_child(panel, drag_target.slot); @@ -199,7 +199,7 @@ export class RegularLayout extends HTMLElement { !drop_target?.is_edge, ), ); - } + }; /** * Inserts a new panel into the layout at a specified path. @@ -207,20 +207,20 @@ export class RegularLayout extends HTMLElement { * @param name - Unique identifier for the new panel. * @param path - Index path defining where to insert. */ - insertPanel(name: string, path: number[] = []) { + insertPanel = (name: string, path: number[] = []) => { this.restore(insert_child(this._panel, name, path)); - } + }; /** * Removes a panel from the layout by name. * * @param name - Name of the panel to remove */ - removePanel(name: string) { + removePanel = (name: string) => { this.restore(remove_child(this._panel, name)); - } + }; - getPanel(name: string, layout: Layout = this._panel): TabLayout | null { + getPanel = (name: string, layout: Layout = this._panel): TabLayout | null => { if (layout.type === "child-panel") { if (layout.child.includes(name)) { return layout; @@ -236,7 +236,7 @@ export class RegularLayout extends HTMLElement { } return null; - } + }; /** * Determines which panel is at a given screen coordinate. @@ -246,11 +246,11 @@ export class RegularLayout extends HTMLElement { * @param row - Y coordinate in screen pixels. * @returns Panel information if a panel is at that position, null otherwise. */ - calculateIntersect( + calculateIntersect = ( x: number, y: number, check_dividers: boolean = false, - ): LayoutPath | null { + ): LayoutPath | null => { const [col, row, box] = this.relativeCoordinates(x, y); const panel = calculate_intersection(col, row, this._panel, check_dividers); if (panel?.type === "layout-path") { @@ -258,11 +258,11 @@ export class RegularLayout extends HTMLElement { } return null; - } + }; - clear() { + clear = () => { this.restore(EMPTY_PANEL); - } + }; /** * Restores the layout from a saved state. @@ -276,7 +276,7 @@ export class RegularLayout extends HTMLElement { * layout.restore(savedState); * ``` */ - restore(layout: Layout, _is_flattened: boolean = false) { + restore = (layout: Layout, _is_flattened: boolean = false) => { this._panel = !_is_flattened ? flatten(layout) : layout; const css = create_css_grid_layout(layout); this._stylesheet.replaceSync(css); @@ -301,7 +301,7 @@ export class RegularLayout extends HTMLElement { const event = new CustomEvent("regular-layout-update", { detail: layout }); this.dispatchEvent(event); - } + }; /** * Serializes the current layout state, which can be restored via `restore`. @@ -315,21 +315,21 @@ export class RegularLayout extends HTMLElement { * localStorage.setItem('layout', JSON.stringify(state)); * ``` */ - save(): Layout { + save = (): Layout => { return structuredClone(this._panel); - } + }; - private relativeCoordinates( + private relativeCoordinates = ( clientX: number, clientY: number, - ): [number, number, DOMRect] { + ): [number, number, DOMRect] => { const box = this.getBoundingClientRect(); const col = (clientX - box.left) / (box.right - box.left); const row = (clientY - box.top) / (box.bottom - box.top); return [col, row, box]; - } + }; - private onPointerDown(event: PointerEvent) { + private onPointerDown = (event: PointerEvent) => { if (event.target === this) { const [col, row] = this.relativeCoordinates(event.clientX, event.clientY); const hit = calculate_intersection(col, row, this._panel); @@ -340,9 +340,9 @@ export class RegularLayout extends HTMLElement { // event.stopImmediatePropagation(); } } - } + }; - private onPointerMove(event: PointerEvent) { + private onPointerMove = (event: PointerEvent) => { if (this._dragPath) { const [col, row] = this.relativeCoordinates(event.clientX, event.clientY); const old_panel = this._panel; @@ -351,9 +351,9 @@ export class RegularLayout extends HTMLElement { const panel = redistribute_panel_sizes(old_panel, path, offset); this._stylesheet.replaceSync(create_css_grid_layout(panel)); } - } + }; - private onPointerUp(event: PointerEvent) { + private onPointerUp = (event: PointerEvent) => { if (this._dragPath) { this.releasePointerCapture(event.pointerId); const [col, row] = this.relativeCoordinates(event.clientX, event.clientY); @@ -369,5 +369,5 @@ export class RegularLayout extends HTMLElement { this._dragPath = undefined; } - } + }; } diff --git a/tests/fixtures.ts b/tests/fixtures.ts deleted file mode 100644 index b7a0389..0000000 --- a/tests/fixtures.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { Layout } from "../src/common/layout_config.ts"; - -export const TEST_PANEL: Layout = { - type: "split-panel", - children: [ - { - type: "split-panel", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - ], - sizes: [0.3, 0.7], - orientation: "vertical", - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.6, 0.4], - orientation: "horizontal", -}; diff --git a/tests/helpers/fixtures.ts b/tests/helpers/fixtures.ts new file mode 100644 index 0000000..6d80728 --- /dev/null +++ b/tests/helpers/fixtures.ts @@ -0,0 +1,694 @@ +import type { Layout } from "../../src/common/layout_config.ts"; + +export const TEST_PANEL: Layout = { + type: "split-panel", + children: [ + { + type: "split-panel", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["BBB"], + }, + ], + sizes: [0.3, 0.7], + orientation: "vertical", + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + sizes: [0.6, 0.4], + orientation: "horizontal", +}; + +/** + * Common layout fixtures for testing + */ +export const LAYOUTS = { + /** Single panel with AAA */ + SINGLE_AAA: { + type: "child-panel", + child: ["AAA"], + } as Layout, + + /** Single panel with BBB */ + SINGLE_BBB: { + type: "child-panel", + child: ["BBB"], + } as Layout, + + /** Single panel with multiple tabs */ + SINGLE_TABS: { + type: "child-panel", + child: ["AAA", "BBB", "CCC"], + } as Layout, + + /** Two panels horizontal split (30/70) */ + TWO_HORIZONTAL: { + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["BBB"], + }, + ], + sizes: [0.3, 0.7], + } as Layout, + + /** Two panels vertical split (50/50) */ + TWO_VERTICAL: { + type: "split-panel", + orientation: "vertical", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["BBB"], + }, + ], + sizes: [0.5, 0.5], + } as Layout, + + /** Three panels horizontal split */ + THREE_HORIZONTAL: { + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["BBB"], + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + sizes: [0.3, 0.3, 0.4], + } as Layout, + + /** Nested layout: horizontal split with left side vertical split (60/40 outer, 30/70 inner) */ + NESTED_BASIC: TEST_PANEL, + + /** Nested layout: vertical split with nested horizontal */ + NESTED_VERTICAL_OUTER: { + type: "split-panel", + orientation: "vertical", + children: [ + { + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["BBB"], + }, + ], + sizes: [0.3, 0.7], + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + sizes: [0.6, 0.4], + } as Layout, + + /** Deeply nested layout with alternating orientations */ + DEEPLY_NESTED: { + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "split-panel", + orientation: "vertical", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["BBB"], + }, + ], + sizes: [0.3, 0.7], + }, + { + type: "split-panel", + orientation: "vertical", + children: [ + { + type: "child-panel", + child: ["CCC"], + }, + { + type: "child-panel", + child: ["DDD"], + }, + ], + sizes: [0.5, 0.5], + }, + ], + sizes: [0.6, 0.4], + } as Layout, + + /** Single panel with DDD */ + SINGLE_DDD: { + type: "child-panel", + child: ["DDD"], + } as Layout, + + /** Two panels horizontal split (50/50) */ + TWO_HORIZONTAL_EQUAL: { + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["BBB"], + }, + ], + sizes: [0.5, 0.5], + } as Layout, + + /** Three panels horizontal split with custom sizes */ + THREE_HORIZONTAL_CUSTOM: { + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["BBB"], + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + sizes: [0.2, 0.3, 0.5], + } as Layout, + + /** Three panels vertical split (CCC, DDD, EEE) */ + THREE_VERTICAL_CDE: { + type: "split-panel", + orientation: "vertical", + children: [ + { + type: "child-panel", + child: ["CCC"], + }, + { + type: "child-panel", + child: ["DDD"], + }, + { + type: "child-panel", + child: ["EEE"], + }, + ], + sizes: [0.3, 0.3, 0.4], + } as Layout, + + /** Three panels horizontal with precise custom sizes */ + THREE_HORIZONTAL_PRECISE: { + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["BBB"], + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + sizes: [0.123456789, 0.456789123, 0.419754088], + } as Layout, + + /** Three panels horizontal with different sizes (30/40/30) */ + THREE_HORIZONTAL_304030: { + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["BBB"], + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + sizes: [0.3, 0.4, 0.3], + } as Layout, + + /** Deeply nested layout: vertical outer with horizontal inner */ + DEEPLY_NESTED_ALT: { + type: "split-panel", + orientation: "vertical", + children: [ + { + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "split-panel", + orientation: "vertical", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["BBB"], + }, + ], + sizes: [0.4, 0.6], + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + sizes: [0.5, 0.5], + }, + { + type: "child-panel", + child: ["DDD"], + }, + ], + sizes: [0.7, 0.3], + } as Layout, + + /** Three panels with tabs in middle */ + THREE_HORIZONTAL_WITH_TABS: { + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["BBB", "DDD", "EEE"], + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + sizes: [0.2, 0.3, 0.5], + } as Layout, + + /** Two panels horizontal with tabs (AAA, BBB) and CCC */ + TWO_HORIZONTAL_WITH_TABS: { + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "child-panel", + child: ["AAA", "BBB"], + selected: 0, + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + sizes: [0.5, 0.5], + } as Layout, + + /** Single panel with tabs (AAA, BBB, CCC) with selected index */ + SINGLE_TABS_WITH_SELECTED: { + type: "child-panel", + child: ["AAA", "BBB", "CCC"], + selected: 0, + } as Layout, + + /** Single panel with one child (ONLY) */ + SINGLE_ONLY: { + type: "child-panel", + child: ["ONLY"], + } as Layout, + + /** Single panel with one child (SECOND) */ + SINGLE_SECOND: { + type: "child-panel", + child: ["SECOND"], + } as Layout, + + /** Single panel horizontal split with one child (AAA) */ + SINGLE_SPLIT_HORIZONTAL: { + type: "split-panel", + sizes: [1], + orientation: "horizontal", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + ], + } as Layout, + + /** Single panel vertical split with one child (AAA) */ + SINGLE_SPLIT_VERTICAL: { + type: "split-panel", + orientation: "vertical", + sizes: [1], + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + ], + } as Layout, + + /** Complex nested layout with 4 children */ + COMPLEX_FOUR_CHILDREN: { + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "split-panel", + orientation: "vertical", + children: [ + { + type: "child-panel", + child: ["EEE"], + }, + { + type: "child-panel", + child: ["BBB"], + }, + { + type: "child-panel", + child: ["DDD"], + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + sizes: [0.25, 0.25, 0.25, 0.25], + }, + ], + sizes: [0.5, 0.5], + }, + { + type: "child-panel", + child: ["FFF"], + }, + ], + sizes: [0.5, 0.5], + } as Layout, + + /** Nested aligned split panels */ + NESTED_ALIGNED: { + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "split-panel", + orientation: "vertical", + children: [ + { + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["BBB"], + }, + ], + sizes: [0.5, 0.5], + }, + { + type: "child-panel", + child: ["DDD"], + }, + ], + sizes: [0.5, 0.5], + }, + { + type: "child-panel", + child: ["FFF"], + }, + ], + sizes: [0.5, 0.5], + } as Layout, + + /** Nested aligned reversed orientation */ + NESTED_ALIGNED_REVERSED: { + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "split-panel", + orientation: "vertical", + children: [ + { + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "split-panel", + orientation: "vertical", + children: [ + { + type: "child-panel", + child: ["BBB"], + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + sizes: [0.5, 0.5], + }, + ], + sizes: [0.5, 0.5], + }, + { + type: "child-panel", + child: ["DDD"], + }, + ], + sizes: [0.5, 0.5], + }, + { + type: "child-panel", + child: ["FFF"], + }, + ], + sizes: [0.5, 0.5], + } as Layout, + + /** Nested aligned reversed with asymmetric sizes */ + NESTED_ALIGNED_ASYMMETRIC: { + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "split-panel", + orientation: "vertical", + children: [ + { + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "split-panel", + orientation: "vertical", + children: [ + { + type: "child-panel", + child: ["BBB"], + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + sizes: [0.5, 0.5], + }, + ], + sizes: [0.5, 0.5], + }, + { + type: "child-panel", + child: ["DDD"], + }, + ], + sizes: [0.75, 0.25], + }, + { + type: "child-panel", + child: ["FFF"], + }, + ], + sizes: [0.5, 0.5], + } as Layout, + + /** Three children with DDD (vertical) */ + THREE_VERTICAL_WITH_DDD: { + type: "split-panel", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["BBB"], + }, + { + type: "child-panel", + child: ["DDD"], + }, + ], + sizes: [0.3, 0.6, 0.1], + orientation: "vertical", + } as Layout, + + /** Nested horizontal with vertical inner (AAA, BBB, DDD) and CCC */ + NESTED_HORIZONTAL_WITH_VERTICAL_DDD: { + type: "split-panel", + children: [ + { + type: "split-panel", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["BBB"], + }, + { + type: "child-panel", + child: ["DDD"], + }, + ], + sizes: [0.3, 0.6, 0.1], + orientation: "vertical", + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + sizes: [0.6, 0.4], + orientation: "horizontal", + } as Layout, + + /** Three children horizontal (AAA, BBB, CCC) */ + THREE_HORIZONTAL_ABC: { + type: "split-panel", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["BBB"], + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + sizes: [0.2, 0.3, 0.5], + orientation: "horizontal", + } as Layout, + + /** Complex nested layout (horizontal outer, vertical inner with A and B, and C) */ + COMPLEX_NESTED_ABC: { + type: "split-panel", + orientation: "horizontal", + sizes: [0.5, 0.5], + children: [ + { + type: "split-panel", + orientation: "vertical", + sizes: [0.5, 0.5], + children: [ + { + type: "child-panel", + child: ["A"], + }, + { + type: "child-panel", + child: ["B"], + }, + ], + }, + { + type: "child-panel", + child: ["C"], + }, + ], + } as Layout, +}; diff --git a/tests/helpers/integration.ts b/tests/helpers/integration.ts new file mode 100644 index 0000000..02835d5 --- /dev/null +++ b/tests/helpers/integration.ts @@ -0,0 +1,158 @@ +// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +// ░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░█░░░█▀█░█░█░█▀█░█░█░▀█▀░▀▄░░░░░░░░ +// ░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░█░░░█▀█░░█░░█░█░█░█░░█░░░▄▀░░░░░░░ +// ░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░▀▀▀░▀░▀░░▀░░▀▀▀░▀▀▀░░▀░░▀░░░░░░░░░ +// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +// ┃ * Copyright (c) 2026, the Regular Layout Authors. This file is part * ┃ +// ┃ * of the Regular Layout library, distributed under the terms of the * ┃ +// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ +// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + +import type { Page } from "@playwright/test"; +import { expect } from "@playwright/test"; +import type { Layout } from "../../src/common/layout_config.ts"; +import type {} from "../../src/extensions.ts"; + +/** + * Sets up a page for layout testing by navigating and waiting for the component. + * Optionally restores an initial layout state. + */ +export async function setupLayout( + page: Page, + initialLayout?: Layout, +): Promise { + await page.goto("/examples/index.html"); + await page.waitForSelector("regular-layout"); + + if (initialLayout) { + await restoreLayout(page, initialLayout); + } +} + +/** + * Saves and returns the current layout state. + */ +export async function saveLayout(page: Page): Promise { + return await page.evaluate(() => { + const layout = document.querySelector("regular-layout"); + return layout?.save() as Layout; + }); +} + +/** + * Restores a layout to the given state. + */ +export async function restoreLayout(page: Page, state: Layout): Promise { + await page.evaluate((s) => { + const layout = document.querySelector("regular-layout"); + layout?.restore(s as Layout); + }, state); +} + +/** + * Restores a layout and verifies it matches the expected state. + */ +export async function restoreAndVerify( + page: Page, + state: Layout, +): Promise { + await restoreLayout(page, state); + const restored = await saveLayout(page); + expect(restored).toStrictEqual(state); +} + +/** + * Saves the current layout state and verifies it matches the expected state. + */ +export async function expectLayoutState( + page: Page, + expectedState: Layout, +): Promise { + const currentState = await saveLayout(page); + expect(currentState).toStrictEqual(expectedState); +} + +/** + * Returns an array of slot names from the layout's shadow DOM. + */ +export async function getSlots(page: Page): Promise { + return await page.evaluate(() => { + const layout = document.querySelector("regular-layout"); + const slotElements = layout?.shadowRoot?.querySelectorAll("slot[name]"); + return Array.from(slotElements || []).map( + (slot) => slot.getAttribute("name") || "", + ); + }); +} + +/** + * Verifies that specific slots exist or don't exist. + */ +export async function expectSlots( + page: Page, + options: { + contains?: string[]; + notContains?: string[]; + }, +): Promise { + const slots = await getSlots(page); + + if (options.contains) { + for (const slot of options.contains) { + expect(slots).toContain(slot); + } + } + + if (options.notContains) { + for (const slot of options.notContains) { + expect(slots).not.toContain(slot); + } + } +} + +/** + * Performs a mouse drag operation from one point to another. + */ +export async function dragMouse( + page: Page, + fromX: number, + fromY: number, + toX: number, + toY: number, +): Promise { + await page.mouse.move(fromX, fromY); + await page.mouse.down(); + await page.mouse.move(toX, toY); + await page.mouse.up(); +} + +/** + * Inserts a panel at the specified path. + */ +export async function insertPanel( + page: Page, + panelName: string, + path: number[], +): Promise { + await page.evaluate( + ({ name, p }) => { + const layout = document.querySelector("regular-layout"); + layout?.insertPanel(name, p); + }, + { name: panelName, p: path }, + ); +} + +/** + * Removes a panel by name or at the specified path. + */ +export async function removePanel( + page: Page, + pathOrName: number[] | string, +): Promise { + await page.evaluate((p) => { + const layout = document.querySelector("regular-layout"); + layout?.removePanel(p as string); + }, pathOrName); +} diff --git a/tests/integration/insert-panel.spec.ts b/tests/integration/insert-panel.spec.ts index cb3d5df..265f4cd 100644 --- a/tests/integration/insert-panel.spec.ts +++ b/tests/integration/insert-panel.spec.ts @@ -11,67 +11,79 @@ import { expect, test } from "@playwright/test"; import type { Layout } from "../../dist/index.js"; +import { + setupLayout, + saveLayout, + expectLayoutState, + expectSlots, + insertPanel, +} from "../helpers/integration.ts"; +import { LAYOUTS } from "../helpers/fixtures.ts"; + +test("should insert a single panel into an empty layout", async ({ page }) => { + await setupLayout(page, LAYOUTS.SINGLE_AAA); + await expectLayoutState(page, LAYOUTS.SINGLE_AAA); + await insertPanel(page, "BBB", []); + await expectLayoutState(page, { + type: "child-panel", + child: ["BBB", "AAA"], + }); -test.describe("insertPanel", () => { - test("should insert a single panel into an empty layout", async ({ - page, - }) => { - await page.goto("/examples/index.html"); - await page.waitForSelector("regular-layout"); + await expectSlots(page, { contains: ["BBB"] }); +}); - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.restore({ +test("should insert panel at specific path in split panel", async ({ + page, +}) => { + await setupLayout(page, LAYOUTS.TWO_HORIZONTAL_EQUAL); + await insertPanel(page, "CCC", [1]); + const afterInsert = await saveLayout(page); + expect(afterInsert).toStrictEqual({ + type: "split-panel", + orientation: "horizontal", + children: [ + { type: "child-panel", child: ["AAA"], - }); - }); - - const initialState = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - expect(initialState).toStrictEqual({ - type: "child-panel", - child: ["AAA"], - }); - - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.insertPanel("BBB", []); - }); - - const afterInsert = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - expect(afterInsert).toStrictEqual({ - type: "child-panel", - child: ["BBB", "AAA"], - }); - - const bbbSlot = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - const slots = layout?.shadowRoot?.querySelectorAll("slot[name]"); - return Array.from(slots || []).map((slot) => slot.getAttribute("name")); - }); + }, + { + type: "child-panel", + child: ["CCC"], + }, + { + type: "child-panel", + child: ["BBB"], + }, + ], + sizes: [0.3333333333333333, 0.3333333333333333, 0.3333333333333333], + }); +}); - expect(bbbSlot).toContain("BBB"); +test("should insert panel into nested split panel", async ({ page }) => { + await page.goto("/examples/index.html"); + await page.waitForSelector("regular-layout"); + await page.evaluate((layoutConfig) => { + const layout = document.querySelector("regular-layout"); + layout?.restore(layoutConfig as Layout); + }, LAYOUTS.NESTED_BASIC); + + await page.evaluate(() => { + const layout = document.querySelector("regular-layout"); + layout?.insertPanel("DDD", [0, 2]); }); - test("should insert panel at specific path in split panel", async ({ - page, - }) => { - await page.goto("/examples/index.html"); - await page.waitForSelector("regular-layout"); + const afterInsert = await page.evaluate(() => { + const layout = document.querySelector("regular-layout"); + return layout?.save(); + }); - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.restore({ + expect(afterInsert).toStrictEqual({ + type: "split-panel", + orientation: "horizontal", + children: [ + { type: "split-panel", - orientation: "horizontal", + orientation: "vertical", children: [ { type: "child-panel", @@ -81,215 +93,100 @@ test.describe("insertPanel", () => { type: "child-panel", child: ["BBB"], }, - ], - sizes: [0.5, 0.5], - }); - }); - - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.insertPanel("CCC", [1]); - }); - - const afterInsert = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - expect(afterInsert).toStrictEqual({ - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["CCC"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - ], - sizes: [0.3333333333333333, 0.3333333333333333, 0.3333333333333333], - }); - }); - - test("should insert panel into nested split panel", async ({ page }) => { - await page.goto("/examples/index.html"); - await page.waitForSelector("regular-layout"); - - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.restore({ - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - ], - sizes: [0.5, 0.5], - }, { type: "child-panel", - child: ["CCC"], + child: ["DDD"], }, ], - sizes: [0.6, 0.4], - }); - }); + sizes: [0.3333333333333333, 0.3333333333333333, 0.3333333333333333], + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + sizes: [0.6, 0.4], + }); +}); - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.insertPanel("DDD", [0, 2]); - }); +test("should split existing panel when inserting at deeper path", async ({ + page, +}) => { + await page.goto("/examples/index.html"); + await page.waitForSelector("regular-layout"); - const afterInsert = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); + await page.evaluate((layoutConfig) => { + const layout = document.querySelector("regular-layout"); + layout?.restore(layoutConfig as Layout); + }, LAYOUTS.TWO_HORIZONTAL_EQUAL); - expect(afterInsert).toStrictEqual({ - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - { - type: "child-panel", - child: ["DDD"], - }, - ], - sizes: [0.3333333333333333, 0.3333333333333333, 0.3333333333333333], - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.6, 0.4], - }); + await page.evaluate(() => { + const layout = document.querySelector("regular-layout"); + layout?.insertPanel("CCC", [1, 1]); }); - test("should split existing panel when inserting at deeper path", async ({ - page, - }) => { - await page.goto("/examples/index.html"); - await page.waitForSelector("regular-layout"); + const afterInsert = await page.evaluate(() => { + const layout = document.querySelector("regular-layout"); + return layout?.save(); + }); - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.restore({ + expect(afterInsert).toStrictEqual({ + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { type: "split-panel", - orientation: "horizontal", + orientation: "vertical", children: [ { type: "child-panel", - child: ["AAA"], + child: ["BBB"], }, { type: "child-panel", - child: ["BBB"], + child: ["CCC"], }, ], sizes: [0.5, 0.5], - }); - }); - - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.insertPanel("CCC", [1, 1]); - }); - - const afterInsert = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - expect(afterInsert).toStrictEqual({ - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "child-panel", - child: ["BBB"], - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.5, 0.5], - }, - ], - sizes: [0.5, 0.5], - }); + }, + ], + sizes: [0.5, 0.5], }); +}); - test("should preserve state with save/restore after insertPanel", async ({ - page, - }) => { - await page.goto("/examples/index.html"); - await page.waitForSelector("regular-layout"); - - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.restore({ - type: "child-panel", - child: ["AAA"], - }); - }); - - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.insertPanel("BBB", []); - }); - - const stateAfterInsert = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); +test("should preserve state with save/restore after insertPanel", async ({ + page, +}) => { + await page.goto("/examples/index.html"); + await page.waitForSelector("regular-layout"); + await page.evaluate((layoutConfig) => { + const layout = document.querySelector("regular-layout"); + layout?.restore(layoutConfig as Layout); + }, LAYOUTS.SINGLE_AAA); + + await page.evaluate(() => { + const layout = document.querySelector("regular-layout"); + layout?.insertPanel("BBB", []); + }); - await page.evaluate((state) => { - const layout = document.querySelector("regular-layout"); - layout?.restore(state as Layout); - }, stateAfterInsert); + const stateAfterInsert = await page.evaluate(() => { + const layout = document.querySelector("regular-layout"); + return layout?.save(); + }); - const restoredState = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); + await page.evaluate((state) => { + const layout = document.querySelector("regular-layout"); + layout?.restore(state as Layout); + }, stateAfterInsert); - expect(restoredState).toStrictEqual(stateAfterInsert); + const restoredState = await page.evaluate(() => { + const layout = document.querySelector("regular-layout"); + return layout?.save(); }); + + expect(restoredState).toStrictEqual(stateAfterInsert); }); diff --git a/tests/integration/remove-panel.spec.ts b/tests/integration/remove-panel.spec.ts index f82a267..82d2b72 100644 --- a/tests/integration/remove-panel.spec.ts +++ b/tests/integration/remove-panel.spec.ts @@ -9,98 +9,32 @@ // ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ -import { expect, test } from "@playwright/test"; -import type { Layout } from "../../dist/index.js"; +import { test } from "@playwright/test"; +import { + setupLayout, + saveLayout, + expectLayoutState, + expectSlots, + removePanel, +} from "../helpers/integration.ts"; +import { LAYOUTS } from "../helpers/fixtures.ts"; test.describe("removePanel", () => { test("should remove panel from 2-panel layout", async ({ page }) => { - await page.goto("/examples/index.html"); - await page.waitForSelector("regular-layout"); + await setupLayout(page, LAYOUTS.TWO_HORIZONTAL_EQUAL); + await removePanel(page, "BBB"); - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.restore({ - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - ], - sizes: [0.5, 0.5], - }); + await expectLayoutState(page, LAYOUTS.SINGLE_AAA); + await expectSlots(page, { + notContains: ["BBB"], + contains: ["AAA"], }); - - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.removePanel("BBB"); - }); - - const afterRemove = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - expect(afterRemove).toStrictEqual({ - type: "child-panel", - child: ["AAA"], - }); - - const slots = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - const slotElements = layout?.shadowRoot?.querySelectorAll("slot[name]"); - return Array.from(slotElements || []).map((slot) => - slot.getAttribute("name"), - ); - }); - - expect(slots).not.toContain("BBB"); - expect(slots).toContain("AAA"); }); test("should remove panel from 3-panel layout", async ({ page }) => { - await page.goto("/examples/index.html"); - await page.waitForSelector("regular-layout"); - - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.restore({ - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.2, 0.3, 0.5], - }); - }); - - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.removePanel("BBB"); - }); - - const afterRemove = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - expect(afterRemove).toStrictEqual({ + await setupLayout(page, LAYOUTS.THREE_HORIZONTAL_CUSTOM); + await removePanel(page, "BBB"); + await expectLayoutState(page, { type: "split-panel", orientation: "horizontal", children: [ @@ -118,50 +52,9 @@ test.describe("removePanel", () => { }); test("should remove panel from nested layout", async ({ page }) => { - await page.goto("/examples/index.html"); - await page.waitForSelector("regular-layout"); - - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.restore({ - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - ], - sizes: [0.3, 0.7], - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.6, 0.4], - }); - }); - - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.removePanel("AAA"); - }); - - const afterRemove = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - expect(afterRemove).toStrictEqual({ + await setupLayout(page, LAYOUTS.NESTED_BASIC); + await removePanel(page, "AAA"); + await expectLayoutState(page, { type: "split-panel", orientation: "horizontal", children: [ @@ -179,61 +72,9 @@ test.describe("removePanel", () => { }); test("should remove panel from deeply nested layout", async ({ page }) => { - await page.goto("/examples/index.html"); - await page.waitForSelector("regular-layout"); - - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.restore({ - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - ], - sizes: [0.4, 0.6], - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.5, 0.5], - }, - { - type: "child-panel", - child: ["DDD"], - }, - ], - sizes: [0.7, 0.3], - }); - }); - - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.removePanel("BBB"); - }); - - const afterRemove = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - expect(afterRemove).toStrictEqual({ + await setupLayout(page, LAYOUTS.DEEPLY_NESTED_ALT); + await removePanel(page, "BBB"); + await expectLayoutState(page, { type: "split-panel", orientation: "vertical", children: [ @@ -264,53 +105,10 @@ test.describe("removePanel", () => { test("should preserve state with save/restore after removePanel", async ({ page, }) => { - await page.goto("/examples/index.html"); - await page.waitForSelector("regular-layout"); - - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.restore({ - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.3, 0.4, 0.3], - }); - }); - - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.removePanel("BBB"); - }); - - const stateAfterRemove = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - await page.evaluate((state) => { - const layout = document.querySelector("regular-layout"); - layout?.restore(state as Layout); - }, stateAfterRemove); - - const restoredState = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - expect(restoredState).toStrictEqual(stateAfterRemove); + await setupLayout(page, LAYOUTS.THREE_HORIZONTAL_304030); + await removePanel(page, "BBB"); + const stateAfterRemove = await saveLayout(page); + await expectLayoutState(page, stateAfterRemove); }); }); @@ -318,42 +116,9 @@ test.describe("tabs", () => { test("should remove a tab from the center of a 3-panel layout", async ({ page, }) => { - await page.goto("/examples/index.html"); - await page.waitForSelector("regular-layout"); - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.restore({ - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB", "DDD", "EEE"], - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.2, 0.3, 0.5], - }); - }); - - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.removePanel("BBB"); - }); - - const afterRemove = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - expect(afterRemove).toStrictEqual({ + await setupLayout(page, LAYOUTS.THREE_HORIZONTAL_WITH_TABS); + await removePanel(page, "BBB"); + await expectLayoutState(page, { type: "split-panel", orientation: "horizontal", children: [ diff --git a/tests/integration/resize.spec.ts b/tests/integration/resize.spec.ts index f3e3ca4..324df72 100644 --- a/tests/integration/resize.spec.ts +++ b/tests/integration/resize.spec.ts @@ -10,197 +10,78 @@ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ import { expect, test } from "@playwright/test"; -import type { Layout } from "../../dist/index.js"; - -test.describe("Panel Resizing Integration", () => { - test("should resize panels by dragging dividers and preserve state with save/restore", async ({ - page, - }) => { - await page.goto("/examples/index.html"); - - await page.waitForSelector("regular-layout"); - - const initialState = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - expect(initialState).toHaveProperty("type", "split-panel"); - expect(initialState).toHaveProperty("children"); - expect(initialState).toHaveProperty("sizes"); - - const layoutBox = await page.locator("regular-layout").boundingBox(); - expect(layoutBox).not.toBeNull(); - - const dividerX = layoutBox!.x + layoutBox!.width * 0.5; - - const dividerY = layoutBox!.y + layoutBox!.height * 0.5; - - const dragDistance = -100; - await page.mouse.move(dividerX, dividerY); - await page.mouse.down(); - await page.mouse.move(dividerX + dragDistance, dividerY); - await page.mouse.up(); - - const resizedState = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - expect(resizedState).not.toEqual(initialState); - - expect(resizedState).toHaveProperty("type", "split-panel"); - expect(resizedState).toHaveProperty("sizes"); - - await page.evaluate((state) => { - const layout = document.querySelector("regular-layout"); - layout?.restore(state as Layout); - }, initialState); - - const restoredState = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - expect(restoredState).toEqual(initialState); - - await page.evaluate((state) => { - const layout = document.querySelector("regular-layout"); - layout?.restore(state as Layout); - }, resizedState); - - const finalState = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - expect(finalState).toEqual(resizedState); - }); - - test("should resize nested panels by dragging horizontal divider", async ({ - page, - }) => { - await page.goto("/examples/index.html"); - await page.waitForSelector("regular-layout"); - - const initialState = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - const layoutBox = await page.locator("regular-layout").boundingBox(); - expect(layoutBox).not.toBeNull(); - - const nestedDividerX = layoutBox!.x + layoutBox!.width * 0.5; - - const nestedDividerY = layoutBox!.y + layoutBox!.height * 0.25; - - const dragDistance = 50; - await page.mouse.move(nestedDividerX, nestedDividerY); - await page.mouse.down(); - await page.mouse.move(nestedDividerX + dragDistance, nestedDividerY); - await page.mouse.up(); - - const resizedState = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - expect(resizedState).not.toEqual(initialState); - - await page.evaluate((state) => { - const layout = document.querySelector("regular-layout"); - layout?.restore(state as Layout); - }, initialState); - - const restoredState = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - expect(restoredState).toEqual(initialState); - }); +import { + setupLayout, + saveLayout, + restoreAndVerify, + dragMouse, +} from "../helpers/integration.ts"; + +test("should resize panels by dragging dividers and preserve state with save/restore", async ({ + page, +}) => { + await setupLayout(page); + const initialState = await saveLayout(page); + expect(initialState).toHaveProperty("type", "split-panel"); + expect(initialState).toHaveProperty("children"); + expect(initialState).toHaveProperty("sizes"); + const layoutBox = await page.locator("regular-layout").boundingBox(); + expect(layoutBox).not.toBeNull(); + const dividerX = layoutBox!.x + layoutBox!.width * 0.5; + const dividerY = layoutBox!.y + layoutBox!.height * 0.5; + const dragDistance = -100; + await dragMouse(page, dividerX, dividerY, dividerX + dragDistance, dividerY); + const resizedState = await saveLayout(page); + expect(resizedState).not.toEqual(initialState); + expect(resizedState).toHaveProperty("type", "split-panel"); + expect(resizedState).toHaveProperty("sizes"); + await restoreAndVerify(page, initialState); + await restoreAndVerify(page, resizedState); +}); - test("should handle multiple resize operations and save/restore cycles", async ({ +test("should resize nested panels by dragging horizontal divider", async ({ + page, +}) => { + await setupLayout(page); + const initialState = await saveLayout(page); + const layoutBox = await page.locator("regular-layout").boundingBox(); + expect(layoutBox).not.toBeNull(); + const nestedDividerX = layoutBox!.x + layoutBox!.width * 0.5; + const nestedDividerY = layoutBox!.y + layoutBox!.height * 0.25; + const dragDistance = 50; + await dragMouse( page, - }) => { - await page.goto("/examples/index.html"); - await page.waitForSelector("regular-layout"); - - const layoutBox = await page.locator("regular-layout").boundingBox(); - expect(layoutBox).not.toBeNull(); - - const divider1X = layoutBox!.x + layoutBox!.width * 0.5; - - const divider1Y = layoutBox!.y + layoutBox!.height * 0.5; - await page.mouse.move(divider1X, divider1Y); - await page.mouse.down(); - await page.mouse.move(divider1X - 80, divider1Y); - await page.mouse.up(); - - const state1 = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - const divider2X = layoutBox!.x + layoutBox!.width * 0.25; - - const divider2Y = layoutBox!.y + layoutBox!.height * 0.25; - await page.mouse.move(divider2X, divider2Y); - await page.mouse.down(); - await page.mouse.move(divider2X, divider2Y + 60); - await page.mouse.up(); - - const state2 = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - await page.mouse.move(divider1X - 80, divider1Y); - await page.mouse.down(); - await page.mouse.move(divider1X + 40, divider1Y); - await page.mouse.up(); - - const state3 = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - expect(state1).not.toEqual(state2); - expect(state2).not.toEqual(state3); - expect(state1).not.toEqual(state3); - - await page.evaluate((state) => { - const layout = document.querySelector("regular-layout"); - layout?.restore(state as Layout); - }, state1); - - let currentState = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - expect(currentState).toEqual(state1); - - await page.evaluate((state) => { - const layout = document.querySelector("regular-layout"); - layout?.restore(state as Layout); - }, state2); - - currentState = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - expect(currentState).toEqual(state2); - - await page.evaluate((state) => { - const layout = document.querySelector("regular-layout"); - layout?.restore(state as Layout); - }, state3); + nestedDividerX, + nestedDividerY, + nestedDividerX + dragDistance, + nestedDividerY, + ); + + const resizedState = await saveLayout(page); + expect(resizedState).not.toEqual(initialState); + await restoreAndVerify(page, initialState); +}); - currentState = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - expect(currentState).toEqual(state3); - }); +test("should handle multiple resize operations and save/restore cycles", async ({ + page, +}) => { + await setupLayout(page); + const layoutBox = await page.locator("regular-layout").boundingBox(); + expect(layoutBox).not.toBeNull(); + const divider1X = layoutBox!.x + layoutBox!.width * 0.5; + const divider1Y = layoutBox!.y + layoutBox!.height * 0.5; + await dragMouse(page, divider1X, divider1Y, divider1X - 80, divider1Y); + const state1 = await saveLayout(page); + const divider2X = layoutBox!.x + layoutBox!.width * 0.25; + const divider2Y = layoutBox!.y + layoutBox!.height * 0.25; + await dragMouse(page, divider2X, divider2Y, divider2X, divider2Y + 60); + const state2 = await saveLayout(page); + await dragMouse(page, divider1X - 80, divider1Y, divider1X + 40, divider1Y); + const state3 = await saveLayout(page); + expect(state1).not.toEqual(state2); + expect(state2).not.toEqual(state3); + expect(state1).not.toEqual(state3); + await restoreAndVerify(page, state1); + await restoreAndVerify(page, state2); + await restoreAndVerify(page, state3); }); diff --git a/tests/integration/save-restore.spec.ts b/tests/integration/save-restore.spec.ts index 7ee9aee..ee51931 100644 --- a/tests/integration/save-restore.spec.ts +++ b/tests/integration/save-restore.spec.ts @@ -10,510 +10,91 @@ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ import { expect, test } from "@playwright/test"; -import type { Layout } from "../../dist/index.js"; - -test.describe("save and restore", () => { - test("should save and restore a simple single-panel layout", async ({ - page, - }) => { - await page.goto("/examples/index.html"); - await page.waitForSelector("regular-layout"); - - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.restore({ - type: "child-panel", - child: ["AAA"], - }); - }); - - const saved = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - expect(saved).toStrictEqual({ - type: "child-panel", - child: ["AAA"], - }); - }); - - test("should save and restore a simple single-panel layout, modify, and revert", async ({ - page, - }) => { - await page.goto("/examples/index.html"); - await page.waitForSelector("regular-layout"); - - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.restore({ - type: "child-panel", - child: ["AAA"], - }); - }); - - const saved = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.restore({ - type: "child-panel", - child: ["BBB"], - }); - }); - - await page.evaluate((state) => { - const layout = document.querySelector("regular-layout"); - layout?.restore(state as Layout); - }, saved); - - const restored = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - expect(restored).toStrictEqual(saved); - }); - - test("should save and restore a 2-panel horizontal split layout", async ({ - page, - }) => { - await page.goto("/examples/index.html"); - await page.waitForSelector("regular-layout"); - - const initialLayout: Layout = { - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - ], - sizes: [0.3, 0.7], - }; - - await page.evaluate((state) => { - const layout = document.querySelector("regular-layout"); - layout?.restore(state as Layout); - }, initialLayout); - - const saved = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - expect(saved).toStrictEqual(initialLayout); - }); - - test("should save and restore a nested layout", async ({ page }) => { - await page.goto("/examples/index.html"); - await page.waitForSelector("regular-layout"); - - const initialLayout: Layout = { - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - ], - sizes: [0.3, 0.7], - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.6, 0.4], - }; - - await page.evaluate((state) => { - const layout = document.querySelector("regular-layout"); - layout?.restore(state as Layout); - }, initialLayout); - - const saved = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - expect(saved).toStrictEqual(initialLayout); - }); - - test("should save and restore a nested layout, modify to single cell, revert", async ({ - page, - }) => { - await page.goto("/examples/index.html"); - await page.waitForSelector("regular-layout"); - - const initialLayout: Layout = { - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - ], - sizes: [0.3, 0.7], - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.6, 0.4], - }; - - await page.evaluate((state) => { - const layout = document.querySelector("regular-layout"); - layout?.restore(state as Layout); - }, initialLayout); - - const saved = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - expect(saved).toStrictEqual(initialLayout); - - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.restore({ - type: "child-panel", - child: ["DDD"], - }); - }); - - await page.evaluate((state) => { - const layout = document.querySelector("regular-layout"); - layout?.restore(state as Layout); - }, saved); - - const restored = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - expect(restored).toStrictEqual(initialLayout); - }); - - test("should save and restore a deeply nested layout", async ({ page }) => { - await page.goto("/examples/index.html"); - await page.waitForSelector("regular-layout"); - const initialLayout: Layout = { - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - ], - sizes: [0.4, 0.6], - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.5, 0.5], - }, - { - type: "child-panel", - child: ["DDD"], - }, - ], - sizes: [0.7, 0.3], - }; - - await page.evaluate((state) => { - const layout = document.querySelector("regular-layout"); - layout?.restore(state as Layout); - }, initialLayout); - - const saved = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - expect(saved).toStrictEqual(initialLayout); - }); - - test("should save returns a deep clone, not a reference", async ({ - page, - }) => { - await page.goto("/examples/index.html"); - await page.waitForSelector("regular-layout"); - - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.restore({ - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - ], - sizes: [0.5, 0.5], - }); - }); - - const saved = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.insertPanel("CCC", []); - }); - - const afterModification = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - expect(afterModification).not.toStrictEqual(saved); - expect(saved).toStrictEqual({ - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - ], - sizes: [0.5, 0.5], - }); - }); - - test("should restore updates shadow DOM slots correctly", async ({ - page, - }) => { - await page.goto("/examples/index.html"); - await page.waitForSelector("regular-layout"); - - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.restore({ - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - ], - sizes: [0.5, 0.5], - }); - }); - - const initialSlots = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - const slotElements = layout?.shadowRoot?.querySelectorAll("slot[name]"); - return Array.from(slotElements || []).map((slot) => - slot.getAttribute("name"), - ); - }); - - expect(initialSlots).toContain("AAA"); - expect(initialSlots).toContain("BBB"); - expect(initialSlots).toHaveLength(2); - - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.restore({ - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "child-panel", - child: ["CCC"], - }, - { - type: "child-panel", - child: ["DDD"], - }, - { - type: "child-panel", - child: ["EEE"], - }, - ], - sizes: [0.3, 0.3, 0.4], - }); - }); - - const updatedSlots = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - const slotElements = layout?.shadowRoot?.querySelectorAll("slot[name]"); - return Array.from(slotElements || []).map((slot) => - slot.getAttribute("name"), - ); - }); - - expect(updatedSlots).not.toContain("AAA"); - expect(updatedSlots).not.toContain("BBB"); - expect(updatedSlots).toContain("CCC"); - expect(updatedSlots).toContain("DDD"); - expect(updatedSlots).toContain("EEE"); - expect(updatedSlots).toHaveLength(3); - }); - - test("should save and restore preserve exact size ratios", async ({ - page, - }) => { - await page.goto("/examples/index.html"); - await page.waitForSelector("regular-layout"); - - const customSizes: Layout = { - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.123456789, 0.456789123, 0.419754088], - }; - - await page.evaluate((state) => { - const layout = document.querySelector("regular-layout"); - layout?.restore(state as Layout); - }, customSizes); - - const saved = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); +import { + setupLayout, + saveLayout, + restoreLayout, + expectLayoutState, + getSlots, + expectSlots, + restoreAndVerify, + insertPanel, +} from "../helpers/integration.ts"; +import { LAYOUTS } from "../helpers/fixtures.ts"; + +test("should save and restore various layout types", async ({ page }) => { + // Test single panel + await setupLayout(page, LAYOUTS.SINGLE_AAA); + await expectLayoutState(page, LAYOUTS.SINGLE_AAA); + + // Test 2-panel horizontal + await restoreLayout(page, LAYOUTS.TWO_HORIZONTAL); + await expectLayoutState(page, LAYOUTS.TWO_HORIZONTAL); + + // Test nested layout + await restoreLayout(page, LAYOUTS.NESTED_BASIC); + await expectLayoutState(page, LAYOUTS.NESTED_BASIC); +}); - expect(saved).toStrictEqual(customSizes); +test("should save, modify, and revert to saved state", async ({ page }) => { + // Simple case: single panel + await setupLayout(page, LAYOUTS.SINGLE_AAA); + const saved1 = await saveLayout(page); + await restoreLayout(page, LAYOUTS.SINGLE_BBB); + await restoreAndVerify(page, saved1); + + // Complex case: nested layout + await restoreLayout(page, LAYOUTS.NESTED_BASIC); + const saved2 = await saveLayout(page); + await restoreLayout(page, LAYOUTS.SINGLE_DDD); + await restoreAndVerify(page, saved2); +}); - await page.evaluate((state) => { - const layout = document.querySelector("regular-layout"); - layout?.restore(state as Layout); - }, saved); +test("should save and restore a deeply nested layout", async ({ page }) => { + await setupLayout(page, LAYOUTS.DEEPLY_NESTED_ALT); + await expectLayoutState(page, LAYOUTS.DEEPLY_NESTED_ALT); +}); - const restored = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); +test("should save returns a deep clone, not a reference", async ({ page }) => { + await setupLayout(page, LAYOUTS.TWO_HORIZONTAL_EQUAL); + const saved = await saveLayout(page); + await insertPanel(page, "CCC", []); + const afterModification = await saveLayout(page); + expect(afterModification).not.toStrictEqual(saved); + expect(saved).toStrictEqual(LAYOUTS.TWO_HORIZONTAL_EQUAL); +}); - expect(restored).toStrictEqual(customSizes); +test("should restore updates shadow DOM slots correctly", async ({ page }) => { + await setupLayout(page, LAYOUTS.TWO_HORIZONTAL_EQUAL); + const initialSlots = await getSlots(page); + expect(initialSlots).toContain("AAA"); + expect(initialSlots).toContain("BBB"); + expect(initialSlots).toHaveLength(2); + await restoreLayout(page, LAYOUTS.THREE_VERTICAL_CDE); + await expectSlots(page, { + notContains: ["AAA", "BBB"], + contains: ["CCC", "DDD", "EEE"], }); - test("should save and restore handle empty then populated layout", async ({ - page, - }) => { - await page.goto("/examples/index.html"); - await page.waitForSelector("regular-layout"); - - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.restore({ - type: "child-panel", - child: ["AAA"], - }); - }); - - const saved1 = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - layout?.insertPanel("BBB", []); - layout?.insertPanel("CCC", []); - }); - - const saved2 = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - await page.evaluate((state) => { - const layout = document.querySelector("regular-layout"); - layout?.restore(state as Layout); - }, saved1); - - const restored1 = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); - - expect(restored1).toStrictEqual(saved1); - - await page.evaluate((state) => { - const layout = document.querySelector("regular-layout"); - layout?.restore(state as Layout); - }, saved2); + const updatedSlots = await getSlots(page); + expect(updatedSlots).toHaveLength(3); +}); - const restored2 = await page.evaluate(() => { - const layout = document.querySelector("regular-layout"); - return layout?.save(); - }); +test("should save and restore preserve exact size ratios", async ({ page }) => { + await setupLayout(page, LAYOUTS.THREE_HORIZONTAL_PRECISE); + const saved = await saveLayout(page); + expect(saved).toStrictEqual(LAYOUTS.THREE_HORIZONTAL_PRECISE); + await restoreAndVerify(page, saved); +}); - expect(restored2).toStrictEqual(saved2); - }); +test("should save and restore handle empty then populated layout", async ({ + page, +}) => { + await setupLayout(page, LAYOUTS.SINGLE_AAA); + const saved1 = await saveLayout(page); + await insertPanel(page, "BBB", []); + await insertPanel(page, "CCC", []); + const saved2 = await saveLayout(page); + await restoreAndVerify(page, saved1); + await restoreAndVerify(page, saved2); }); diff --git a/tests/integration/tabs.spec.ts b/tests/integration/tabs.spec.ts new file mode 100644 index 0000000..fd533ec --- /dev/null +++ b/tests/integration/tabs.spec.ts @@ -0,0 +1,234 @@ +// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +// ░░░░░░░░▄▀░█▀▄░█▀▀░█▀▀░█░█░█░░░█▀█░█▀▄░░░░░█░░░█▀█░█░█░█▀█░█░█░▀█▀░▀▄░░░░░░░░ +// ░░░░░░░▀▄░░█▀▄░█▀▀░█░█░█░█░█░░░█▀█░█▀▄░▀▀▀░█░░░█▀█░░█░░█░█░█░█░░█░░░▄▀░░░░░░░ +// ░░░░░░░░░▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀░▀░░░░░▀▀▀░▀░▀░░▀░░▀▀▀░▀▀▀░░▀░░▀░░░░░░░░░ +// ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +// ┃ * Copyright (c) 2026, the Regular Layout Authors. This file is part * ┃ +// ┃ * of the Regular Layout library, distributed under the terms of the * ┃ +// ┃ * [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). * ┃ +// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + +import { expect, test } from "@playwright/test"; +import type { Layout } from "../../dist/index.js"; +import { LAYOUTS } from "../helpers/fixtures.ts"; + +test("should switch between tabs by clicking", async ({ page }) => { + await page.goto("/examples/index.html"); + await page.waitForSelector("regular-layout"); + await page.evaluate((layout) => { + const layoutElement = document.querySelector("regular-layout"); + layoutElement?.restore(layout as Layout); + }, LAYOUTS.SINGLE_TABS_WITH_SELECTED); + + const getSelectedTab = async () => { + return await page.evaluate(() => { + const frame = document.querySelector('regular-layout-frame[slot="AAA"]'); + const activeTab = frame?.shadowRoot?.querySelector( + '[part~="active-tab"]', + ); + return activeTab?.textContent; + }); + }; + + const selectedBefore = await getSelectedTab(); + expect(selectedBefore).toBe("AAA"); + const frameBounds = await page.evaluate(() => { + const frame = document.querySelector('regular-layout-frame[slot="AAA"]'); + const tabs = frame?.shadowRoot?.querySelectorAll('[part~="tab"]'); + if (!tabs || tabs.length < 2) return null; + const secondTab = tabs[1] as HTMLElement; + const rect = secondTab.getBoundingClientRect(); + return { + x: rect.left + rect.width / 2, + y: rect.top + rect.height / 2, + }; + }); + + expect(frameBounds).not.toBeNull(); + if (frameBounds) { + await page.mouse.click(frameBounds.x, frameBounds.y); + } + + await page.waitForTimeout(100); + const selectedAfter = await getSelectedTab(); + expect(selectedAfter).toBe("BBB"); + const layoutState = await page.evaluate(() => { + const layout = document.querySelector("regular-layout"); + return layout?.save(); + }); + + expect(layoutState).toMatchObject({ + type: "child-panel", + child: ["AAA", "BBB", "CCC"], + selected: 1, + }); +}); + +test("should move a panel by dragging a selected tab", async ({ page }) => { + await page.goto("/examples/index.html"); + await page.waitForSelector("regular-layout"); + await page.evaluate((layout) => { + const layoutElement = document.querySelector("regular-layout"); + layoutElement?.restore(layout as Layout); + }, LAYOUTS.TWO_HORIZONTAL_WITH_TABS); + + const layoutBefore = await page.evaluate(() => { + const layout = document.querySelector("regular-layout"); + return layout?.save(); + }); + + expect(layoutBefore).toMatchObject({ + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "child-panel", + child: ["AAA", "BBB"], + selected: 0, + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + }); + + const dragCoords = await page.evaluate(() => { + const frame = document.querySelector('regular-layout-frame[slot="AAA"]'); + const activeTab = frame?.shadowRoot?.querySelector( + '[part~="active-tab"]', + ) as HTMLElement; + + if (!activeTab) return null; + const tabRect = activeTab.getBoundingClientRect(); + const layout = document.querySelector("regular-layout"); + const layoutRect = layout?.getBoundingClientRect(); + if (!layoutRect) return null; + return { + fromX: tabRect.left + tabRect.width / 2, + fromY: tabRect.top + tabRect.height / 2, + toX: layoutRect.right - 50, + toY: layoutRect.top + layoutRect.height / 2, + }; + }); + + expect(dragCoords).not.toBeNull(); + if (dragCoords) { + await page.mouse.move(dragCoords.fromX, dragCoords.fromY); + await page.mouse.down(); + await page.mouse.move(dragCoords.toX, dragCoords.toY); + await page.mouse.up(); + } + + await page.waitForTimeout(100); + const layoutAfter = await page.evaluate(() => { + const layout = document.querySelector("regular-layout"); + return layout?.save(); + }); + + expect(layoutAfter).toMatchObject({ + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "child-panel", + child: ["BBB"], + selected: 0, + }, + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + }); +}); + +test("should move a panel by dragging a deselected tab", async ({ page }) => { + await page.goto("/examples/index.html"); + await page.waitForSelector("regular-layout"); + await page.evaluate((layout) => { + const layoutElement = document.querySelector("regular-layout"); + layoutElement?.restore(layout as Layout); + }, LAYOUTS.TWO_HORIZONTAL_WITH_TABS); + + const layoutBefore = await page.evaluate(() => { + const layout = document.querySelector("regular-layout"); + return layout?.save(); + }); + + expect(layoutBefore).toMatchObject({ + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "child-panel", + child: ["AAA", "BBB"], + selected: 0, + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + }); + + const dragCoords = await page.evaluate(() => { + const frame = document.querySelector('regular-layout-frame[slot="AAA"]'); + const tabs = frame?.shadowRoot?.querySelectorAll('[part~="tab"]'); + if (!tabs || tabs.length < 2) return null; + const inactiveTab = Array.from(tabs).find( + (tab) => !tab.getAttribute("part")?.includes("active-tab"), + ) as HTMLElement; + + if (!inactiveTab) return null; + const tabRect = inactiveTab.getBoundingClientRect(); + const layout = document.querySelector("regular-layout"); + const layoutRect = layout?.getBoundingClientRect(); + if (!layoutRect) return null; + return { + fromX: tabRect.left + tabRect.width / 2, + fromY: tabRect.top + tabRect.height / 2, + toX: layoutRect.right - 50, + toY: layoutRect.top + layoutRect.height / 2, + }; + }); + + expect(dragCoords).not.toBeNull(); + if (dragCoords) { + await page.mouse.move(dragCoords.fromX, dragCoords.fromY); + await page.mouse.down(); + await page.mouse.move(dragCoords.toX, dragCoords.toY); + await page.mouse.up(); + } + + await page.waitForTimeout(100); + const layoutAfter = await page.evaluate(() => { + const layout = document.querySelector("regular-layout"); + return layout?.save(); + }); + + expect(layoutAfter).toMatchObject({ + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "child-panel", + child: ["AAA"], + selected: 0, + }, + { + type: "child-panel", + child: ["BBB"], + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + }); +}); diff --git a/tests/unit/calculate_split.spec.ts b/tests/unit/calculate_split.spec.ts index b7f9c26..a6772be 100644 --- a/tests/unit/calculate_split.spec.ts +++ b/tests/unit/calculate_split.spec.ts @@ -10,323 +10,286 @@ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ import { expect, test } from "@playwright/test"; -import { TEST_PANEL } from "../fixtures.ts"; +import { TEST_PANEL, LAYOUTS } from "../helpers/fixtures.ts"; import { calculate_split } from "../../src/common/calculate_split.ts"; import { calculate_intersection } from "../../src/common/calculate_intersect.ts"; -import type { Layout, LayoutPath } from "../../src/common/layout_config.ts"; +import type { LayoutPath, TabLayout } from "../../src/common/layout_config.ts"; -test.describe("calculate_split", () => { - test("cursor in center of panel - no split", () => { - const drop_target = calculate_intersection(0.3, 0.5, TEST_PANEL, false); - const result = calculate_split(0.3, 0.5, TEST_PANEL, "DDD", drop_target); - - expect(result.is_edge).toBe(false); - expect(result.slot).toBe("BBB"); - }); - - test("cursor near left edge of vertical split panel", () => { - const drop_target = calculate_intersection(0.05, 0.3, TEST_PANEL, false); - const result = calculate_split(0.05, 0.3, TEST_PANEL, "DDD", drop_target); - - expect(result.is_edge).toBe(true); - expect(result.slot).toBe("DDD"); - }); - - test("cursor near right edge of vertical split panel", () => { - const drop_target = calculate_intersection(0.55, 0.3, TEST_PANEL, false); - const result = calculate_split(0.55, 0.3, TEST_PANEL, "DDD", drop_target); - - expect(result.is_edge).toBe(true); - expect(result.slot).toBe("DDD"); - }); - - test("cursor near top edge of horizontal split panel", () => { - const drop_target = calculate_intersection(0.7, 0.05, TEST_PANEL, false); - const result = calculate_split(0.7, 0.05, TEST_PANEL, "DDD", drop_target); - - expect(result.is_edge).toBe(true); - expect(result.slot).toBe("CCC"); // ??? - }); - - test("cursor near bottom edge of horizontal split panel", () => { - const drop_target = calculate_intersection(0.7, 0.95, TEST_PANEL, false); - const result = calculate_split(0.7, 0.95, TEST_PANEL, "DDD", drop_target); +test("cursor in center of panel - no split", () => { + const drop_target = calculate_intersection(0.3, 0.5, TEST_PANEL, false); + const result = calculate_split(0.3, 0.5, TEST_PANEL, "DDD", drop_target); + expect(result.is_edge).toBe(false); + expect(result.slot).toBe("BBB"); +}); - expect(result.is_edge).toBe(true); - expect(result.slot).toBe("CCC"); // ??? - }); +test("cursor near left edge of vertical split panel", () => { + const drop_target = calculate_intersection(0.05, 0.3, TEST_PANEL, false); + const result = calculate_split(0.05, 0.3, TEST_PANEL, "DDD", drop_target); + expect(result.is_edge).toBe(true); + expect(result.slot).toBe("DDD"); +}); - test("cursor near left edge but with horizontal orientation", () => { - const singlePanel: Layout = { - type: "split-panel", - sizes: [1], - orientation: "horizontal", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - ], - }; - - const drop_target = calculate_intersection(0.05, 0.5, singlePanel, false); - const result = calculate_split(0.05, 0.5, singlePanel, "BBB", drop_target); - - expect(result.is_edge).toBe(true); - expect(result.slot).toBe("BBB"); - }); +test("cursor near right edge of vertical split panel", () => { + const drop_target = calculate_intersection(0.55, 0.3, TEST_PANEL, false); + const result = calculate_split(0.55, 0.3, TEST_PANEL, "DDD", drop_target); + expect(result.is_edge).toBe(true); + expect(result.slot).toBe("DDD"); +}); - test("cursor near right edge but with horizontal orientation", () => { - const singlePanel: Layout = { - type: "split-panel", - orientation: "horizontal", - sizes: [1], - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - ], - }; - - const drop_target = calculate_intersection(0.95, 0.5, singlePanel, false); - const result = calculate_split(0.95, 0.5, singlePanel, "BBB", drop_target); - - expect(result.is_edge).toBe(true); - expect(result.slot).toBe("BBB"); - }); +test("cursor near top edge of horizontal split panel", () => { + const drop_target = calculate_intersection(0.7, 0.05, TEST_PANEL, false); + const result = calculate_split(0.7, 0.05, TEST_PANEL, "DDD", drop_target); + expect(result.is_edge).toBe(true); + expect(result.slot).toBe("CCC"); // ??? +}); - test("cursor near top edge but with vertical orientation", () => { - const singlePanel: Layout = { - type: "split-panel", - orientation: "vertical", - sizes: [1], - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - ], - }; - - const drop_target = calculate_intersection(0.5, 0.05, singlePanel, false); - const result = calculate_split(0.5, 0.05, singlePanel, "BBB", drop_target); - - expect(result.is_edge).toBe(true); - expect(result.slot).toBe("BBB"); - }); +test("cursor near bottom edge of horizontal split panel", () => { + const drop_target = calculate_intersection(0.7, 0.95, TEST_PANEL, false); + const result = calculate_split(0.7, 0.95, TEST_PANEL, "DDD", drop_target); + expect(result.is_edge).toBe(true); + expect(result.slot).toBe("CCC"); // ??? +}); - test("cursor near bottom edge but with vertical orientation", () => { - const singlePanel: Layout = { - type: "split-panel", - orientation: "vertical", - sizes: [1], - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - ], - }; - - const drop_target = calculate_intersection(0.5, 0.95, singlePanel, false); - const result = calculate_split(0.5, 0.95, singlePanel, "BBB", drop_target); - - expect(result.is_edge).toBe(true); - expect(result.slot).toBe("BBB"); - }); +test("cursor near left edge but with horizontal orientation", () => { + const drop_target = calculate_intersection( + 0.05, + 0.5, + LAYOUTS.SINGLE_SPLIT_HORIZONTAL, + false, + ); + + const result = calculate_split( + 0.05, + 0.5, + LAYOUTS.SINGLE_SPLIT_HORIZONTAL, + "BBB", + drop_target, + ); + + expect(result.is_edge).toBe(true); + expect(result.slot).toBe("BBB"); +}); - test("integrated top edge", () => { - const col = 0.51; - const row = 0.002; - const panel: Layout = { type: "child-panel", child: ["AAA"] }; - - let drop_target = calculate_intersection(col, row, panel, false); - if (drop_target) { - drop_target = calculate_split(col, row, panel, "BBB", drop_target); - } - - expect(drop_target.view_window).toStrictEqual({ - col_end: 1, - col_start: 0, - row_end: 0.5, - row_start: 0, - }); - }); +test("cursor near right edge but with horizontal orientation", () => { + const drop_target = calculate_intersection( + 0.95, + 0.5, + LAYOUTS.SINGLE_SPLIT_HORIZONTAL, + false, + ); + + const result = calculate_split( + 0.95, + 0.5, + LAYOUTS.SINGLE_SPLIT_HORIZONTAL, + "BBB", + drop_target, + ); + + expect(result.is_edge).toBe(true); + expect(result.slot).toBe("BBB"); +}); - test("integrated right edge", () => { - const col = 0.998; - const row = 0.53; - const panel: Layout = { type: "child-panel", child: ["AAA"] }; - - let drop_target = calculate_intersection(col, row, panel, false); - if (drop_target) { - drop_target = calculate_split(col, row, panel, "BBB", drop_target); - } - - expect(drop_target.view_window).toStrictEqual({ - col_end: 1, - col_start: 0.5, - row_end: 1, - row_start: 0, - }); - }); +test("cursor near top edge but with vertical orientation", () => { + const drop_target = calculate_intersection( + 0.5, + 0.05, + LAYOUTS.SINGLE_SPLIT_VERTICAL, + false, + ); + + const result = calculate_split( + 0.5, + 0.05, + LAYOUTS.SINGLE_SPLIT_VERTICAL, + "BBB", + drop_target, + ); + + expect(result.is_edge).toBe(true); + expect(result.slot).toBe("BBB"); +}); - test("cursor in top-left corner prioritizes row offset", () => { - const singlePanel: Layout = { - type: "child-panel", - child: ["AAA"], - }; - - const drop_target: LayoutPath = { - type: "layout-path", - slot: "AAA", - panel: singlePanel, - path: [], - view_window: { row_start: 0, row_end: 1, col_start: 0, col_end: 1 }, - column_offset: 0.1, - row_offset: 0.05, - orientation: "horizontal", - is_edge: false, - box: undefined, - }; - - const result = calculate_split(0.1, 0.05, singlePanel, "BBB", drop_target); - - expect(result.is_edge).toBe(true); - }); +test("cursor near bottom edge but with vertical orientation", () => { + const drop_target = calculate_intersection( + 0.5, + 0.95, + LAYOUTS.SINGLE_SPLIT_VERTICAL, + false, + ); + + const result = calculate_split( + 0.5, + 0.95, + LAYOUTS.SINGLE_SPLIT_VERTICAL, + "BBB", + drop_target, + ); + + expect(result.is_edge).toBe(true); + expect(result.slot).toBe("BBB"); +}); - test("cursor in bottom-right corner prioritizes row offset", () => { - const singlePanel: Layout = { - type: "child-panel", - child: ["AAA"], - }; - - const drop_target: LayoutPath = { - type: "layout-path", - slot: "AAA", - panel: singlePanel, - path: [], - view_window: { row_start: 0, row_end: 1, col_start: 0, col_end: 1 }, - column_offset: 0.9, - row_offset: 0.95, - orientation: "horizontal", - is_edge: false, - box: undefined, - }; - - const result = calculate_split(0.9, 0.95, singlePanel, "BBB", drop_target); - - expect(result.is_edge).toBe(true); +test("integrated top edge", () => { + const col = 0.51; + const row = 0.002; + let drop_target = calculate_intersection(col, row, LAYOUTS.SINGLE_AAA, false); + if (drop_target) { + drop_target = calculate_split( + col, + row, + LAYOUTS.SINGLE_AAA, + "BBB", + drop_target, + ); + } + + expect(drop_target.view_window).toStrictEqual({ + col_end: 1, + col_start: 0, + row_end: 0.5, + row_start: 0, }); +}); - test("cursor near edge with offset exactly at tolerance threshold", () => { - const singlePanel: Layout = { - type: "child-panel", - child: ["AAA"], - }; - - const drop_target: LayoutPath = { - type: "layout-path", - slot: "AAA", - path: [], - panel: singlePanel, - view_window: { row_start: 0, row_end: 1, col_start: 0, col_end: 1 }, - column_offset: 0.3, - row_offset: 0.5, - orientation: "horizontal", - is_edge: false, - box: undefined, - }; - - const result = calculate_split(0.3, 0.5, singlePanel, "BBB", drop_target); - - expect(result.is_edge).toBe(false); +test("integrated right edge", () => { + const col = 0.998; + const row = 0.53; + let drop_target = calculate_intersection(col, row, LAYOUTS.SINGLE_AAA, false); + if (drop_target) { + drop_target = calculate_split( + col, + row, + LAYOUTS.SINGLE_AAA, + "BBB", + drop_target, + ); + } + + expect(drop_target.view_window).toStrictEqual({ + col_end: 1, + col_start: 0.5, + row_end: 1, + row_start: 0, }); +}); - test("cursor near edge with offset just below tolerance threshold", () => { - const singlePanel: Layout = { - type: "child-panel", - child: ["AAA"], - }; - - const drop_target: LayoutPath = { - type: "layout-path", - slot: "AAA", - panel: singlePanel, - path: [], - view_window: { row_start: 0, row_end: 1, col_start: 0, col_end: 1 }, - column_offset: 0.14, - row_offset: 0.5, - orientation: "horizontal", - is_edge: false, - box: undefined, - }; - - const result = calculate_split(0.14, 0.5, singlePanel, "BBB", drop_target); - - expect(result.is_edge).toBe(true); - }); +test("cursor in top-left corner prioritizes row offset", () => { + const singlePanel = LAYOUTS.SINGLE_AAA; + const drop_target: LayoutPath = { + type: "layout-path", + slot: "AAA", + panel: singlePanel as TabLayout, + path: [], + view_window: { row_start: 0, row_end: 1, col_start: 0, col_end: 1 }, + column_offset: 0.1, + row_offset: 0.05, + orientation: "horizontal", + is_edge: false, + box: undefined, + }; + + const result = calculate_split(0.1, 0.05, singlePanel, "BBB", drop_target); + expect(result.is_edge).toBe(true); +}); - test("nested panel with vertical orientation at left edge", () => { - const drop_target = calculate_intersection(0.02, 0.3, TEST_PANEL, false); - const result = calculate_split(0.02, 0.3, TEST_PANEL, "DDD", drop_target); +test("cursor in bottom-right corner prioritizes row offset", () => { + const singlePanel = LAYOUTS.SINGLE_AAA; + const drop_target: LayoutPath = { + type: "layout-path", + slot: "AAA", + panel: singlePanel as TabLayout, + path: [], + view_window: { row_start: 0, row_end: 1, col_start: 0, col_end: 1 }, + column_offset: 0.9, + row_offset: 0.95, + orientation: "horizontal", + is_edge: false, + box: undefined, + }; + + const result = calculate_split(0.9, 0.95, singlePanel, "BBB", drop_target); + expect(result.is_edge).toBe(true); +}); - expect(result.is_edge).toBe(true); - expect(result.slot).toBe("DDD"); - expect(result.path).toEqual([0, 1, 0]); - }); +test("cursor near edge with offset exactly at tolerance threshold", () => { + const singlePanel = LAYOUTS.SINGLE_AAA; + const drop_target: LayoutPath = { + type: "layout-path", + slot: "AAA", + path: [], + panel: singlePanel as TabLayout, + view_window: { row_start: 0, row_end: 1, col_start: 0, col_end: 1 }, + column_offset: 0.3, + row_offset: 0.5, + orientation: "horizontal", + is_edge: false, + box: undefined, + }; + + const result = calculate_split(0.3, 0.5, singlePanel, "BBB", drop_target); + expect(result.is_edge).toBe(false); +}); - test("nested panel with vertical orientation at right edge", () => { - const drop_target = calculate_intersection(0.58, 0.3, TEST_PANEL, false); - const result = calculate_split(0.58, 0.3, TEST_PANEL, "DDD", drop_target); +test("cursor near edge with offset just below tolerance threshold", () => { + const singlePanel = LAYOUTS.SINGLE_AAA; + const drop_target: LayoutPath = { + type: "layout-path", + slot: "AAA", + panel: singlePanel as TabLayout, + path: [], + view_window: { row_start: 0, row_end: 1, col_start: 0, col_end: 1 }, + column_offset: 0.14, + row_offset: 0.5, + orientation: "horizontal", + is_edge: false, + box: undefined, + }; + + const result = calculate_split(0.14, 0.5, singlePanel, "BBB", drop_target); + expect(result.is_edge).toBe(true); +}); - expect(result.is_edge).toBe(true); - expect(result.slot).toBe("DDD"); - expect(result.path).toEqual([0, 1, 1]); - }); +test("nested panel with vertical orientation at left edge", () => { + const drop_target = calculate_intersection(0.02, 0.3, TEST_PANEL, false); + const result = calculate_split(0.02, 0.3, TEST_PANEL, "DDD", drop_target); + expect(result.is_edge).toBe(true); + expect(result.slot).toBe("DDD"); + expect(result.path).toEqual([0, 1, 0]); +}); - test("complex layout with multiple nested panels", () => { - const complexPanel: Layout = { - type: "split-panel", - orientation: "horizontal", - sizes: [0.5, 0.5], - children: [ - { - type: "split-panel", - orientation: "vertical", - sizes: [0.5, 0.5], - children: [ - { - type: "child-panel", - child: ["A"], - }, - { - type: "child-panel", - child: ["B"], - }, - ], - }, - { - type: "child-panel", - child: ["C"], - }, - ], - }; - - const drop_target = calculate_intersection(0.02, 0.3, complexPanel, false); - const result = calculate_split(0.02, 0.3, complexPanel, "DDD", drop_target); - - expect(result.is_edge).toBe(true); - expect(result.slot).toBe("DDD"); - }); +test("nested panel with vertical orientation at right edge", () => { + const drop_target = calculate_intersection(0.58, 0.3, TEST_PANEL, false); + const result = calculate_split(0.58, 0.3, TEST_PANEL, "DDD", drop_target); + expect(result.is_edge).toBe(true); + expect(result.slot).toBe("DDD"); + expect(result.path).toEqual([0, 1, 1]); +}); - test("preserves drop_target properties when no split occurs", () => { - const drop_target = calculate_intersection(0.3, 0.3, TEST_PANEL, false); - const result = calculate_split(0.3, 0.3, TEST_PANEL, "DDD", drop_target); +test("complex layout with multiple nested panels", () => { + const drop_target = calculate_intersection( + 0.02, + 0.3, + LAYOUTS.COMPLEX_NESTED_ABC, + false, + ); + + const result = calculate_split( + 0.02, + 0.3, + LAYOUTS.COMPLEX_NESTED_ABC, + "DDD", + drop_target, + ); + + expect(result.is_edge).toBe(true); + expect(result.slot).toBe("DDD"); +}); - expect(result.view_window).toBeDefined(); - expect(result.path).toBeDefined(); - expect(result.orientation).toBeDefined(); - }); +test("preserves drop_target properties when no split occurs", () => { + const drop_target = calculate_intersection(0.3, 0.3, TEST_PANEL, false); + const result = calculate_split(0.3, 0.3, TEST_PANEL, "DDD", drop_target); + expect(result.view_window).toBeDefined(); + expect(result.path).toBeDefined(); + expect(result.orientation).toBeDefined(); }); diff --git a/tests/unit/css_grid_layout.spec.ts b/tests/unit/css_grid_layout.spec.ts index 86df77e..5126a51 100644 --- a/tests/unit/css_grid_layout.spec.ts +++ b/tests/unit/css_grid_layout.spec.ts @@ -10,7 +10,7 @@ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ import { expect, test } from "@playwright/test"; -import { TEST_PANEL } from "../fixtures.ts"; +import { TEST_PANEL } from "../helpers/fixtures.ts"; import { create_css_grid_layout } from "../../src/common/generate_grid.ts"; import type { Layout } from "../../src/common/layout_config.ts"; diff --git a/tests/unit/flatten.spec.ts b/tests/unit/flatten.spec.ts index 7f9706b..c094a1f 100644 --- a/tests/unit/flatten.spec.ts +++ b/tests/unit/flatten.spec.ts @@ -10,10 +10,6 @@ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ import { expect, test } from "@playwright/test"; - -import { calculate_intersection } from "../../src/common/calculate_intersect.ts"; -import { calculate_split } from "../../src/common/calculate_split.ts"; - import { flatten } from "../../src/common/flatten.ts"; import type { Layout } from "../../src/common/layout_config.ts"; diff --git a/tests/unit/hit_detection.spec.ts b/tests/unit/hit_detection.spec.ts index c82e086..f4dd028 100644 --- a/tests/unit/hit_detection.spec.ts +++ b/tests/unit/hit_detection.spec.ts @@ -11,102 +11,104 @@ import { expect, test } from "@playwright/test"; import { calculate_intersection } from "../../src/common/calculate_intersect.ts"; -import { TEST_PANEL } from "../fixtures.ts"; +import { TEST_PANEL } from "../helpers/fixtures.ts"; -test.describe("hit detection", async () => { - test("AAA", () => { - const result = calculate_intersection(0.1, 0.1, TEST_PANEL); - expect(result).toStrictEqual({ - slot: "AAA", - box: undefined, - path: [0, 0], - type: "layout-path", - is_edge: false, - orientation: "vertical", - view_window: { - col_end: 0.6, - col_start: 0, - row_end: 0.3, - row_start: 0, - }, - column_offset: 0.16666666666666669, - row_offset: 0.33333333333333337, - panel: { - child: ["AAA"], - type: "child-panel", - }, - }); +test("AAA", () => { + const result = calculate_intersection(0.1, 0.1, TEST_PANEL); + expect(result).toStrictEqual({ + slot: "AAA", + box: undefined, + path: [0, 0], + type: "layout-path", + is_edge: false, + orientation: "vertical", + view_window: { + col_end: 0.6, + col_start: 0, + row_end: 0.3, + row_start: 0, + }, + column_offset: 0.16666666666666669, + row_offset: 0.33333333333333337, + panel: { + child: ["AAA"], + type: "child-panel", + }, }); +}); - test("BBB", () => { - const result = calculate_intersection(0.1, 0.4, TEST_PANEL); - expect(result).toStrictEqual({ - slot: "BBB", - path: [0, 1], - box: undefined, - type: "layout-path", - is_edge: false, - orientation: "vertical", - view_window: { - col_end: 0.6, - col_start: 0, - row_end: 1, - row_start: 0.3, - }, - column_offset: 0.16666666666666669, - row_offset: 0.1428571428571429, - panel: { - child: ["BBB"], - type: "child-panel", - }, - }); +test("BBB", () => { + const result = calculate_intersection(0.1, 0.4, TEST_PANEL); + expect(result).toStrictEqual({ + slot: "BBB", + path: [0, 1], + box: undefined, + type: "layout-path", + is_edge: false, + orientation: "vertical", + view_window: { + col_end: 0.6, + col_start: 0, + row_end: 1, + row_start: 0.3, + }, + column_offset: 0.16666666666666669, + row_offset: 0.1428571428571429, + panel: { + child: ["BBB"], + type: "child-panel", + }, }); +}); - test.skip("CCC", () => { - const result = calculate_intersection(0.7, 0.1, TEST_PANEL); - expect(result).toStrictEqual({ - slot: "CCC", - path: [1], - box: undefined, - type: "layout-path", - is_edge: false, - orientation: "horizontal", - view_window: { - row_end: 1, - row_start: 0, - col_end: 1, - col_start: 0.6, - }, - column_offset: 0.24999999999999994, - row_offset: 0.1, - }); +test("CCC", () => { + const result = calculate_intersection(0.7, 0.1, TEST_PANEL); + expect(result).toStrictEqual({ + slot: "CCC", + path: [1], + box: undefined, + type: "layout-path", + is_edge: false, + orientation: "horizontal", + panel: { + child: ["CCC"], + type: "child-panel", + }, + view_window: { + row_end: 1, + row_start: 0, + col_end: 1, + col_start: 0.6, + }, + column_offset: 0.24999999999999994, + row_offset: 0.1, }); +}); - test("gap", () => { - const result = calculate_intersection(0.6, 0.1, TEST_PANEL); - expect(result).toStrictEqual({ - path: [0], - type: "horizontal", - view_window: { - row_start: 0, - row_end: 1, - col_start: 0, - col_end: 0.6, - }, - }); +test("gap", () => { + const result = calculate_intersection(0.6, 0.1, TEST_PANEL); + expect(result).toStrictEqual({ + path: [0], + type: "horizontal", + view_window: { + row_start: 0, + row_end: 1, + col_start: 0, + col_end: 0.6, + }, }); +}); - test("nested gap", () => { - const result = calculate_intersection(0.1, 0.3, TEST_PANEL); - expect(result).toStrictEqual({ - path: [0, 0], - type: "vertical", - view_window: { - row_start: 0, - row_end: 0.3, - col_start: 0, - col_end: 0.6, - }, - }); +test("nested gap", () => { + const result = calculate_intersection(0.1, 0.3, TEST_PANEL); + expect(result).toStrictEqual({ + path: [0, 0], + type: "vertical", + view_window: { + row_start: 0, + row_end: 0.3, + col_start: 0, + col_end: 0.6, + }, }); }); diff --git a/tests/unit/insert_child.spec.ts b/tests/unit/insert_child.spec.ts index 7d184e5..ee5416d 100644 --- a/tests/unit/insert_child.spec.ts +++ b/tests/unit/insert_child.spec.ts @@ -10,355 +10,326 @@ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ import { expect, test } from "@playwright/test"; -import { TEST_PANEL } from "../fixtures.ts"; +import { TEST_PANEL, LAYOUTS } from "../helpers/fixtures.ts"; import { insert_child } from "../../src/common/insert_child.ts"; -import type { Layout } from "../../src/common/layout_config.ts"; -test.describe("insert_child", async () => { - test("insert into root split panel", () => { - const result = insert_child(TEST_PANEL, "DDD", []); - expect(result).toStrictEqual({ - type: "split-panel", - children: [ - { - type: "split-panel", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - ], - sizes: [0.3, 0.7], - orientation: "vertical", - }, - { - type: "child-panel", - child: ["CCC"], - }, - { - type: "child-panel", - child: ["DDD"], - }, - ], - sizes: [0.3333333333333333, 0.3333333333333333, 0.3333333333333333], - orientation: "horizontal", - }); +test("insert into root split panel", () => { + const result = insert_child(TEST_PANEL, "DDD", []); + expect(result).toStrictEqual({ + type: "split-panel", + children: [ + { + type: "split-panel", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["BBB"], + }, + ], + sizes: [0.3, 0.7], + orientation: "vertical", + }, + { + type: "child-panel", + child: ["CCC"], + }, + { + type: "child-panel", + child: ["DDD"], + }, + ], + sizes: [0.3333333333333333, 0.3333333333333333, 0.3333333333333333], + orientation: "horizontal", }); +}); - test("append top level split-panel", () => { - const result = insert_child(TEST_PANEL, "DDD", [2]); - expect(result).toStrictEqual({ - type: "split-panel", - children: [ - { - type: "split-panel", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - ], - sizes: [0.3, 0.7], - orientation: "vertical", - }, - { - type: "child-panel", - child: ["CCC"], - }, - { - type: "child-panel", - child: ["DDD"], - }, - ], - sizes: [0.3333333333333333, 0.3333333333333333, 0.3333333333333333], - orientation: "horizontal", - }); +test("append top level split-panel", () => { + const result = insert_child(TEST_PANEL, "DDD", [2]); + expect(result).toStrictEqual({ + type: "split-panel", + children: [ + { + type: "split-panel", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["BBB"], + }, + ], + sizes: [0.3, 0.7], + orientation: "vertical", + }, + { + type: "child-panel", + child: ["CCC"], + }, + { + type: "child-panel", + child: ["DDD"], + }, + ], + sizes: [0.3333333333333333, 0.3333333333333333, 0.3333333333333333], + orientation: "horizontal", }); +}); - test("insert into top level split-panel", () => { - const result = insert_child(TEST_PANEL, "DDD", [1]); - expect(result).toStrictEqual({ - type: "split-panel", - children: [ - { - type: "split-panel", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - ], - sizes: [0.3, 0.7], - orientation: "vertical", - }, - { - type: "child-panel", - child: ["DDD"], - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.3333333333333333, 0.3333333333333333, 0.3333333333333333], - orientation: "horizontal", - }); +test("insert into top level split-panel", () => { + const result = insert_child(TEST_PANEL, "DDD", [1]); + expect(result).toStrictEqual({ + type: "split-panel", + children: [ + { + type: "split-panel", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["BBB"], + }, + ], + sizes: [0.3, 0.7], + orientation: "vertical", + }, + { + type: "child-panel", + child: ["DDD"], + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + sizes: [0.3333333333333333, 0.3333333333333333, 0.3333333333333333], + orientation: "horizontal", }); +}); - test("insert at path splitting a child panel", () => { - const result = insert_child(TEST_PANEL, "DDD", [1, 1]); - expect(result).toStrictEqual({ - type: "split-panel", - children: [ - { - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - ], - sizes: [0.3, 0.7], - }, - { - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "child-panel", - child: ["CCC"], - }, - { - type: "child-panel", - child: ["DDD"], - }, - ], - sizes: [0.5, 0.5], - }, - ], - sizes: [0.6, 0.4], - orientation: "horizontal", - }); +test("insert at path splitting a child panel", () => { + const result = insert_child(TEST_PANEL, "DDD", [1, 1]); + expect(result).toStrictEqual({ + type: "split-panel", + children: [ + { + type: "split-panel", + orientation: "vertical", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["BBB"], + }, + ], + sizes: [0.3, 0.7], + }, + { + type: "split-panel", + orientation: "vertical", + children: [ + { + type: "child-panel", + child: ["CCC"], + }, + { + type: "child-panel", + child: ["DDD"], + }, + ], + sizes: [0.5, 0.5], + }, + ], + sizes: [0.6, 0.4], + orientation: "horizontal", }); +}); - test("insert into nested split panel", () => { - const result = insert_child(TEST_PANEL, "DDD", [0, 2]); - expect(result).toStrictEqual({ - type: "split-panel", - children: [ - { - type: "split-panel", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - { - type: "child-panel", - child: ["DDD"], - }, - ], - sizes: [0.3333333333333333, 0.3333333333333333, 0.3333333333333333], - orientation: "vertical", - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.6, 0.4], - orientation: "horizontal", - }); +test("insert into nested split panel", () => { + const result = insert_child(TEST_PANEL, "DDD", [0, 2]); + expect(result).toStrictEqual({ + type: "split-panel", + children: [ + { + type: "split-panel", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["BBB"], + }, + { + type: "child-panel", + child: ["DDD"], + }, + ], + sizes: [0.3333333333333333, 0.3333333333333333, 0.3333333333333333], + orientation: "vertical", + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + sizes: [0.6, 0.4], + orientation: "horizontal", }); +}); - test("split a nested child panel", () => { - const result = insert_child(TEST_PANEL, "DDD", [0, 0, 1]); - expect(result).toStrictEqual({ - type: "split-panel", - children: [ - { - type: "split-panel", - children: [ - { - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["DDD"], - }, - ], - sizes: [0.5, 0.5], - }, - { - type: "child-panel", - child: ["BBB"], - }, - ], - sizes: [0.3, 0.7], - orientation: "vertical", - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.6, 0.4], - orientation: "horizontal", - }); +test("split a nested child panel", () => { + const result = insert_child(TEST_PANEL, "DDD", [0, 0, 1]); + expect(result).toStrictEqual({ + type: "split-panel", + children: [ + { + type: "split-panel", + children: [ + { + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["DDD"], + }, + ], + sizes: [0.5, 0.5], + }, + { + type: "child-panel", + child: ["BBB"], + }, + ], + sizes: [0.3, 0.7], + orientation: "vertical", + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + sizes: [0.6, 0.4], + orientation: "horizontal", }); +}); - test("insert into single child panel", () => { - const singleChild: Layout = { - type: "child-panel", - child: ["ONLY"], - }; - - const result = insert_child(singleChild, "SECOND", [], "horizontal"); - expect(result).toStrictEqual({ - type: "child-panel", - child: ["SECOND", "ONLY"], - }); +test("insert into single child panel", () => { + const result = insert_child(LAYOUTS.SINGLE_ONLY, "SECOND", [], "horizontal"); + expect(result).toStrictEqual({ + type: "child-panel", + child: ["SECOND", "ONLY"], }); +}); - test("insert into single child panel, on the top edge", () => { - const singleChild: Layout = { - type: "child-panel", - child: ["ONLY"], - }; - - const result = insert_child(singleChild, "SECOND", [0], "vertical"); - expect(result).toStrictEqual({ - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "child-panel", - child: ["SECOND"], - }, - { - type: "child-panel", - child: ["ONLY"], - }, - ], - sizes: [0.5, 0.5], - }); +test("insert into single child panel, on the top edge", () => { + const result = insert_child(LAYOUTS.SINGLE_ONLY, "SECOND", [0], "vertical"); + expect(result).toStrictEqual({ + type: "split-panel", + orientation: "vertical", + children: [ + { + type: "child-panel", + child: ["SECOND"], + }, + { + type: "child-panel", + child: ["ONLY"], + }, + ], + sizes: [0.5, 0.5], }); +}); - test("insert into single child panel, on the left edge", () => { - const singleChild: Layout = { - type: "child-panel", - child: ["ONLY"], - }; - - const result = insert_child(singleChild, "SECOND", [0], "horizontal"); - expect(result).toStrictEqual({ - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "child-panel", - child: ["SECOND"], - }, - { - type: "child-panel", - child: ["ONLY"], - }, - ], - sizes: [0.5, 0.5], - }); +test("insert into single child panel, on the left edge", () => { + const result = insert_child(LAYOUTS.SINGLE_ONLY, "SECOND", [0], "horizontal"); + expect(result).toStrictEqual({ + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "child-panel", + child: ["SECOND"], + }, + { + type: "child-panel", + child: ["ONLY"], + }, + ], + sizes: [0.5, 0.5], }); +}); - test("insert into single child panel, on the bottom edge", () => { - const singleChild: Layout = { - type: "child-panel", - child: ["ONLY"], - }; - - const result = insert_child(singleChild, "SECOND", [1], "vertical"); - expect(result).toStrictEqual({ - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "child-panel", - child: ["ONLY"], - }, - { - type: "child-panel", - child: ["SECOND"], - }, - ], - sizes: [0.5, 0.5], - }); +test("insert into single child panel, on the bottom edge", () => { + const result = insert_child(LAYOUTS.SINGLE_ONLY, "SECOND", [1], "vertical"); + expect(result).toStrictEqual({ + type: "split-panel", + orientation: "vertical", + children: [ + { + type: "child-panel", + child: ["ONLY"], + }, + { + type: "child-panel", + child: ["SECOND"], + }, + ], + sizes: [0.5, 0.5], }); +}); - test("insert into single child panel, on the right edge", () => { - const singleChild: Layout = { - type: "child-panel", - child: ["ONLY"], - }; - - const result = insert_child(singleChild, "SECOND", [1], "horizontal"); - expect(result).toStrictEqual({ - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "child-panel", - child: ["ONLY"], - }, - { - type: "child-panel", - child: ["SECOND"], - }, - ], - sizes: [0.5, 0.5], - }); +test("insert into single child panel, on the right edge", () => { + const result = insert_child(LAYOUTS.SINGLE_ONLY, "SECOND", [1], "horizontal"); + expect(result).toStrictEqual({ + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "child-panel", + child: ["ONLY"], + }, + { + type: "child-panel", + child: ["SECOND"], + }, + ], + sizes: [0.5, 0.5], }); +}); - test("insert into a child-panel root, on the top edge", () => { - const panel: Layout = { type: "child-panel", child: ["AAA"] }; - const result = insert_child(panel, "BBB", [0], "vertical"); - expect(result).toStrictEqual({ - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "child-panel", - child: ["BBB"], - }, - { - type: "child-panel", - child: ["AAA"], - }, - ], - sizes: [0.5, 0.5], - }); +test("insert into a child-panel root, on the top edge", () => { + const result = insert_child(LAYOUTS.SINGLE_AAA, "BBB", [0], "vertical"); + expect(result).toStrictEqual({ + type: "split-panel", + orientation: "vertical", + children: [ + { + type: "child-panel", + child: ["BBB"], + }, + { + type: "child-panel", + child: ["AAA"], + }, + ], + sizes: [0.5, 0.5], }); }); diff --git a/tests/unit/redistribute_panel_sizes.spec.ts b/tests/unit/redistribute_panel_sizes.spec.ts index ac41f29..a423860 100644 --- a/tests/unit/redistribute_panel_sizes.spec.ts +++ b/tests/unit/redistribute_panel_sizes.spec.ts @@ -10,513 +10,310 @@ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ import { expect, test } from "@playwright/test"; -import { TEST_PANEL } from "../fixtures.ts"; +import { TEST_PANEL, LAYOUTS } from "../helpers/fixtures.ts"; import { redistribute_panel_sizes } from "../../src/common/redistribute_panel_sizes.ts"; -import type { Layout } from "../../src/common/layout_config.ts"; -test.describe("redistribute_panel_sizes", async () => { - test("redistribute depth 1 child", () => { - const clone = redistribute_panel_sizes( - structuredClone(TEST_PANEL), - [0], - 0.1, - ); +test("redistribute depth 1 child", () => { + const clone = redistribute_panel_sizes(structuredClone(TEST_PANEL), [0], 0.1); - expect(clone).toStrictEqual({ - type: "split-panel", - children: [ - { - type: "split-panel", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - ], - sizes: [0.3, 0.7], - orientation: "vertical", - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.5, 0.5], - orientation: "horizontal", - }); + expect(clone).toStrictEqual({ + type: "split-panel", + children: [ + { + type: "split-panel", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["BBB"], + }, + ], + sizes: [0.3, 0.7], + orientation: "vertical", + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + sizes: [0.5, 0.5], + orientation: "horizontal", }); +}); - test("redistribute depth 2 children", () => { - const clone = redistribute_panel_sizes(TEST_PANEL, [0, 0], 0.1); - expect(clone).toStrictEqual({ - type: "split-panel", - children: [ - { - type: "split-panel", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - ], - sizes: [0.19999999999999998, 0.7999999999999999], - orientation: "vertical", - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.6, 0.4], - orientation: "horizontal", - }); +test("redistribute depth 2 children", () => { + const clone = redistribute_panel_sizes(TEST_PANEL, [0, 0], 0.1); + expect(clone).toStrictEqual({ + type: "split-panel", + children: [ + { + type: "split-panel", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["BBB"], + }, + ], + sizes: [0.19999999999999998, 0.7999999999999999], + orientation: "vertical", + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + sizes: [0.6, 0.4], + orientation: "horizontal", }); +}); - test("redistribute with 3 children", () => { - const TEST2: Layout = { - type: "split-panel", - children: [ - { - type: "split-panel", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - { - type: "child-panel", - child: ["DDD"], - }, - ], - sizes: [0.3, 0.6, 0.1], - orientation: "vertical", - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.6, 0.4], - orientation: "horizontal", - }; - const clone = redistribute_panel_sizes(TEST2, [0, 0], 0.1); - expect(clone).toStrictEqual({ - type: "split-panel", - children: [ - { - type: "split-panel", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - { - type: "child-panel", - child: ["DDD"], - }, - ], - sizes: [0.19999999999999998, 0.6857142857142857, 0.1142857142857143], - orientation: "vertical", - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.6, 0.4], - orientation: "horizontal", - }); +test("redistribute with 3 children", () => { + const clone = redistribute_panel_sizes( + LAYOUTS.NESTED_HORIZONTAL_WITH_VERTICAL_DDD, + [0, 0], + 0.1, + ); + expect(clone).toStrictEqual({ + type: "split-panel", + children: [ + { + type: "split-panel", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["BBB"], + }, + { + type: "child-panel", + child: ["DDD"], + }, + ], + sizes: [0.19999999999999998, 0.6857142857142857, 0.1142857142857143], + orientation: "vertical", + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + sizes: [0.6, 0.4], + orientation: "horizontal", }); +}); - test("redistribute nested spec with 4 children", () => { - const test: Layout = { - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "child-panel", - child: ["EEE"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - { - type: "child-panel", - child: ["DDD"], - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.25, 0.25, 0.25, 0.25], - }, - ], - sizes: [0.5, 0.5], - }, - { - type: "child-panel", - child: ["FFF"], - }, - ], - sizes: [0.5, 0.5], - }; - - const clone = redistribute_panel_sizes(test, [0, 1, 1], 0.1); - expect(clone).toStrictEqual({ - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "child-panel", - child: ["EEE"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - { - type: "child-panel", - child: ["DDD"], - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.2, 0.2, 0.3, 0.3], - }, - ], - sizes: [0.5, 0.5], - }, - { - type: "child-panel", - child: ["FFF"], - }, - ], - sizes: [0.5, 0.5], - }); +test("redistribute nested spec with 4 children", () => { + const clone = redistribute_panel_sizes( + LAYOUTS.COMPLEX_FOUR_CHILDREN, + [0, 1, 1], + 0.1, + ); + expect(clone).toStrictEqual({ + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "split-panel", + orientation: "vertical", + children: [ + { + type: "child-panel", + child: ["EEE"], + }, + { + type: "child-panel", + child: ["BBB"], + }, + { + type: "child-panel", + child: ["DDD"], + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + sizes: [0.2, 0.2, 0.3, 0.3], + }, + ], + sizes: [0.5, 0.5], + }, + { + type: "child-panel", + child: ["FFF"], + }, + ], + sizes: [0.5, 0.5], }); +}); - test("nested aligned splitpanels", () => { - const test: Layout = { - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - ], - sizes: [0.5, 0.5], - }, - { - type: "child-panel", - child: ["DDD"], - }, - ], - sizes: [0.5, 0.5], - }, - { - type: "child-panel", - child: ["FFF"], - }, - ], - sizes: [0.5, 0.5], - }; - - const clone = redistribute_panel_sizes(test, [0, 0, 0], 0.1); - expect(clone).toStrictEqual({ - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - ], - sizes: [0.3, 0.7], - }, - { - type: "child-panel", - child: ["DDD"], - }, - ], - sizes: [0.5, 0.5], - }, - { - type: "child-panel", - child: ["FFF"], - }, - ], - sizes: [0.5, 0.5], - }); +test("nested aligned splitpanels", () => { + const clone = redistribute_panel_sizes(LAYOUTS.NESTED_ALIGNED, [0, 0, 0], 0.1); + expect(clone).toStrictEqual({ + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "split-panel", + orientation: "vertical", + children: [ + { + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["BBB"], + }, + ], + sizes: [0.3, 0.7], + }, + { + type: "child-panel", + child: ["DDD"], + }, + ], + sizes: [0.5, 0.5], + }, + { + type: "child-panel", + child: ["FFF"], + }, + ], + sizes: [0.5, 0.5], }); +}); - test("nested aligned splitpanels, reversed orientation", () => { - const test: Layout = { - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "child-panel", - child: ["BBB"], - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.5, 0.5], - }, - ], - sizes: [0.5, 0.5], - }, - { - type: "child-panel", - child: ["DDD"], - }, - ], - sizes: [0.5, 0.5], - }, - { - type: "child-panel", - child: ["FFF"], - }, - ], - sizes: [0.5, 0.5], - }; - - const clone = redistribute_panel_sizes(test, [0, 0, 1, 0], 0.1); - expect(clone).toStrictEqual({ - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "child-panel", - child: ["BBB"], - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.3, 0.7], - }, - ], - sizes: [0.5, 0.5], - }, - { - type: "child-panel", - child: ["DDD"], - }, - ], - sizes: [0.5, 0.5], - }, - { - type: "child-panel", - child: ["FFF"], - }, - ], - sizes: [0.5, 0.5], - }); +test("nested aligned splitpanels, reversed orientation", () => { + const clone = redistribute_panel_sizes( + LAYOUTS.NESTED_ALIGNED_REVERSED, + [0, 0, 1, 0], + 0.1, + ); + expect(clone).toStrictEqual({ + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "split-panel", + orientation: "vertical", + children: [ + { + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "split-panel", + orientation: "vertical", + children: [ + { + type: "child-panel", + child: ["BBB"], + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + sizes: [0.3, 0.7], + }, + ], + sizes: [0.5, 0.5], + }, + { + type: "child-panel", + child: ["DDD"], + }, + ], + sizes: [0.5, 0.5], + }, + { + type: "child-panel", + child: ["FFF"], + }, + ], + sizes: [0.5, 0.5], }); +}); - test("nested aligned splitpanels, reversed orientation with asymmetric sizes", () => { - const test: Layout = { - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "child-panel", - child: ["BBB"], - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.5, 0.5], - }, - ], - sizes: [0.5, 0.5], - }, - { - type: "child-panel", - child: ["DDD"], - }, - ], - sizes: [0.75, 0.25], - }, - { - type: "child-panel", - child: ["FFF"], - }, - ], - sizes: [0.5, 0.5], - }; - - const clone = redistribute_panel_sizes(test, [0, 0, 1, 0], 0.1); - expect(clone).toStrictEqual({ - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "split-panel", - orientation: "horizontal", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "split-panel", - orientation: "vertical", - children: [ - { - type: "child-panel", - child: ["BBB"], - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.3666666666666667, 0.6333333333333333], - }, - ], - sizes: [0.5, 0.5], - }, - { - type: "child-panel", - child: ["DDD"], - }, - ], - sizes: [0.75, 0.25], - }, - { - type: "child-panel", - child: ["FFF"], - }, - ], - sizes: [0.5, 0.5], - }); +test("nested aligned splitpanels, reversed orientation with asymmetric sizes", () => { + const clone = redistribute_panel_sizes( + LAYOUTS.NESTED_ALIGNED_ASYMMETRIC, + [0, 0, 1, 0], + 0.1, + ); + expect(clone).toStrictEqual({ + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "split-panel", + orientation: "vertical", + children: [ + { + type: "split-panel", + orientation: "horizontal", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "split-panel", + orientation: "vertical", + children: [ + { + type: "child-panel", + child: ["BBB"], + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + sizes: [0.3666666666666667, 0.6333333333333333], + }, + ], + sizes: [0.5, 0.5], + }, + { + type: "child-panel", + child: ["DDD"], + }, + ], + sizes: [0.75, 0.25], + }, + { + type: "child-panel", + child: ["FFF"], + }, + ], + sizes: [0.5, 0.5], }); }); diff --git a/tests/unit/remove_child.spec.ts b/tests/unit/remove_child.spec.ts index b053a8f..87c202c 100644 --- a/tests/unit/remove_child.spec.ts +++ b/tests/unit/remove_child.spec.ts @@ -10,167 +10,107 @@ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ import { expect, test } from "@playwright/test"; -import { TEST_PANEL } from "../fixtures.ts"; +import { TEST_PANEL, LAYOUTS } from "../helpers/fixtures.ts"; import { remove_child } from "../../src/common/remove_child.ts"; -import type { Layout } from "../../src/common/layout_config.ts"; -test.describe("remove_child", async () => { - test("remove child from nested split panel", () => { - const result = remove_child(TEST_PANEL, "AAA"); - expect(result).toStrictEqual({ - type: "split-panel", - children: [ - { - type: "child-panel", - child: ["BBB"], - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.6, 0.4], - orientation: "horizontal", - }); - }); - - test("remove child from top-level split panel", () => { - const result = remove_child(TEST_PANEL, "CCC"); - expect(result).toStrictEqual({ - type: "split-panel", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - ], - sizes: [0.3, 0.7], - orientation: "vertical", - }); +test("remove child from nested split panel", () => { + const result = remove_child(TEST_PANEL, "AAA"); + expect(result).toStrictEqual({ + type: "split-panel", + children: [ + { + type: "child-panel", + child: ["BBB"], + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + sizes: [0.6, 0.4], + orientation: "horizontal", }); +}); - test("remove child from top-level tab panel", () => { - const result = remove_child( +test("remove child from top-level split panel", () => { + const result = remove_child(TEST_PANEL, "CCC"); + expect(result).toStrictEqual({ + type: "split-panel", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, { type: "child-panel", - child: ["AAA", "CCC"], + child: ["BBB"], }, - "AAA", - ); + ], + sizes: [0.3, 0.7], + orientation: "vertical", + }); +}); - expect(result).toStrictEqual({ +test("remove child from top-level tab panel", () => { + const result = remove_child( + { type: "child-panel", - child: ["CCC"], - }); - }); + child: ["AAA", "CCC"], + }, + "AAA", + ); - test("remove child from split panel with 3 children", () => { - const TEST3: Layout = { - type: "split-panel", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.2, 0.3, 0.5], - orientation: "horizontal", - }; + expect(result).toStrictEqual({ + type: "child-panel", + child: ["CCC"], + }); +}); - const result = remove_child(TEST3, "BBB"); - expect(result).toStrictEqual({ - type: "split-panel", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.28571428571428575, 0.7142857142857143], - orientation: "horizontal", - }); +test("remove child from split panel with 3 children", () => { + const result = remove_child(LAYOUTS.THREE_HORIZONTAL_CUSTOM, "BBB"); + expect(result).toStrictEqual({ + type: "split-panel", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + sizes: [0.28571428571428575, 0.7142857142857143], + orientation: "horizontal", }); +}); - test("remove deeply nested child", () => { - const DEEP_TEST: Layout = { - type: "split-panel", - children: [ - { - type: "split-panel", - children: [ - { - type: "split-panel", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["BBB"], - }, - ], - sizes: [0.4, 0.6], - orientation: "vertical", - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.5, 0.5], - orientation: "horizontal", - }, - { - type: "child-panel", - child: ["DDD"], - }, - ], - sizes: [0.7, 0.3], - orientation: "vertical", - }; - const result = remove_child(DEEP_TEST, "BBB"); - expect(result).toStrictEqual({ - type: "split-panel", - children: [ - { - type: "split-panel", - children: [ - { - type: "child-panel", - child: ["AAA"], - }, - { - type: "child-panel", - child: ["CCC"], - }, - ], - sizes: [0.5, 0.5], - orientation: "horizontal", - }, - { - type: "child-panel", - child: ["DDD"], - }, - ], - sizes: [0.7, 0.3], - orientation: "vertical", - }); +test("remove deeply nested child", () => { + const result = remove_child(LAYOUTS.DEEPLY_NESTED_ALT, "BBB"); + expect(result).toStrictEqual({ + type: "split-panel", + children: [ + { + type: "split-panel", + children: [ + { + type: "child-panel", + child: ["AAA"], + }, + { + type: "child-panel", + child: ["CCC"], + }, + ], + sizes: [0.5, 0.5], + orientation: "horizontal", + }, + { + type: "child-panel", + child: ["DDD"], + }, + ], + sizes: [0.7, 0.3], + orientation: "vertical", }); });