// Vitest unit coverage for the pure `applyOrderOverlay` projection. // Phase 14 introduced the overlay for `planetRename`; Phase 15 // extends it to `setProductionType` and shares the same eligibility // rule. Future phases (route updates, etc.) will extend the overlay // and need equivalent cases here. import { describe, expect, test } from "vitest"; import { applyOrderOverlay, productionDisplayFromCommand, type GameReport, type ReportPlanet, } from "../src/api/game-state"; import type { CommandStatus, OrderCommand, ProductionType, } from "../src/sync/order-types"; 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: 4, mapWidth: 4000, mapHeight: 4000, planetCount: planets.length, planets, race: "", localShipClass: [], }; } describe("applyOrderOverlay", () => { test("returns the same report when no commands match", () => { const report = makeReport([makePlanet({ number: 1, name: "Earth" })]); const out = applyOrderOverlay(report, [], {}); expect(out).toBe(report); }); test("renames a planet on applied commands", () => { const report = makeReport([ makePlanet({ number: 1, name: "Earth" }), makePlanet({ number: 2, name: "Mars" }), ]); const cmd: OrderCommand = { kind: "planetRename", id: "cmd-1", planetNumber: 1, name: "New Earth", }; const statuses: Record = { "cmd-1": "applied" }; const out = applyOrderOverlay(report, [cmd], statuses); expect(out).not.toBe(report); expect(out.planets[0]!.name).toBe("New Earth"); expect(out.planets[1]!.name).toBe("Mars"); // raw report stays untouched expect(report.planets[0]!.name).toBe("Earth"); }); test("renames on submitting too (in-flight optimistic)", () => { const report = makeReport([makePlanet({ number: 1, name: "Earth" })]); const cmd: OrderCommand = { kind: "planetRename", id: "cmd-1", planetNumber: 1, name: "Pending", }; const out = applyOrderOverlay(report, [cmd], { "cmd-1": "submitting" }); expect(out.planets[0]!.name).toBe("Pending"); }); test("skips draft / invalid / rejected statuses", () => { const report = makeReport([makePlanet({ number: 1, name: "Earth" })]); const cmd: OrderCommand = { kind: "planetRename", id: "cmd-1", planetNumber: 1, name: "Tentative", }; for (const status of ["draft", "invalid", "rejected"] as const) { const out = applyOrderOverlay(report, [cmd], { "cmd-1": status }); expect(out.planets[0]!.name).toBe("Earth"); } }); test("applies on `valid` so the player sees their committed intent immediately", () => { const report = makeReport([makePlanet({ number: 1, name: "Earth" })]); const cmd: OrderCommand = { kind: "planetRename", id: "cmd-1", planetNumber: 1, name: "Pending-Sync", }; const out = applyOrderOverlay(report, [cmd], { "cmd-1": "valid" }); expect(out.planets[0]!.name).toBe("Pending-Sync"); }); test("ignores rename for missing planet (visibility lost)", () => { const report = makeReport([makePlanet({ number: 1, name: "Earth" })]); const cmd: OrderCommand = { kind: "planetRename", id: "cmd-1", planetNumber: 99, name: "Phantom", }; const out = applyOrderOverlay(report, [cmd], { "cmd-1": "applied" }); expect(out).toBe(report); }); test("placeholder commands pass through", () => { const report = makeReport([makePlanet({ number: 1, name: "Earth" })]); const cmd: OrderCommand = { kind: "placeholder", id: "cmd-1", label: "noop", }; const out = applyOrderOverlay(report, [cmd], { "cmd-1": "applied" }); expect(out).toBe(report); }); test("multiple renames apply in command order", () => { const report = makeReport([makePlanet({ number: 1, name: "Old" })]); const first: OrderCommand = { kind: "planetRename", id: "cmd-1", planetNumber: 1, name: "Mid", }; const second: OrderCommand = { kind: "planetRename", id: "cmd-2", planetNumber: 1, name: "Final", }; const out = applyOrderOverlay(report, [first, second], { "cmd-1": "applied", "cmd-2": "applied", }); expect(out.planets[0]!.name).toBe("Final"); }); test("setProductionType rewrites planet.production for valid statuses", () => { const report = makeReport([ makePlanet({ number: 1, name: "Earth", production: "Capital" }), ]); const cmd: OrderCommand = { kind: "setProductionType", id: "cmd-1", planetNumber: 1, productionType: "DRIVE", subject: "", }; for (const status of ["valid", "submitting", "applied"] as const) { const out = applyOrderOverlay(report, [cmd], { "cmd-1": status }); expect(out.planets[0]!.production).toBe("Drive"); } }); test("setProductionType skips draft / invalid / rejected statuses", () => { const report = makeReport([ makePlanet({ number: 1, name: "Earth", production: "Capital" }), ]); const cmd: OrderCommand = { kind: "setProductionType", id: "cmd-1", planetNumber: 1, productionType: "DRIVE", subject: "", }; for (const status of ["draft", "invalid", "rejected"] as const) { const out = applyOrderOverlay(report, [cmd], { "cmd-1": status }); expect(out.planets[0]!.production).toBe("Capital"); } }); test("setProductionType applied with subject mirrors the engine's display", () => { const report = makeReport([ makePlanet({ number: 1, name: "Earth", production: "Capital" }), ]); const cmd: OrderCommand = { kind: "setProductionType", id: "cmd-1", planetNumber: 1, productionType: "SHIP", subject: "Scout", }; const out = applyOrderOverlay(report, [cmd], { "cmd-1": "applied" }); expect(out.planets[0]!.production).toBe("Scout"); }); test("setProductionType + planetRename for the same planet compose", () => { const report = makeReport([ makePlanet({ number: 1, name: "Earth", production: "Capital" }), ]); const rename: OrderCommand = { kind: "planetRename", id: "cmd-rename", planetNumber: 1, name: "New-Earth", }; const setProd: OrderCommand = { kind: "setProductionType", id: "cmd-prod", planetNumber: 1, productionType: "DRIVE", subject: "", }; const out = applyOrderOverlay(report, [rename, setProd], { "cmd-rename": "applied", "cmd-prod": "applied", }); expect(out.planets[0]!.name).toBe("New-Earth"); expect(out.planets[0]!.production).toBe("Drive"); }); test("ignores setProductionType for missing planet (visibility lost)", () => { const report = makeReport([ makePlanet({ number: 1, name: "Earth", production: "Capital" }), ]); const cmd: OrderCommand = { kind: "setProductionType", id: "cmd-1", planetNumber: 99, productionType: "DRIVE", subject: "", }; const out = applyOrderOverlay(report, [cmd], { "cmd-1": "applied" }); expect(out).toBe(report); }); }); describe("productionDisplayFromCommand", () => { const cases: ReadonlyArray<{ productionType: ProductionType; subject: string; expected: string; }> = [ { productionType: "MAT", subject: "", expected: "Material" }, { productionType: "CAP", subject: "", expected: "Capital" }, { productionType: "DRIVE", subject: "", expected: "Drive" }, { productionType: "WEAPONS", subject: "", expected: "Weapons" }, { productionType: "SHIELDS", subject: "", expected: "Shields" }, { productionType: "CARGO", subject: "", expected: "Cargo" }, { productionType: "SCIENCE", subject: "AlphaSci", expected: "AlphaSci" }, { productionType: "SHIP", subject: "Scout", expected: "Scout" }, ]; for (const tc of cases) { test(`${tc.productionType} → ${tc.expected}`, () => { expect(productionDisplayFromCommand(tc.productionType, tc.subject)).toBe( tc.expected, ); }); } });