// Phase 27 unit tests for battle and bombing map markers. import { describe, expect, it } from "vitest"; import type { GameReport } from "../src/api/game-state"; import { battleMarkerStrokeWidth, BATTLE_MARKER_COLOR, BOMBING_MARKER_COLOR_DAMAGED, BOMBING_MARKER_COLOR_WIPED, buildBattleAndBombingMarkers, } from "../src/map/battle-markers"; import { EMPTY_SHIP_GROUPS } from "./helpers/empty-ship-groups"; describe("battleMarkerStrokeWidth", () => { it("clamps to 1 px at one shot", () => { expect(battleMarkerStrokeWidth(1)).toBe(1); }); it("clamps to 5 px at 100 shots", () => { expect(battleMarkerStrokeWidth(100)).toBe(5); }); it("caps above 100 shots at 5 px", () => { expect(battleMarkerStrokeWidth(250)).toBe(5); }); it("interpolates linearly between 1 and 100 shots", () => { // ~halfway: 50 shots → 1 + 49 * 4 / 99 ≈ 2.98 expect(battleMarkerStrokeWidth(50)).toBeCloseTo(2.98, 2); }); }); function makeReport(overrides: Partial): GameReport { return { turn: 1, mapWidth: 200, mapHeight: 200, planetCount: 0, race: "Earthlings", planets: [], localShipClass: [], routes: [], localPlayerDrive: 0, localPlayerWeapons: 0, localPlayerShields: 0, localPlayerCargo: 0, ...EMPTY_SHIP_GROUPS, ...overrides, }; } describe("buildBattleAndBombingMarkers", () => { it("returns no primitives when both battles and bombings are empty", () => { const report = makeReport({}); const out = buildBattleAndBombingMarkers(report); expect(out.primitives).toEqual([]); expect(out.lookup.size).toBe(0); }); it("emits two yellow lines through opposite corners of the planet square per battle", () => { const report = makeReport({ planets: [ { number: 4, name: "Test", kind: "local", x: 10, y: 20, size: 50, resources: 0, industryStockpile: 0, materialsStockpile: 0, population: 0, colonists: 0, industry: 0, freeIndustry: 0, production: "MAT", owner: null, }, ], battles: [ { id: "11111111-1111-1111-1111-111111111111", planet: 4, shots: 100 }, ], }); const out = buildBattleAndBombingMarkers(report); const lines = out.primitives.filter((p) => p.kind === "line"); expect(lines).toHaveLength(2); // Same yellow colour, 5 px wide for a 100-shot battle. for (const l of lines) { expect(l.style.strokeColor).toBe(BATTLE_MARKER_COLOR); expect(l.style.strokeWidthPx).toBe(5); } // First line: top-left → bottom-right corner of the planet square. const [a, b] = lines as Array; expect(a.x1).toBeLessThan(a.x2); expect(a.y1).toBeLessThan(a.y2); // Second line: top-right → bottom-left. expect(b.x1).toBeLessThan(b.x2); expect(b.y1).toBeGreaterThan(b.y2); }); it("skips battles whose planet is not in the planet list", () => { const report = makeReport({ battles: [ { id: "11111111-1111-1111-1111-111111111111", planet: 99, shots: 4 }, ], }); const out = buildBattleAndBombingMarkers(report); expect(out.primitives).toHaveLength(0); }); it("emits one yellow ring per damaged bombing and red per wiped", () => { const report = makeReport({ planets: [ { number: 1, name: "A", kind: "local", x: 1, y: 2, size: 50, resources: 0, industryStockpile: 0, materialsStockpile: 0, population: 0, colonists: 0, industry: 0, freeIndustry: 0, production: "MAT", owner: null, }, { number: 2, name: "B", kind: "local", x: 5, y: 6, size: 50, resources: 0, industryStockpile: 0, materialsStockpile: 0, population: 0, colonists: 0, industry: 0, freeIndustry: 0, production: "MAT", owner: null, }, ], bombings: [ { planetNumber: 1, planet: "A", owner: "X", attacker: "Y", production: "MAT", industry: 0, population: 0, colonists: 0, industryStockpile: 0, materialsStockpile: 0, attackPower: 1, wiped: false, }, { planetNumber: 2, planet: "B", owner: "X", attacker: "Y", production: "MAT", industry: 0, population: 0, colonists: 0, industryStockpile: 0, materialsStockpile: 0, attackPower: 1, wiped: true, }, ], }); const out = buildBattleAndBombingMarkers(report); const rings = out.primitives.filter((p) => p.kind === "circle"); expect(rings).toHaveLength(2); expect(rings[0].style.strokeColor).toBe(BOMBING_MARKER_COLOR_DAMAGED); expect(rings[1].style.strokeColor).toBe(BOMBING_MARKER_COLOR_WIPED); }); });