// Phase 10 end-to-end coverage for the in-game shell. Every spec // boots an authenticated session through `/__debug/store` (no // gateway calls — the shell makes none in Phase 10), navigates into // `/games/test-shell/map`, and exercises one slice of the chrome: // header navigation, sidebar tab preservation, mobile bottom-tabs, // and the breakpoint switches at 768 / 1024 px. import { expect, test, type Page } from "@playwright/test"; // The `window.__galaxyDebug` surface is owned by // `src/routes/__debug/store/+page.svelte` and typed by // `tests/e2e/storage-keypair-persistence.spec.ts`. This spec only // needs the auth-bootstrap subset (`clearSession`, // `setDeviceSessionId`); the merged global declaration covers both. const SESSION_ID = "phase-10-shell-session"; const GAME_ID = "test-shell"; async function bootShell(page: Page): Promise { await page.goto("/__debug/store"); await expect(page.getByTestId("debug-store-ready")).toBeVisible(); await page.waitForFunction(() => window.__galaxyDebug?.ready === true); await page.evaluate(() => window.__galaxyDebug!.clearSession()); await page.evaluate( (id) => window.__galaxyDebug!.setDeviceSessionId(id), SESSION_ID, ); await page.goto(`/games/${GAME_ID}/map`); await expect(page.getByTestId("game-shell")).toBeVisible(); await expect(page.getByTestId("active-view-map")).toBeVisible(); } test("shell mounts with header / sidebar / active-view chrome", async ({ page, }) => { await bootShell(page); await expect(page.getByTestId("game-shell-header")).toBeVisible(); await expect(page.getByTestId("game-shell-headline")).toContainText( "turn", ); await expect(page.getByTestId("view-menu-trigger")).toBeVisible(); await expect(page.getByTestId("account-menu-trigger")).toBeVisible(); }); test("header view-menu navigates to every active view", async ({ page }) => { await bootShell(page); const destinations: Array<[string, string, string]> = [ ["view-menu-item-report", "active-view-report", "/report"], ["view-menu-item-mail", "active-view-mail", "/mail"], ["view-menu-item-battle", "active-view-battle", "/battle"], [ "view-menu-item-designer-ship-class", "active-view-designer-ship-class", "/designer/ship-class", ], [ "view-menu-item-designer-science", "active-view-designer-science", "/designer/science", ], ["view-menu-item-map", "active-view-map", "/map"], ]; for (const [trigger, viewTestId, urlSuffix] of destinations) { await page.getByTestId("view-menu-trigger").click(); await page.getByTestId(trigger).click(); await expect(page.getByTestId(viewTestId)).toBeVisible(); await expect(page).toHaveURL(new RegExp(`/games/${GAME_ID}${urlSuffix}$`)); } }); test("header view-menu Tables sub-list navigates to every entity", async ({ page, }) => { await bootShell(page); const entities = [ "planets", "ship-classes", "ship-groups", "fleets", "sciences", "races", ]; for (const entity of entities) { await page.getByTestId("view-menu-trigger").click(); await page .getByTestId("view-menu-tables") .locator("summary") .click(); await page.getByTestId(`view-menu-item-table-${entity}`).click(); const view = page.getByTestId("active-view-table"); await expect(view).toBeVisible(); await expect(view).toHaveAttribute("data-entity", entity); await expect(page).toHaveURL( new RegExp(`/games/${GAME_ID}/table/${entity}$`), ); } }); test("sidebar tab choice survives navigation between active views", async ({ page, browserName, }, testInfo) => { test.skip( testInfo.project.name.startsWith("chromium-mobile") || testInfo.project.name === "webkit-desktop" ? false : false, "sidebar test runs on every project", ); await bootShell(page); // Skip on viewports below 1024 — sidebar is hidden by CSS there. const viewport = page.viewportSize(); if (viewport === null || viewport.width < 1024) { test.skip(); return; } void browserName; await page.getByTestId("sidebar-tab-calculator").click(); await expect(page.getByTestId("sidebar-tool-calculator")).toBeVisible(); await page.getByTestId("view-menu-trigger").click(); await page.getByTestId("view-menu-item-report").click(); await expect(page.getByTestId("active-view-report")).toBeVisible(); // Sidebar still rendered; the calculator tool remains selected. await expect(page.getByTestId("sidebar-tool-calculator")).toBeVisible(); await expect(page.getByTestId("sidebar")).toHaveAttribute( "data-active-tab", "calculator", ); await page.getByTestId("view-menu-trigger").click(); await page.getByTestId("view-menu-item-map").click(); await expect(page.getByTestId("active-view-map")).toBeVisible(); await expect(page.getByTestId("sidebar")).toHaveAttribute( "data-active-tab", "calculator", ); }); test("mobile bottom-tabs show on small viewports and toggle the tool overlay", async ({ page, }, testInfo) => { if (!testInfo.project.name.startsWith("chromium-mobile")) { test.skip(); return; } await bootShell(page); await expect(page.getByTestId("bottom-tabs")).toBeVisible(); await expect(page.getByTestId("sidebar")).not.toBeVisible(); await page.getByTestId("bottom-tab-calc").click(); await expect(page.getByTestId("sidebar-tool-calculator")).toBeVisible(); await page.getByTestId("bottom-tab-order").click(); await expect(page.getByTestId("sidebar-tool-order")).toBeVisible(); await page.getByTestId("bottom-tab-map").click(); await expect(page.getByTestId("active-view-map")).toBeVisible(); }); test("mobile More drawer navigates to every destination", async ({ page, }, testInfo) => { if (!testInfo.project.name.startsWith("chromium-mobile")) { test.skip(); return; } await bootShell(page); await page.getByTestId("bottom-tab-more").click(); await expect(page.getByTestId("bottom-tabs-more-drawer")).toBeVisible(); await page.getByTestId("bottom-tabs-more-mail").click(); await expect(page.getByTestId("active-view-mail")).toBeVisible(); await page.getByTestId("bottom-tab-more").click(); await page.getByTestId("bottom-tabs-more-report").click(); await expect(page.getByTestId("active-view-report")).toBeVisible(); }); test("breakpoint switches between desktop / tablet / mobile", async ({ page, }, testInfo) => { // Use a single chromium-desktop run to drive all three viewports in // the same browser. Other projects skip — the viewport diff is the // goal here, not browser-specific behaviour. if (testInfo.project.name !== "chromium-desktop") { test.skip(); return; } await bootShell(page); // Desktop ≥ 1024: sidebar visible, bottom-tabs hidden, sidebar // toggle hidden. await page.setViewportSize({ width: 1280, height: 800 }); await expect(page.getByTestId("sidebar")).toBeVisible(); await expect(page.getByTestId("bottom-tabs")).not.toBeVisible(); await expect(page.getByTestId("sidebar-toggle")).not.toBeVisible(); // Tablet 768–1024: sidebar hidden by default, sidebar toggle // visible, bottom-tabs hidden. Click the toggle and the sidebar // becomes visible again. await page.setViewportSize({ width: 900, height: 800 }); await expect(page.getByTestId("sidebar")).not.toBeVisible(); await expect(page.getByTestId("sidebar-toggle")).toBeVisible(); await expect(page.getByTestId("bottom-tabs")).not.toBeVisible(); await page.getByTestId("sidebar-toggle").click(); await expect(page.getByTestId("sidebar")).toBeVisible(); // Mobile < 768: sidebar hidden entirely, bottom-tabs visible, // sidebar toggle hidden again. await page.setViewportSize({ width: 390, height: 800 }); await expect(page.getByTestId("bottom-tabs")).toBeVisible(); await expect(page.getByTestId("sidebar")).not.toBeVisible(); await expect(page.getByTestId("sidebar-toggle")).not.toBeVisible(); });