import { describe, expect, it } from 'vitest'; import { buildRegionMap } from './map'; import { mapFeatureOwner, nationColor, regionOwner, type RegionMap } from '../shared/protocol'; import type { GameState } from '../shared/types'; // As of v0.19 territory is a list of **sub-region ids** (the RegionMap keys), // not whole-country ISO_A3 codes. const state: GameState = { id: 'g', presetId: 'p', presetTitle: 'P', playerNationId: 'uk', turn: 1, date: '1939-09-01', nations: { uk: { id: 'uk', name: 'UK', type: 'major', stats: { military: 80, economy: 70, stability: 60, population: 50 }, territory: ['GB-01', 'GB-02'], relations: {}, }, de: { id: 'de', name: 'DE', type: 'major', stats: { military: 90, economy: 70, stability: 70, population: 90 }, territory: ['DE-01'], relations: {}, }, }, events: [], pending: [], version: 1, }; // The RegionMap built from the fixture: the authoritative sub-region id → owner // source. const regions: RegionMap = buildRegionMap(state); describe('buildRegionMap', () => { it('maps each sub-region id to its owner + that nation\'s stable color', () => { const m = buildRegionMap(state); expect(m['GB-01']).toEqual({ color: nationColor('uk'), owner: 'UK', ownerId: 'uk' }); expect(m['GB-02']).toEqual({ color: nationColor('uk'), owner: 'UK', ownerId: 'uk' }); expect(m['DE-01']).toEqual({ color: nationColor('de'), owner: 'DE', ownerId: 'de' }); // a sub-region nobody owns is simply absent expect(m['FR-57']).toBeUndefined(); }); }); describe('regionOwner', () => { it('resolves an owned sub-region id to its owner record', () => { expect(regionOwner(regions, 'GB-01')).toEqual({ color: nationColor('uk'), owner: 'UK', ownerId: 'uk', }); expect(regionOwner(regions, 'DE-01')).toEqual({ color: nationColor('de'), owner: 'DE', ownerId: 'de', }); }); it('returns undefined for an unowned region (no RegionMap entry)', () => { expect(regionOwner(regions, 'FR-57')).toBeUndefined(); expect(regionOwner(regions, 'US-01')).toBeUndefined(); }); it('returns undefined for an empty or missing id', () => { expect(regionOwner(regions, '')).toBeUndefined(); expect(regionOwner(regions, null)).toBeUndefined(); expect(regionOwner(regions, undefined)).toBeUndefined(); }); }); /** * #6 regression guard (sub-national map, v0.19 region-level ownership). The * click/hover handlers resolve the owning nation from a rendered map feature. * MapLibre repacks a GeoJSON source's top-level `id` into a *numeric index* * when it stores the source, so a feature the map hands back at interaction * time has a number for `id`. Since v0.19 ownership is at **sub-region** * granularity: a feature's owner lives at its **own sub-region id** (e.g. * `DE-01`), which the client stamps into `properties.regionId` (the RegionMap * is keyed by that id). This pins the exact data shape and proves the * resolution: `mapFeatureOwner` works via `properties.regionId`, while the * repacked numeric `feature.id` path does not. */ describe('#6 — region owner must be resolved from properties.regionId, never the repacked feature.id', () => { // The shape MapLibre returns for a sub-region feature after setData(): `id` // is a number (repacked), and the stable identifiers live only in properties // — `regionId` (the sub-region id, the RegionMap key) and `parent` (the // static parent country's ISO_A3). const repackedFeature = { id: 0, properties: { name: 'Rhineland', regionId: 'DE-01', parent: 'DEU' }, }; it('resolves the owner from properties.regionId (the fix)', () => { expect(mapFeatureOwner(regions, repackedFeature)).toEqual({ color: nationColor('de'), owner: 'DE', ownerId: 'de', }); }); it('the repacked numeric feature.id does NOT resolve — the original bug', () => { // String(0) === '0' is not a valid key, so the pre-fix lookup failed and // reported "No owner" even for owned regions. expect(regionOwner(regions, String(repackedFeature.id))).toBeUndefined(); }); it('a sub-region id nobody owns does NOT resolve', () => { expect(regionOwner(regions, 'FR-57')).toBeUndefined(); }); }); describe('mapFeatureOwner', () => { it('returns undefined for a missing feature, missing regionId, or non-string regionId', () => { expect(mapFeatureOwner(regions, null)).toBeUndefined(); expect(mapFeatureOwner(regions, undefined)).toBeUndefined(); expect(mapFeatureOwner(regions, {})).toBeUndefined(); expect(mapFeatureOwner(regions, { properties: {} })).toBeUndefined(); expect(mapFeatureOwner(regions, { properties: { regionId: 42 } })).toBeUndefined(); expect(mapFeatureOwner(regions, { properties: { regionId: '' } })).toBeUndefined(); }); it('returns undefined for a sub-region nobody owns', () => { expect(mapFeatureOwner(regions, { properties: { regionId: 'FR-57' } })).toBeUndefined(); expect(mapFeatureOwner(regions, { properties: { regionId: 'US-01' } })).toBeUndefined(); }); }); describe('nationColor', () => { it('is deterministic per id and distinct across different ids', () => { expect(nationColor('uk')).toBe(nationColor('uk')); expect(nationColor('uk')).not.toBe(nationColor('de')); // always a valid HSL string expect(nationColor('uk')).toMatch(/^hsl\(\d+ 58% 46%\)$/); }); });