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>
This commit is contained in:
Ilia Denisov
2026-05-23 20:49:35 +02:00
parent 80545e9f9d
commit 4e0058d46c
36 changed files with 707 additions and 343 deletions
+32 -62
View File
@@ -238,7 +238,12 @@ test.describe("Phase 23 report view", () => {
await mockGateway(page);
await bootSession(page);
await page.goto(`/games/${GAME_ID}/report`);
await page.goto("/");
await page.waitForFunction(() => window.__galaxyNav !== undefined);
await page.evaluate(
(id) => window.__galaxyNav!.enterGame(id, "report", {}),
GAME_ID,
);
await expect(page.getByTestId("active-view-report")).toBeVisible();
await expect(page.getByTestId("report-toc")).toBeVisible();
@@ -265,65 +270,19 @@ test.describe("Phase 23 report view", () => {
}
});
test("scroll position survives a /map round-trip via Snapshot", async ({
page,
}, testInfo) => {
test.skip(
testInfo.project.name.startsWith("chromium-mobile"),
"snapshot mechanism is the same on mobile; one project is enough",
);
// NOTE: the old "scroll position survives a /map round-trip via
// Snapshot" spec was dropped here. It exercised the per-route
// SvelteKit `Snapshot` exported by the deleted
// `routes/games/[id]/report/+page.svelte`, which captured and
// restored `window.scrollY` across a browser history navigation to
// `/map` and back. The single-URL app-shell switches the active view
// in memory (`activeView.select`) without changing the URL or pushing
// a history entry, and it remounts the report component on return —
// so neither the URL round-trip, the `page.goBack()`, nor the
// scroll-restoration the test asserted exist any more. Re-adding that
// behaviour would be a production change outside this test migration.
await mockGateway(page);
await bootSession(page);
await page.goto(`/games/${GAME_ID}/report`);
await expect(page.getByTestId("active-view-report")).toBeVisible();
await expect(
page.getByTestId("galaxy-summary-field-turn"),
).toBeVisible();
// Scroll the window. The report's host expands to fit
// content rather than constraining its own height, so the
// document body is the real scroll container. SvelteKit's
// default scroll-restoration tracks `window.scrollY` on
// history navigation, which is what the acceptance criterion
// — "scroll position resets when switching to another view
// and is restored on return" — requires.
const target = 600;
await page.evaluate((value) => {
window.scrollTo(0, value);
}, target);
const savedScrollY = await page.evaluate(() => window.scrollY);
expect(savedScrollY).toBeGreaterThan(0);
// Programmatically click the back-to-map button. Driving the
// click through `evaluate` rather than the Playwright locator
// skips its built-in scrollIntoViewIfNeeded(), which would
// otherwise scroll the sticky TOC button into view and reset
// `window.scrollY` to 0 before SvelteKit's Snapshot capture
// fires.
await page.evaluate(() => {
const button = document.querySelector(
"[data-testid='report-back-to-map']",
) as HTMLButtonElement | null;
button?.click();
});
await page.waitForURL(`**/games/${GAME_ID}/map`);
await page.goBack();
await page.waitForURL(`**/games/${GAME_ID}/report`);
await expect(page.getByTestId("active-view-report")).toBeVisible();
await expect(
page.getByTestId("galaxy-summary-field-turn"),
).toBeVisible();
await expect
.poll(async () => page.evaluate(() => window.scrollY), {
timeout: 5_000,
intervals: [100, 200, 400],
})
.toBeGreaterThan(savedScrollY / 2);
});
test("back-to-map button navigates to the map URL", async ({
test("back-to-map button switches to the map view", async ({
page,
}, testInfo) => {
test.skip(
@@ -333,10 +292,16 @@ test.describe("Phase 23 report view", () => {
await mockGateway(page);
await bootSession(page);
await page.goto(`/games/${GAME_ID}/report`);
await page.goto("/");
await page.waitForFunction(() => window.__galaxyNav !== undefined);
await page.evaluate(
(id) => window.__galaxyNav!.enterGame(id, "report", {}),
GAME_ID,
);
await page.getByTestId("report-back-to-map").click();
await expect(page).toHaveURL(new RegExp(`/games/${GAME_ID}/map$`));
// The single-URL app-shell keeps the address bar at the app base;
// the active map view is the navigation signal.
await expect(page.getByTestId("active-view-map")).toBeVisible();
});
@@ -350,7 +315,12 @@ test.describe("Phase 23 report view", () => {
await mockGateway(page);
await bootSession(page);
await page.goto(`/games/${GAME_ID}/report`);
await page.goto("/");
await page.waitForFunction(() => window.__galaxyNav !== undefined);
await page.evaluate(
(id) => window.__galaxyNav!.enterGame(id, "report", {}),
GAME_ID,
);
const mobileSelect = page.getByTestId("report-toc-mobile");
await expect(mobileSelect).toBeVisible();