paperclip/tests/e2e/sidebar-takeover.spec.ts

113 lines
4.6 KiB
TypeScript

import { test, expect, request as pwRequest, type APIRequestContext } from "@playwright/test";
/**
* E2E: contextual sidebar companion model.
*
* Most contextual routes render their navigation beside the stable global
* sidebar. Settings intentionally takes over that sidebar while preserving the
* account menu, and its Back to app link restores the global navigation.
*
* Plugin route sidebars share the same Layout path. A live plugin-route test
* requires a plugin fixture, so that branch remains covered by Layout tests.
*/
const PORT = Number(process.env.PAPERCLIP_E2E_PORT ?? 3199);
const BASE_URL = `http://127.0.0.1:${PORT}`;
const COMPANY_NAME_PREFIX = "E2E-SidebarTakeover";
const COLLAPSED_STORAGE_KEY = "paperclip.sidebar.collapsed";
const APP_SIDEBAR_EXPANDED_MARKER = "Collapse sidebar";
async function createCompany(board: APIRequestContext): Promise<{ id: string; prefix: string }> {
const healthRes = await board.get(`${BASE_URL}/api/health`);
expect(healthRes.ok()).toBe(true);
const health = await healthRes.json();
expect(health.deploymentMode).toBe("local_trusted");
const companyRes = await board.post(`${BASE_URL}/api/companies`, {
data: { name: `${COMPANY_NAME_PREFIX}-${Date.now()}` },
});
if (!companyRes.ok()) {
throw new Error(`POST /api/companies → ${companyRes.status()}: ${await companyRes.text()}`);
}
const company = await companyRes.json();
return {
id: company.id,
prefix: company.issuePrefix ?? company.prefix ?? company.urlKey ?? "E2E",
};
}
test.describe("Contextual sidebar companion", () => {
let board: APIRequestContext;
let companyId: string;
let prefix: string;
test.beforeAll(async () => {
board = await pwRequest.newContext({ baseURL: BASE_URL });
const company = await createCompany(board);
companyId = company.id;
prefix = company.prefix;
});
test.afterAll(async () => {
await board.delete(`${BASE_URL}/api/companies/${companyId}`).catch(() => {});
await board.dispose();
});
test.beforeEach(async ({ page }) => {
await page.addInitScript((key) => {
window.localStorage.removeItem(key);
window.sessionStorage.clear();
}, COLLAPSED_STORAGE_KEY);
});
test("replaces global navigation with Settings navigation", async ({ page }) => {
await page.goto(`/${prefix}/company/settings`);
const contextual = page.locator('[data-contextual-sidebar="settings"]');
await expect(contextual).toBeVisible();
await expect(contextual).toHaveCount(1);
await expect(page.locator("[data-secondary-sidebar]")).toHaveCount(1);
await expect(contextual.getByRole("link", { name: "General" })).toBeVisible();
await expect(contextual.getByText("Environments", { exact: true })).toBeVisible();
await expect(contextual.getByRole("link", { name: "Back to app" })).toBeVisible();
await expect(page.getByRole("button", { name: "Open account menu" })).toBeVisible();
await expect(page.getByRole("link", { name: "Dashboard" })).toHaveCount(0);
await expect(page.getByLabel(APP_SIDEBAR_EXPANDED_MARKER)).toHaveCount(0);
});
test("renders contextual labels at full width", async ({ page }) => {
await page.goto(`/${prefix}/company/settings`);
const contextual = page.locator('[data-contextual-sidebar="settings"]');
const envLabel = contextual.getByText("Environments", { exact: true });
await expect(envLabel).toBeVisible();
const labelBox = await envLabel.boundingBox();
expect(labelBox).not.toBeNull();
expect(labelBox!.width).toBeGreaterThan(20);
});
test("keeps the retired collapse control absent across contextual navigation", async ({ page }) => {
await page.goto(`/${prefix}/company/settings`);
await expect(page.locator('[data-contextual-sidebar="settings"]')).toBeVisible();
await expect(page.getByRole("link", { name: "Back to app" })).toBeVisible();
await expect(page.getByLabel(APP_SIDEBAR_EXPANDED_MARKER)).toHaveCount(0);
await page.goto(`/${prefix}/dashboard`);
await expect(page.locator("[data-contextual-sidebar]")).toHaveCount(0);
await expect(page.getByRole("link", { name: "Dashboard" })).toBeVisible();
await expect(page.getByLabel(APP_SIDEBAR_EXPANDED_MARKER)).toHaveCount(0);
});
test("uses Dashboard as the destination for a direct Settings link", async ({ page }) => {
await page.goto(`/${prefix}/company/settings`);
await page.getByRole("link", { name: "Back to app" }).click();
await expect(page).toHaveURL(new RegExp(`/${prefix}/dashboard$`));
await expect(page.locator("[data-contextual-sidebar]")).toHaveCount(0);
await expect(page.getByRole("link", { name: "Dashboard" })).toBeVisible();
});
});