Files
paseo/packages/app/e2e/support/helpers/split-pane.ts
T
Mohamed BoudraandGitHub ab274d635e Make the workspace right pane a first-class tab host (#3287)
* feat(app): add persistent workspace side panes

Keep the common review and file-browsing workflow beside the active agent without tearing down pane contents. Workspace tabs now share one compact chip language, assistant file links route into the retained right pane, and diff/file trees share stable geometry.

* fix(app): localize pull request empty state

* test(app): update explorer pane coverage

* fix(app): preserve explorer pane workflows

* test(app): make explorer tab selection idempotent

* fix(app): persist workspace review pins

* fix(app): preserve sidebar scrim drag hook

* fix(app): restore companion pane interactions

* test(app): follow selected sidebar surface
2026-08-16 18:18:32 +02:00

85 lines
3.2 KiB
TypeScript

import { expect, type Locator, type Page } from "@playwright/test";
export interface SplitPaneGeometry {
containerWidth: number;
explorerWidth: number;
visibleWidth: number;
separatorCount: number;
occupiedWidth: number;
}
function splitSeparator(page: Page): Locator {
return page.getByRole("separator", { name: "" });
}
export async function openExplorerSplit(page: Page): Promise<void> {
await page.getByRole("button", { name: "Open explorer" }).click();
await expect(splitSeparator(page)).toHaveCount(1, { timeout: 30_000 });
}
export async function hideExplorerSplit(page: Page): Promise<void> {
await page.getByRole("button", { name: "Close explorer" }).click();
await expect(splitSeparator(page)).toHaveCount(0, { timeout: 30_000 });
}
export async function resizeExplorerSplit(page: Page, deltaX: number): Promise<void> {
const separator = splitSeparator(page);
await expect(separator).toHaveCount(1);
const box = await separator.boundingBox();
expect(box).not.toBeNull();
if (!box) throw new Error("Split separator has no bounding box");
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
await page.mouse.down();
await page.mouse.move(box.x + box.width / 2 + deltaX, box.y + box.height / 2, { steps: 10 });
await page.mouse.up();
}
export async function stampExplorerNode(page: Page): Promise<void> {
await page
.getByTestId("split-group-child")
.last()
.evaluate((node) => {
Reflect.set(window, "__splitPaneExplorerNode", node);
node.setAttribute("data-split-pane-identity", "retained");
});
}
export async function expectExplorerNodeRetained(page: Page): Promise<void> {
const stampedNode = page.locator('[data-split-pane-identity="retained"]');
await expect(stampedNode).toHaveCount(1);
expect(
await stampedNode.evaluate((node) => Reflect.get(window, "__splitPaneExplorerNode") === node),
).toBe(true);
}
export async function measureExplorerSplit(page: Page): Promise<SplitPaneGeometry> {
const explorerChild = page.locator('[data-split-pane-identity="retained"]');
await expect(explorerChild).toHaveCount(1);
return explorerChild.evaluate((explorerNode) => {
const visibleNode = Array.from(explorerNode.parentElement?.children ?? [])
.filter(
(candidate) =>
candidate !== explorerNode &&
(candidate as HTMLElement).dataset.testid === "split-group-child",
)
.sort(
(left, right) => right.getBoundingClientRect().width - left.getBoundingClientRect().width,
)[0];
if (!(visibleNode instanceof HTMLElement)) throw new Error("Visible split child not found");
const visible = visibleNode.getBoundingClientRect();
const explorer = explorerNode.getBoundingClientRect();
const container = explorerNode.parentElement?.getBoundingClientRect();
if (!container) throw new Error("Split child has no container");
const separatorCount =
explorerNode.parentElement?.querySelectorAll('[role="separator"]').length ?? 0;
return {
containerWidth: container.width,
explorerWidth: explorer.width,
visibleWidth: visible.width,
separatorCount,
occupiedWidth: explorer.width + visible.width,
};
});
}