Files
galaxy-game/ui/frontend/tests/e2e/game-shell.spec.ts
T
Ilia Denisov 4e0058d46c test(ui): migrate suite to the app-shell (state-driven navigation)
- Unit: repoint moved screen imports (lib/screens, lib/game), mock
  $lib/app-nav (appScreen/activeView) instead of $app/navigation, drop the
  removed gameId props, assert screen/view selection.
- e2e: add a dev-only window.__galaxyNav affordance; specs enter a game via
  enterGame(...) instead of a /games/:id URL; URL assertions become content
  assertions (the URL stays /game/); reload uses waitUntil:"commit" (shallow
  routing) and mocks /rpc on game entry.
- Remove the obsolete report scroll-restore test (it relied on a SvelteKit
  route Snapshot that no longer exists); update the missing-membership test
  to the new lobby-redirect+toast behaviour. Fix a stale report.svelte
  docstring.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 20:49:35 +02:00

223 lines
8.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Phase 10 end-to-end coverage for the in-game shell. Every spec
// boots an authenticated session through `/__debug/store` (the
// in-game shell makes a handful of gateway calls — for the lobby
// record, the report, and the order read-back; we don't mock them
// here, the shell tolerates ECONNREFUSED), enters the game through
// the dev-only `window.__galaxyNav` affordance (the single-URL
// app-shell has no `/games/<id>/<view>` route — the address bar
// stays at the app base), 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";
// `window.__galaxyDebug` is owned by `src/routes/__debug/store/+page.svelte`
// (auth bootstrap) and `window.__galaxyNav` by `src/routes/+page.svelte`
// (dev-only screen/view driver); both are typed by
// `tests/e2e/storage-keypair-persistence.spec.ts`.
const SESSION_ID = "phase-10-shell-session";
// GAME_ID has to be a real UUID — Phase 14's auto-sync calls
// `uuidToHiLo` on it for the FBS request envelope, and an
// arbitrary string would throw on every layout boot.
const GAME_ID = "10101010-1010-1010-1010-101010101010";
async function bootShell(page: Page): Promise<void> {
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,
);
// Load the app (seeded session → authenticated → lobby), then enter
// the game via the in-memory nav affordance.
await page.goto("/");
await page.waitForFunction(() => window.__galaxyNav !== undefined);
await page.evaluate(
(id) => window.__galaxyNav!.enterGame(id, "map", {}),
GAME_ID,
);
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);
// The address bar stays at the app base in the single-URL app-shell,
// so the visible active view is the only navigation signal to assert.
const destinations: Array<[string, string]> = [
["view-menu-item-report", "active-view-report"],
["view-menu-item-mail", "active-view-mail"],
["view-menu-item-battle", "active-view-battle"],
["view-menu-item-designer-science", "active-view-designer-science"],
["view-menu-item-map", "active-view-map"],
];
for (const [trigger, viewTestId] of destinations) {
await page.getByTestId("view-menu-trigger").click();
await page.getByTestId(trigger).click();
await expect(page.getByTestId(viewTestId)).toBeVisible();
}
});
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);
}
});
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 7681024: 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();
});