// Component tests for the Phase 10 in-game shell header. The header // composes the static `race ?` placeholder, the placeholder // turn-counter (Phase 11 wires the live source), the view-menu, and // the account-menu. The tests assert the placeholder copy, that // every view-menu entry dispatches `goto` with the right URL, and // that the Logout entry of the account-menu calls // `session.signOut("user")`. import "@testing-library/jest-dom/vitest"; import { fireEvent, render } from "@testing-library/svelte"; import { afterEach, beforeEach, describe, expect, test, vi, } from "vitest"; import { i18n } from "../src/lib/i18n/index.svelte"; import { session } from "../src/lib/session-store.svelte"; import Header from "../src/lib/header/header.svelte"; const gotoSpy = vi.fn(async (..._args: unknown[]) => {}); vi.mock("$app/navigation", () => ({ goto: (...args: unknown[]) => gotoSpy(...args), })); beforeEach(() => { i18n.resetForTests("en"); gotoSpy.mockReset(); vi.spyOn(session, "signOut").mockResolvedValue(undefined); }); afterEach(() => { vi.restoreAllMocks(); }); describe("game-shell header", () => { test("renders the static race / turn placeholders and toggles", () => { const onToggleSidebar = vi.fn(); const ui = render(Header, { props: { gameId: "g1", sidebarOpen: false, onToggleSidebar }, }); expect(ui.getByTestId("race-name")).toHaveTextContent("race ?"); expect(ui.getByTestId("turn-counter").textContent ?? "").toMatch( /turn\s+\?/, ); expect(ui.getByTestId("view-menu-trigger")).toBeInTheDocument(); expect(ui.getByTestId("account-menu-trigger")).toBeInTheDocument(); }); test("clicking the sidebar toggle invokes the prop callback", async () => { const onToggleSidebar = vi.fn(); const ui = render(Header, { props: { gameId: "g1", sidebarOpen: false, onToggleSidebar }, }); await fireEvent.click(ui.getByTestId("sidebar-toggle")); expect(onToggleSidebar).toHaveBeenCalledTimes(1); }); test("view-menu navigates to every IA destination", async () => { const ui = render(Header, { props: { gameId: "g1", sidebarOpen: false, onToggleSidebar: () => {} }, }); const destinations: Array<[string, string]> = [ ["view-menu-item-map", "/games/g1/map"], ["view-menu-item-report", "/games/g1/report"], ["view-menu-item-battle", "/games/g1/battle"], ["view-menu-item-mail", "/games/g1/mail"], [ "view-menu-item-designer-ship-class", "/games/g1/designer/ship-class", ], [ "view-menu-item-designer-science", "/games/g1/designer/science", ], ]; for (const [testId, href] of destinations) { await fireEvent.click(ui.getByTestId("view-menu-trigger")); await fireEvent.click(ui.getByTestId(testId)); expect(gotoSpy).toHaveBeenLastCalledWith(href); } }); test("view-menu Tables sub-list navigates to every entity", async () => { const ui = render(Header, { props: { gameId: "g1", sidebarOpen: false, onToggleSidebar: () => {} }, }); const tableEntities: Array<[string, string]> = [ ["view-menu-item-table-planets", "/games/g1/table/planets"], [ "view-menu-item-table-ship-classes", "/games/g1/table/ship-classes", ], [ "view-menu-item-table-ship-groups", "/games/g1/table/ship-groups", ], ["view-menu-item-table-fleets", "/games/g1/table/fleets"], ["view-menu-item-table-sciences", "/games/g1/table/sciences"], ["view-menu-item-table-races", "/games/g1/table/races"], ]; for (const [testId, href] of tableEntities) { await fireEvent.click(ui.getByTestId("view-menu-trigger")); // Open the Tables sub-disclosure each iteration; the menu // closes on every navigation. const summary = ui .getByTestId("view-menu-tables") .querySelector("summary"); if (summary !== null) { await fireEvent.click(summary); } await fireEvent.click(ui.getByTestId(testId)); expect(gotoSpy).toHaveBeenLastCalledWith(href); } }); test("account-menu Logout triggers session.signOut('user')", async () => { const ui = render(Header, { props: { gameId: "g1", sidebarOpen: false, onToggleSidebar: () => {} }, }); await fireEvent.click(ui.getByTestId("account-menu-trigger")); await fireEvent.click(ui.getByTestId("account-menu-logout")); expect(session.signOut).toHaveBeenCalledWith("user"); }); test("account-menu language picker switches the i18n locale", async () => { const ui = render(Header, { props: { gameId: "g1", sidebarOpen: false, onToggleSidebar: () => {} }, }); await fireEvent.click(ui.getByTestId("account-menu-trigger")); const select = ui.getByTestId("account-menu-language-select"); await fireEvent.change(select, { target: { value: "ru" } }); expect(i18n.locale).toBe("ru"); }); });