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
+14 -12
View File
@@ -12,9 +12,13 @@ import { beforeEach, describe, expect, test, vi } from "vitest";
import { i18n } from "../src/lib/i18n/index.svelte";
import type { TranslationKey } from "../src/lib/i18n/index.svelte";
const gotoMock = vi.hoisted(() => vi.fn());
vi.mock("$app/navigation", () => ({
goto: gotoMock,
// The TOC's "back to map" button switches the active in-game view via
// `activeView.select("map")` (the single-URL app-shell has no
// `/games/:id/map` route). Mock the nav store so the spy captures the
// view switch and no real `pushState` runs.
const activeViewSelectMock = vi.hoisted(() => vi.fn());
vi.mock("$lib/app-nav.svelte", () => ({
activeView: { select: activeViewSelectMock },
}));
import ReportToc, {
@@ -29,13 +33,13 @@ const ENTRIES: readonly TocEntry[] = [
beforeEach(() => {
i18n.resetForTests("en");
gotoMock.mockClear();
activeViewSelectMock.mockClear();
});
describe("report TOC", () => {
test("renders one anchor per entry and one option in the mobile select", () => {
const ui = render(ReportToc, {
props: { entries: ENTRIES, activeSlug: "galaxy-summary", gameId: "g-1" },
props: { entries: ENTRIES, activeSlug: "galaxy-summary" },
});
for (const e of ENTRIES) {
expect(ui.getByTestId(`report-toc-${e.slug}`)).toBeInTheDocument();
@@ -47,7 +51,7 @@ describe("report TOC", () => {
test("marks the active anchor with aria-current=location and a class", () => {
const ui = render(ReportToc, {
props: { entries: ENTRIES, activeSlug: "bombings", gameId: "g-1" },
props: { entries: ENTRIES, activeSlug: "bombings" },
});
const active = ui.getByTestId("report-toc-bombings");
expect(active).toHaveAttribute("aria-current", "location");
@@ -58,17 +62,16 @@ describe("report TOC", () => {
expect(inactive).not.toHaveClass("active");
});
test("back-to-map button calls goto with the active game's map URL", async () => {
test("back-to-map button switches the active view to the map", async () => {
const ui = render(ReportToc, {
props: {
entries: ENTRIES,
activeSlug: "galaxy-summary",
gameId: "abc",
},
});
const button = ui.getByTestId("report-back-to-map");
await fireEvent.click(button);
expect(gotoMock).toHaveBeenCalledWith("/games/abc/map");
expect(activeViewSelectMock).toHaveBeenCalledWith("map");
});
test("anchor click cancels the default jump and calls scrollIntoView on the target", async () => {
@@ -97,7 +100,7 @@ describe("report TOC", () => {
});
const ui = render(ReportToc, {
props: { entries: ENTRIES, activeSlug: "galaxy-summary", gameId: "g" },
props: { entries: ENTRIES, activeSlug: "galaxy-summary" },
});
await fireEvent.click(ui.getByTestId("report-toc-bombings"));
expect(scrollSpy).toHaveBeenCalledWith({
@@ -132,13 +135,12 @@ describe("report TOC", () => {
props: {
entries: ENTRIES,
activeSlug: "galaxy-summary",
gameId: "g",
},
});
const select = ui.getByTestId("report-toc-mobile") as HTMLSelectElement;
await fireEvent.change(select, { target: { value: "votes" } });
expect(scrollSpy).toHaveBeenCalled();
expect(gotoMock).not.toHaveBeenCalled();
expect(activeViewSelectMock).not.toHaveBeenCalled();
target.remove();
});