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
@@ -7,15 +7,9 @@ section is its own component under `lib/active-view/report/` — the
data shapes are too varied for one generic table, and the
component-per-section seam matches Phase 23's targeted-test contract.
Active-section highlighting and scroll save/restore land here:
- `IntersectionObserver` rooted on the active-view-host element
(`bind:this` in `+layout.svelte`, plumbed through
`ACTIVE_VIEW_HOST_CONTEXT_KEY`) watches every `<section
id="report-<slug>">` and updates a local `activeSlug` rune.
- The matching `+page.svelte` exports a SvelteKit `Snapshot` that
captures and restores `host.element.scrollTop`, so navigating to
/map and back lands on the same scroll position. The save lives in
`+page.svelte` because SvelteKit binds snapshots per route.
Active-section highlighting lands here: an `IntersectionObserver`
rooted on the viewport watches every `<section id="report-<slug>">`
and updates a local `activeSlug` rune that drives the TOC highlight.
The 20-section list lives here as a single source of truth so the
TOC and the body iterate the same data.
+42 -1
View File
@@ -6,8 +6,16 @@
// layout intercepts the `loading` and `unsupported` session states
// before this component renders, so here `session.status` is either
// `anonymous` (login) or `authenticated` (lobby / create / game).
import { onMount } from "svelte";
import { dev } from "$app/environment";
import { session } from "$lib/session-store.svelte";
import { appScreen } from "$lib/app-nav.svelte";
import {
appScreen,
activeView,
type AppScreen,
type GameView,
type GameViewState,
} from "$lib/app-nav.svelte";
import LoginScreen from "$lib/screens/login-screen.svelte";
import LobbyScreen from "$lib/screens/lobby-screen.svelte";
import LobbyCreateScreen from "$lib/screens/lobby-create-screen.svelte";
@@ -15,6 +23,39 @@
import { pushState } from "$app/navigation";
import { page } from "$app/state";
// Dev-only navigation affordance for the Playwright e2e suite. The
// single-URL app-shell has no per-screen / per-view routes, so a
// spec can no longer drive the UI by `page.goto("/games/:id/:view")`.
// Instead the suite seeds the session, loads `/` (which lands on the
// authenticated lobby), then calls `window.__galaxyNav.enterGame(...)`
// to switch the in-memory screen and view. Guarded by `dev` so it is
// stripped from the production bundle — `import.meta.env.DEV` (and the
// SvelteKit `dev` re-export) is statically `false` there, so the
// whole `onMount` body tree-shakes away.
type ViewParams = Omit<GameViewState, "view">;
interface NavSurface {
enterGame(gameId: string, view?: GameView, params?: ViewParams): void;
select(view: GameView, params?: ViewParams): void;
go(screen: AppScreen, opts?: { gameId?: string }): void;
}
type NavWindow = typeof globalThis & { __galaxyNav?: NavSurface };
onMount(() => {
if (!dev) return;
(window as NavWindow).__galaxyNav = {
enterGame(gameId, view = "map", params = {}): void {
activeView.select(view, params);
appScreen.go("game", { gameId });
},
select(view, params = {}): void {
activeView.select(view, params);
},
go(screen, opts = {}): void {
appScreen.go(screen, opts);
},
};
});
// Screen-level browser history (Back → lobby) without changing the URL.
// On the first authenticated render, stamp a restored overlay (game /
// lobby-create) on top of the load entry so Back falls through to lobby.