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
+29 -8
View File
@@ -105,16 +105,21 @@ async function mockGateway(page: Page): Promise<MockState> {
},
);
// The first SubscribeEvents request from the root layout receives
// one signed `game.turn.ready` frame for turn 5; subsequent
// reconnect attempts (events.ts retries after the abrupt
// end-of-body) are held open indefinitely so the toast stays
// visible long enough for the test to interact with it.
// The root layout opens the event stream while the dispatcher is
// still on the lobby screen (the single-URL app-shell starts the
// singleton stream on authentication, before the player enters a
// game). The per-game `game.turn.ready` handler only registers once
// the game shell mounts, so the very first frame can land before the
// handler exists. Deliver the signed frame on the first TWO
// SubscribeEvents requests — the post-frame reconnect (events.ts
// retries after the abrupt end-of-body) carries it again once the
// handler is up; `markPendingTurn(5)` is idempotent. Hold every
// later reconnect open so the toast stays visible for the test.
await page.route(
"**/edge.v1.Gateway/SubscribeEvents",
async (route) => {
state.subscribeHits += 1;
if (state.subscribeHits === 1) {
if (state.subscribeHits <= 2) {
const payload = new TextEncoder().encode(
JSON.stringify({ game_id: GAME_ID, turn: 5 }),
);
@@ -155,7 +160,12 @@ test("signed game.turn.ready frame surfaces the toast", async ({ page }) => {
await mockGateway(page);
await bootSession(page);
await page.goto(`/games/${GAME_ID}/map`);
await page.goto("/");
await page.waitForFunction(() => window.__galaxyNav !== undefined);
await page.evaluate(
(id) => window.__galaxyNav!.enterGame(id, "map", {}),
GAME_ID,
);
// Initial chrome reflects the bootstrap currentTurn=4.
await expect(page.getByTestId("active-view-map")).toHaveAttribute(
@@ -181,8 +191,19 @@ test("manual dismiss clears the turn-ready toast without advancing the view", as
await mockGateway(page);
await bootSession(page);
await page.goto(`/games/${GAME_ID}/map`);
await page.goto("/");
await page.waitForFunction(() => window.__galaxyNav !== undefined);
await page.evaluate(
(id) => window.__galaxyNav!.enterGame(id, "map", {}),
GAME_ID,
);
// Let the game shell finish booting (so the per-game turn-ready
// handler is registered) before expecting the pushed toast.
await expect(page.getByTestId("active-view-map")).toHaveAttribute(
"data-status",
"ready",
);
await expect(page.getByTestId("toast")).toBeVisible({ timeout: 5_000 });
await page.getByTestId("toast-close").click();
await expect(page.getByTestId("toast")).toBeHidden();