// Component tests for the Phase 10 in-game shell sidebar. Validates // the default selected tab, the Calculator / Inspector / Order // switching, the empty-state copy that matches the IA section, the // `?sidebar=` URL seed convention used by the mobile bottom-tabs, // and the Phase 13 selection-driven planet inspector content. import "@testing-library/jest-dom/vitest"; import { fireEvent, render } from "@testing-library/svelte"; import { beforeEach, describe, expect, test, vi, } from "vitest"; import { i18n } from "../src/lib/i18n/index.svelte"; import { GAME_STATE_CONTEXT_KEY, GameStateStore, } from "../src/lib/game-state.svelte"; import { SELECTION_CONTEXT_KEY, SelectionStore, } from "../src/lib/selection.svelte"; import { EMPTY_SHIP_GROUPS } from "./helpers/empty-ship-groups"; import { RENDERED_REPORT_CONTEXT_KEY, createRenderedReportSource, } from "../src/lib/rendered-report.svelte"; import { ORDER_DRAFT_CONTEXT_KEY, OrderDraftStore, } from "../src/sync/order-draft.svelte"; import type { GameReport, ReportPlanet } from "../src/api/game-state"; const pageMock = vi.hoisted(() => ({ url: new URL("http://localhost/games/g1/map"), params: { id: "g1" } as Record, })); vi.mock("$app/state", () => ({ page: pageMock, })); import Sidebar from "../src/lib/sidebar/sidebar.svelte"; function makePlanet(overrides: Partial): ReportPlanet { return { number: 0, name: "", x: 0, y: 0, kind: "local", owner: null, size: null, resources: null, industryStockpile: null, materialsStockpile: null, industry: null, population: null, colonists: null, production: null, freeIndustry: null, ...overrides, }; } function makeReport(planets: ReportPlanet[]): GameReport { return { turn: 1, mapWidth: 1000, mapHeight: 1000, planetCount: planets.length, planets, race: "", localShipClass: [], routes: [], localPlayerDrive: 0, localPlayerWeapons: 0, localPlayerShields: 0, localPlayerCargo: 0, ...EMPTY_SHIP_GROUPS, }; } function withStores(report: GameReport | null): { gameState: GameStateStore; selection: SelectionStore; orderDraft: OrderDraftStore; context: Map; } { const gameState = new GameStateStore(); gameState.report = report; gameState.status = report === null ? "idle" : "ready"; const selection = new SelectionStore(); const orderDraft = new OrderDraftStore(); const renderedReport = createRenderedReportSource(gameState, orderDraft); const context = new Map([ [GAME_STATE_CONTEXT_KEY, gameState], [SELECTION_CONTEXT_KEY, selection], [ORDER_DRAFT_CONTEXT_KEY, orderDraft], [RENDERED_REPORT_CONTEXT_KEY, renderedReport], ]); return { gameState, selection, orderDraft, context }; } beforeEach(() => { i18n.resetForTests("en"); pageMock.url = new URL("http://localhost/games/g1/map"); }); describe("game-shell sidebar", () => { test("renders the inspector tab content by default", () => { const ui = render(Sidebar, { props: { open: false, onClose: () => {} }, }); expect(ui.getByTestId("sidebar-tool-inspector")).toBeInTheDocument(); expect(ui.getByTestId("sidebar-tool-inspector")).toHaveTextContent( "select an object on the map", ); expect(ui.getByTestId("sidebar")).toHaveAttribute( "data-active-tab", "inspector", ); }); test("switching tabs updates the rendered tool", async () => { const ui = render(Sidebar, { props: { open: false, onClose: () => {} }, }); await fireEvent.click(ui.getByTestId("sidebar-tab-calculator")); expect(ui.getByTestId("sidebar-tool-calculator")).toBeInTheDocument(); expect(ui.queryByTestId("sidebar-tool-inspector")).toBeNull(); expect(ui.queryByTestId("sidebar-tool-order")).toBeNull(); await fireEvent.click(ui.getByTestId("sidebar-tab-order")); expect(ui.getByTestId("sidebar-tool-order")).toBeInTheDocument(); expect(ui.queryByTestId("sidebar-tool-calculator")).toBeNull(); }); test("empty-state copy matches the IA section verbatim", () => { const ui = render(Sidebar, { props: { open: false, onClose: () => {} }, }); expect(ui.getByTestId("sidebar-tool-inspector")).toHaveTextContent( "select an object on the map", ); }); test("?sidebar=calc seeds the calculator tab on first mount", () => { pageMock.url = new URL("http://localhost/games/g1/map?sidebar=calc"); const ui = render(Sidebar, { props: { open: false, onClose: () => {} }, }); expect(ui.getByTestId("sidebar-tool-calculator")).toBeInTheDocument(); expect(ui.getByTestId("sidebar")).toHaveAttribute( "data-active-tab", "calculator", ); }); test("?sidebar=order seeds the order tab on first mount", () => { pageMock.url = new URL("http://localhost/games/g1/map?sidebar=order"); const ui = render(Sidebar, { props: { open: false, onClose: () => {} }, }); expect(ui.getByTestId("sidebar-tool-order")).toBeInTheDocument(); }); test("close button calls the onClose prop", async () => { const onClose = vi.fn(); const ui = render(Sidebar, { props: { open: true, onClose } }); await fireEvent.click(ui.getByTestId("sidebar-close")); expect(onClose).toHaveBeenCalledTimes(1); }); test("inspector tab swaps to the planet view when a planet is selected", async () => { const planet = makePlanet({ number: 17, name: "Galactica", kind: "local", x: 50, y: 75, size: 1000, resources: 10, population: 800, colonists: 0, industry: 600, industryStockpile: 0, materialsStockpile: 0, production: "drive", freeIndustry: 200, }); const { selection, context } = withStores(makeReport([planet])); const ui = render( Sidebar, { props: { open: false, onClose: () => {} }, context, }, ); expect(ui.getByTestId("sidebar-tool-inspector")).toHaveTextContent( "select an object on the map", ); selection.selectPlanet(17); await Promise.resolve(); expect(ui.queryByText("select an object on the map")).toBeNull(); expect(ui.getByTestId("inspector-planet")).toHaveAttribute( "data-planet-id", "17", ); expect(ui.getByTestId("inspector-planet-name")).toHaveTextContent( "Galactica", ); }); test("selection that points at a missing planet falls back to the empty state", () => { const { selection, context } = withStores( makeReport([makePlanet({ number: 1, name: "Visible", kind: "local" })]), ); selection.selectPlanet(999); const ui = render( Sidebar, { props: { open: false, onClose: () => {} }, context, }, ); expect(ui.queryByTestId("inspector-planet")).toBeNull(); expect(ui.getByTestId("sidebar-tool-inspector")).toHaveTextContent( "select an object on the map", ); }); });