From 2cfa2f78f9bf69083112cb4f51f786a8b0999690 Mon Sep 17 00:00:00 2001 From: Kamil Krzywda Date: Wed, 14 Oct 2026 08:32:36 +0000 Subject: [PATCH] Add region-keys.test.ts: 1,796 unique -<4char> keys, geo-id join (v0.19 P1 of #16) --- scripts/region-keys.test.ts | 61 +++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 scripts/region-keys.test.ts diff --git a/scripts/region-keys.test.ts b/scripts/region-keys.test.ts new file mode 100644 index 0000000..577d18c --- /dev/null +++ b/scripts/region-keys.test.ts @@ -0,0 +1,61 @@ +/** + * scripts/region-keys.test.ts — validation for the committed `public/region-keys.json`. + * + * The manifest maps each sub-region's **stable feature id** (from + * `public/regions.geo.json`) to a deterministic, human-readable, globally + * unique region key of the form `-<4char>` (built by + * `scripts/build-region-keys.mjs`). Region-level ownership (v0.19) keys both + * `Nation.territory` and the `RegionMap` on these keys, so the manifest is a + * load-bearing data artifact — this test pins its contract. + * + * Deliberately dependency-free: it reads both JSON files with plain + * `fs`/`JSON.parse` and never imports `@turf`/geojson types (importing one + * pulls `@types/geojson` into the tsc program and breaks `WorldMap.tsx`; see + * the v0.17 changelog note). Mirrors the style of `scripts/regions.test.ts`. + */ +import { readFileSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { describe, expect, it } from 'vitest'; + +const here = path.dirname(fileURLToPath(import.meta.url)); +const root = path.resolve(here, '..'); + +function readJson(rel: string): Record { + return JSON.parse(readFileSync(path.join(root, rel), 'utf8')); +} + +describe('region-keys.json', () => { + const manifest = readJson('public/region-keys.json') as Record; + const geo = readJson('public/regions.geo.json') as { + features?: Array<{ id?: string | number }>; + }; + const keys = Object.values(manifest); + const featureIds = (geo.features ?? []).map((f) => String(f.id)); + + it('has exactly 1,796 entries (one per region)', () => { + expect(Object.keys(manifest)).toHaveLength(1796); + }); + + it('has 1,796 unique keys (no collisions)', () => { + expect(new Set(keys).size).toBe(keys.length); + expect(new Set(keys).size).toBe(1796); + }); + + it('keys each end in -[a-z]{4} and contain at least one hyphen', () => { + const ending = /-[a-z]{4}$/; + for (const k of keys) { + expect(k, `key does not end in -[a-z]{4}: ${k}`).toMatch(ending); + expect(k, `key has no hyphen: ${k}`).toContain('-'); + } + }); + + it('is a stable join: its keys are exactly the regions.geo.json feature ids', () => { + // The manifest is looked up by feature id, so its key set must line up + // 1:1 with the geo file (catches drift if the geo is regenerated without + // rebuilding the keys). + expect(featureIds).toHaveLength(1796); + const manifestIds = Object.keys(manifest); + expect(new Set(manifestIds)).toEqual(new Set(featureIds)); + }); +});