// Vitest component coverage for the Phase 15 production-controls // subsection of the planet inspector. Drives the component against a // real `OrderDraftStore` (with `fake-indexeddb` standing in for the // browser's IDB factory) so the collapse-by-`planetNumber` rule and // the per-row status side-effects are exercised end-to-end. // // The active-segment derivation is covered by direct render-and- // query assertions: the parser is small enough that a table-driven // pass over the canonical engine display strings catches every // branch. import "@testing-library/jest-dom/vitest"; import "fake-indexeddb/auto"; import { fireEvent, render, waitFor } from "@testing-library/svelte"; import { afterEach, beforeEach, describe, expect, test } from "vitest"; import { i18n } from "../src/lib/i18n/index.svelte"; import type { ReportPlanet, ShipClassSummary, } from "../src/api/game-state"; import Production from "../src/lib/inspectors/planet/production.svelte"; import { ORDER_DRAFT_CONTEXT_KEY, OrderDraftStore, } from "../src/sync/order-draft.svelte"; import { IDBCache } from "../src/platform/store/idb-cache"; import { openGalaxyDB, type GalaxyDB } from "../src/platform/store/idb"; import type { Cache } from "../src/platform/store/index"; import type { IDBPDatabase } from "idb"; const GAME_ID = "11111111-2222-3333-4444-555555555555"; let db: IDBPDatabase; let dbName: string; let cache: Cache; let draft: OrderDraftStore; beforeEach(async () => { dbName = `galaxy-production-${crypto.randomUUID()}`; db = await openGalaxyDB(dbName); cache = new IDBCache(db); draft = new OrderDraftStore(); await draft.init({ cache, gameId: GAME_ID }); i18n.resetForTests("en"); }); afterEach(async () => { draft.dispose(); db.close(); await new Promise((resolve) => { const req = indexedDB.deleteDatabase(dbName); req.onsuccess = () => resolve(); req.onerror = () => resolve(); req.onblocked = () => resolve(); }); }); function localPlanet( overrides: Partial & Pick, ): ReportPlanet { return { name: "Earth", x: 0, y: 0, kind: "local", owner: null, size: 1000, resources: 5, industryStockpile: 0, materialsStockpile: 0, industry: 100, population: 100, colonists: 0, production: null, freeIndustry: 100, ...overrides, }; } function shipClass( overrides: Partial & Pick, ): ShipClassSummary { return { drive: 0, armament: 0, weapons: 0, shields: 0, cargo: 0, ...overrides, }; } function mountProduction( planet: ReportPlanet, localShipClass: ShipClassSummary[] = [], ) { const context = new Map([ [ORDER_DRAFT_CONTEXT_KEY, draft], ]); return render(Production, { props: { planet, localShipClass }, context, }); } describe("planet inspector — production controls", () => { test("renders the four main segments with localised labels", () => { const ui = mountProduction(localPlanet({ number: 1 })); expect( ui.getByTestId("inspector-planet-production-segment-industry"), ).toHaveTextContent("industry"); expect( ui.getByTestId("inspector-planet-production-segment-materials"), ).toHaveTextContent("materials"); expect( ui.getByTestId("inspector-planet-production-segment-research"), ).toHaveTextContent("research"); expect( ui.getByTestId("inspector-planet-production-segment-ship"), ).toHaveTextContent("build ship"); }); test("Industry click emits a CAP setProductionType command", async () => { const ui = mountProduction(localPlanet({ number: 7 })); await fireEvent.click( ui.getByTestId("inspector-planet-production-segment-industry"), ); await waitFor(() => expect(draft.commands).toHaveLength(1)); const cmd = draft.commands[0]!; expect(cmd.kind).toBe("setProductionType"); if (cmd.kind !== "setProductionType") return; expect(cmd.planetNumber).toBe(7); expect(cmd.productionType).toBe("CAP"); expect(cmd.subject).toBe(""); }); test("Materials click emits a MAT setProductionType command", async () => { const ui = mountProduction(localPlanet({ number: 7 })); await fireEvent.click( ui.getByTestId("inspector-planet-production-segment-materials"), ); await waitFor(() => expect(draft.commands).toHaveLength(1)); const cmd = draft.commands[0]!; if (cmd.kind !== "setProductionType") throw new Error("wrong kind"); expect(cmd.productionType).toBe("MAT"); }); test("Research click reveals the four tech sub-buttons without emitting", async () => { const ui = mountProduction(localPlanet({ number: 7 })); expect( ui.queryByTestId("inspector-planet-production-research-row"), ).toBeNull(); await fireEvent.click( ui.getByTestId("inspector-planet-production-segment-research"), ); expect( ui.getByTestId("inspector-planet-production-research-row"), ).toBeInTheDocument(); expect(draft.commands).toHaveLength(0); await fireEvent.click( ui.getByTestId("inspector-planet-production-research-drive"), ); await waitFor(() => expect(draft.commands).toHaveLength(1)); const cmd = draft.commands[0]!; if (cmd.kind !== "setProductionType") throw new Error("wrong kind"); expect(cmd.productionType).toBe("DRIVE"); expect(cmd.subject).toBe(""); }); test("Build-Ship segment shows the empty placeholder when no classes designed", async () => { const ui = mountProduction(localPlanet({ number: 7 }), []); await fireEvent.click( ui.getByTestId("inspector-planet-production-segment-ship"), ); expect( ui.getByTestId("inspector-planet-production-ship-empty"), ).toBeInTheDocument(); }); test("Build-Ship click on a class emits a SHIP setProductionType command", async () => { const ui = mountProduction(localPlanet({ number: 7 }), [ shipClass({ name: "Scout" }), shipClass({ name: "Destroyer" }), ]); await fireEvent.click( ui.getByTestId("inspector-planet-production-segment-ship"), ); await fireEvent.click( ui.getByTestId("inspector-planet-production-ship-Scout"), ); await waitFor(() => expect(draft.commands).toHaveLength(1)); const cmd = draft.commands[0]!; if (cmd.kind !== "setProductionType") throw new Error("wrong kind"); expect(cmd.productionType).toBe("SHIP"); expect(cmd.subject).toBe("Scout"); }); test("re-clicks on the same planet collapse to the latest entry via the store", async () => { const ui = mountProduction(localPlanet({ number: 7 }), [ shipClass({ name: "Scout" }), ]); await fireEvent.click( ui.getByTestId("inspector-planet-production-segment-industry"), ); await fireEvent.click( ui.getByTestId("inspector-planet-production-segment-materials"), ); await fireEvent.click( ui.getByTestId("inspector-planet-production-segment-research"), ); await fireEvent.click( ui.getByTestId("inspector-planet-production-research-cargo"), ); await waitFor(() => expect(draft.commands).toHaveLength(1)); const cmd = draft.commands[0]!; if (cmd.kind !== "setProductionType") throw new Error("wrong kind"); expect(cmd.productionType).toBe("CARGO"); }); test("active main segment derives from planet.production display string", () => { const cases: ReadonlyArray<{ production: string | null; expected: "industry" | "materials" | "research" | "ship" | "none"; }> = [ { production: "Capital", expected: "industry" }, { production: "Material", expected: "materials" }, { production: "Drive", expected: "research" }, { production: "Weapons", expected: "research" }, { production: "Shields", expected: "research" }, { production: "Cargo", expected: "research" }, { production: "Scout", expected: "ship" }, { production: "-", expected: "none" }, { production: null, expected: "none" }, { production: "UnknownThing", expected: "none" }, ]; for (const tc of cases) { const ui = mountProduction( localPlanet({ number: 1, production: tc.production }), [shipClass({ name: "Scout" })], ); const ids: ReadonlyArray< "industry" | "materials" | "research" | "ship" > = ["industry", "materials", "research", "ship"]; for (const id of ids) { const el = ui.getByTestId( `inspector-planet-production-segment-${id}`, ); if (tc.expected === id) { expect(el.classList.contains("active")).toBe(true); } else { expect(el.classList.contains("active")).toBe(false); } } ui.unmount(); } }); test("active research sub-button highlights for known display strings", () => { const cases: ReadonlyArray<{ production: string; slug: "drive" | "weapons" | "shields" | "cargo"; }> = [ { production: "Drive", slug: "drive" }, { production: "Weapons", slug: "weapons" }, { production: "Shields", slug: "shields" }, { production: "Cargo", slug: "cargo" }, ]; for (const tc of cases) { const ui = mountProduction( localPlanet({ number: 1, production: tc.production }), ); const el = ui.getByTestId( `inspector-planet-production-research-${tc.slug}`, ); expect(el.classList.contains("active")).toBe(true); ui.unmount(); } }); test("ship class sub-row matches when production equals a class name", async () => { const ui = mountProduction( localPlanet({ number: 1, production: "Scout" }), [shipClass({ name: "Scout" }), shipClass({ name: "Destroyer" })], ); expect( ui.getByTestId("inspector-planet-production-ship-Scout").classList .contains("active"), ).toBe(true); expect( ui .getByTestId("inspector-planet-production-ship-Destroyer") .classList.contains("active"), ).toBe(false); }); });