Switch map to sub-national regions (v0.12.0)
CI / build + unit + e2e (pull_request) Successful in 1m18s
CI / build + unit + e2e (pull_request) Successful in 1m18s
Part of #16
This commit is contained in:
+42
-20
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { buildRegionMap } from './map';
|
||||
import { nationColor, regionOwner, type RegionMap } from '../shared/protocol';
|
||||
import { mapFeatureOwner, nationColor, regionOwner, type RegionMap } from '../shared/protocol';
|
||||
import type { GameState } from '../shared/types';
|
||||
|
||||
const state: GameState = {
|
||||
@@ -74,40 +74,62 @@ describe('regionOwner', () => {
|
||||
});
|
||||
|
||||
/**
|
||||
* #6 regression guard. The click/hover handlers resolve the owning nation from a
|
||||
* map feature. MapLibre repacks a GeoJSON source's top-level `id` (the ISO_A3
|
||||
* string) into a *numeric index* when it stores the source, so a feature the map
|
||||
* hands back at interaction time has a number for `id` and the ISO_A3 only lives
|
||||
* in `properties.regionId`. This test pins the exact data shape and proves the
|
||||
* fix: resolution via `properties.regionId` works, while the pre-fix path that
|
||||
* read `feature.id` does not.
|
||||
* #6 regression guard (sub-national map). 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`. The
|
||||
* choropleth is now sub-national: a feature's id is a sub-region code
|
||||
* (`DEU-03`) and its owner lives at the *parent country* (`DEU`), which the
|
||||
* client stamps into `properties.parent` (the RegionMap is keyed by that
|
||||
* country code, never the sub-region id). This pins the exact data shape and
|
||||
* proves the resolution: `mapFeatureOwner` works via `properties.parent`, while
|
||||
* the two wrong paths (the repacked numeric `feature.id`, and the sub-region
|
||||
* `regionId`, which is not a RegionMap key) do not.
|
||||
*/
|
||||
describe('#6 — owner must be resolved from properties.regionId, not the repacked feature.id', () => {
|
||||
// The shape MapLibre returns for a source feature after setData(): `id` is a
|
||||
// number (here 0, as observed in the live map), and the ISO_A3 is preserved
|
||||
// only in properties (the client stamps it as `regionId`).
|
||||
describe('#6 — sub-region owner must be resolved from properties.parent, not the sub-region id / 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 code) and `parent` (the ISO_A3 country).
|
||||
const repackedFeature = {
|
||||
id: 0,
|
||||
properties: { name: 'DE', regionId: 'DEU' },
|
||||
properties: { name: 'Germany R03', regionId: 'DEU-03', parent: 'DEU' },
|
||||
};
|
||||
|
||||
it('resolves the owner from properties.regionId (the fix)', () => {
|
||||
const code = typeof repackedFeature.properties.regionId === 'string' ? repackedFeature.properties.regionId : '';
|
||||
expect(regionOwner(regions, code)).toEqual({
|
||||
it('resolves the owner from properties.parent (the fix)', () => {
|
||||
expect(mapFeatureOwner(regions, repackedFeature)).toEqual({
|
||||
color: nationColor('de'),
|
||||
owner: 'DE',
|
||||
ownerId: 'de',
|
||||
});
|
||||
});
|
||||
|
||||
it('the old path (feature.id as a string key) does NOT resolve — the bug', () => {
|
||||
// String(0) === '0' is not a valid ISO_A3 key, so the pre-fix lookup failed
|
||||
// and the click popup reported "No owner" even for owned regions.
|
||||
expect(regions[String(repackedFeature.id)]).toBeUndefined();
|
||||
it('the sub-region regionId is NOT a RegionMap key — resolving from it fails', () => {
|
||||
// 'DEU-03' is a sub-region id; the RegionMap is keyed by the parent 'DEU'.
|
||||
expect(regionOwner(regions, repackedFeature.properties.regionId)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('the repacked numeric feature.id does NOT resolve — the 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();
|
||||
});
|
||||
});
|
||||
|
||||
describe('mapFeatureOwner', () => {
|
||||
it('returns undefined for a missing feature, missing parent, or non-string parent', () => {
|
||||
expect(mapFeatureOwner(regions, null)).toBeUndefined();
|
||||
expect(mapFeatureOwner(regions, undefined)).toBeUndefined();
|
||||
expect(mapFeatureOwner(regions, {})).toBeUndefined();
|
||||
expect(mapFeatureOwner(regions, { properties: {} })).toBeUndefined();
|
||||
expect(mapFeatureOwner(regions, { properties: { parent: 42 } })).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns undefined for a parent country nobody owns', () => {
|
||||
expect(mapFeatureOwner(regions, { properties: { parent: 'FRA' } })).toBeUndefined();
|
||||
expect(mapFeatureOwner(regions, { properties: { parent: 'USA' } })).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('nationColor', () => {
|
||||
it('is deterministic per id and distinct across different ids', () => {
|
||||
expect(nationColor('uk')).toBe(nationColor('uk'));
|
||||
|
||||
Reference in New Issue
Block a user