// Vitest coverage for the Phase 19 ship-group → World binding. The // `reportToWorld` function now blends planet and ship-group // primitives in one pass and returns a hitLookup map keyed by the // primitive id; these tests assert that each ship-group variant // (own on-planet, own in-hyperspace, foreign in-hyperspace, // incoming, unidentified) shows up with the expected position, // style, priority, and lookup entry. import "@testing-library/jest-dom/vitest"; import { describe, expect, test } from "vitest"; import type { GameReport, ReportPlanet, } from "../src/api/game-state"; import { reportToWorld } from "../src/map/state-binding"; import { SHIP_GROUP_ID_OFFSETS } from "../src/map/ship-groups"; import { EMPTY_SHIP_GROUPS } from "./helpers/empty-ship-groups"; function planet(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(overrides: Partial = {}): GameReport { return { turn: 1, mapWidth: 1000, mapHeight: 1000, planetCount: 0, planets: [], race: "Earthlings", localShipClass: [], routes: [], localPlayerDrive: 0, localPlayerWeapons: 0, localPlayerShields: 0, localPlayerCargo: 0, ...EMPTY_SHIP_GROUPS, ...overrides, }; } describe("reportToWorld — ship groups", () => { test("on-planet local group is NOT rendered on the map (planet inspector hosts it)", () => { const home = planet({ number: 17, x: 100, y: 100, kind: "local" }); const { world, hitLookup } = reportToWorld( makeReport({ planets: [home], localShipGroups: [ { id: "uuid-local-1", count: 2, class: "Frontier", tech: { drive: 5, weapons: 0, shields: 0, cargo: 1 }, cargo: "NONE", load: 0, destination: 17, origin: null, range: null, speed: 0, mass: 12, state: "In_Orbit", fleet: null, }, ], }), ); // Only the planet itself contributes a primitive; the on-planet // group is intentionally invisible on the map. Phase 19's // `lib/inspectors/planet/ship-groups.svelte` lists it inside the // planet inspector instead. expect(world.primitives.length).toBe(1); expect(hitLookup.has(SHIP_GROUP_ID_OFFSETS.local + 0)).toBe(false); expect(hitLookup.get(17)).toEqual({ kind: "planet", number: 17 }); }); test("in-hyperspace local group renders at the interpolated position", () => { const dest = planet({ number: 1, x: 0, y: 0 }); const orig = planet({ number: 2, x: 100, y: 0 }); const { world } = reportToWorld( makeReport({ planets: [dest, orig], localShipGroups: [ { id: "uuid-local-fly", count: 1, class: "Cruiser", tech: { drive: 10, weapons: 0, shields: 0, cargo: 0 }, cargo: "NONE", load: 0, destination: 1, origin: 2, range: 25, speed: 0, mass: 50, state: "In_Space", fleet: null, }, ], }), ); const groupPrimId = SHIP_GROUP_ID_OFFSETS.local + 0; const group = world.primitives.find((p) => p.id === groupPrimId); if (group?.kind !== "point") throw new Error("expected point"); // dest=(0,0), orig=(100,0), range=25 → 25 units toward orig from // dest along the segment of length 100 → (25, 0). expect(group.x).toBe(25); expect(group.y).toBe(0); }); test("incoming-group line crosses the torus seam via the shortest path", () => { const dest = planet({ number: 1, x: 5, y: 50 }); const orig = planet({ number: 9, x: 95, y: 50 }); const { world } = reportToWorld( makeReport({ mapWidth: 100, mapHeight: 100, planets: [dest, orig], incomingShipGroups: [ { origin: 9, destination: 1, distance: 5, speed: 5, mass: 1, }, ], }), ); const line = world.primitives.find( (p) => p.id === SHIP_GROUP_ID_OFFSETS.incomingLine + 0, ); if (line?.kind !== "line") throw new Error("expected line"); // Origin (95) → unwrapped destination at 105 (origin.x + (-10) is // the no-wrap path). The shortest delta from 95 to 5 on width 100 // is +10, so we expect line.x2 = 95 + 10 = 105. expect(line.x1).toBe(95); expect(line.x2).toBe(105); }); test("incoming group emits one dashed line + one clickable point", () => { const dest = planet({ number: 1, x: 0, y: 0 }); const orig = planet({ number: 9, x: 100, y: 0 }); const { world, hitLookup } = reportToWorld( makeReport({ planets: [dest, orig], incomingShipGroups: [ { origin: 9, destination: 1, distance: 40, speed: 20, mass: 4, }, ], }), ); const lineId = SHIP_GROUP_ID_OFFSETS.incomingLine + 0; const pointId = SHIP_GROUP_ID_OFFSETS.incoming + 0; const line = world.primitives.find((p) => p.id === lineId); if (line?.kind !== "line") throw new Error("expected line for incoming"); expect(line.x1).toBe(100); // origin expect(line.x2).toBe(0); // destination expect(line.style.strokeDashPx).toBeGreaterThan(0); const point = world.primitives.find((p) => p.id === pointId); if (point?.kind !== "point") throw new Error("expected point for incoming"); expect(point.x).toBe(40); // distance=40 from dest along line of len 100 expect(point.y).toBe(0); // Hit lookup is registered only for the clickable point, not // the dashed trajectory line. expect(hitLookup.get(pointId)).toEqual({ kind: "shipGroup", ref: { variant: "incoming", index: 0 }, }); expect(hitLookup.has(lineId)).toBe(false); }); test("unidentified group renders at its absolute coordinates", () => { const { world, hitLookup } = reportToWorld( makeReport({ unidentifiedShipGroups: [{ x: 555, y: 222 }], }), ); const id = SHIP_GROUP_ID_OFFSETS.unidentified + 0; const point = world.primitives.find((p) => p.id === id); if (point?.kind !== "point") throw new Error("expected point"); expect(point.x).toBe(555); expect(point.y).toBe(222); expect(hitLookup.get(id)).toEqual({ kind: "shipGroup", ref: { variant: "unidentified", index: 0 }, }); }); test("group whose destination is missing from the report is dropped", () => { const { world } = reportToWorld( makeReport({ planets: [], localShipGroups: [ { id: "uuid-orphan", count: 1, class: "Drone", tech: { drive: 1, weapons: 0, shields: 0, cargo: 0 }, cargo: "NONE", load: 0, destination: 999, // not in planets origin: null, range: null, speed: 0, mass: 1, state: "In_Orbit", fleet: null, }, ], }), ); // Only the (empty) planet list contributes — no group primitive. expect(world.primitives.length).toBe(0); }); test("planet hitLookup entries are registered alongside ship groups", () => { const { hitLookup } = reportToWorld( makeReport({ planets: [planet({ number: 42, x: 0, y: 0, kind: "local" })], }), ); expect(hitLookup.get(42)).toEqual({ kind: "planet", number: 42 }); }); });