Files
History/scripts/region-keys.test.ts
T
kamilkrzywda f6ad554823
CI / build + unit + e2e (pull_request) Successful in 2m34s
Re-coarsen RU-ARK Arctic-coast band into RU-PEO + RU-YAM (#41)
#41: re-partition the RU-ARK mainland's 28-part lat>=68 Arctic-coast band
into two new regions via a new turf-free, idempotent pipeline stage
scripts/split-arkhangel-band.mjs (npm run split:regions:band), mirroring the
#31 split:

- RU-PEO (Pechora, 13 parts, ~37,678 km2) -> key pechora-qwhs
- RU-YAM (Yamal, 15 parts, ~51,722 km2) -> key yamal-gosw
- RU-ARK keeps its id/name/key arkhangel-sk-lhbk (ownership-safe); the 5-part
  lat<68 core (~305,229 km2) stays RU-ARK.

Pure re-partition: no geometry moved (byte-identical union). The two new
features are inserted immediately after RU-ARK-SZ. Manifests regenerated with
the real scripts (1,798 -> 1,800 keys + adjacency; adjacency pairs 4,471 ->
4,473). Fixed-point dataset unchanged except feature count (11,794 parts /
11,795 rings / 164,801 verts / 1 KZ-KZY hole). Four dataset/manifest tests
updated (RUS 82 -> 84 sub-regions). Version 0.25.0 -> 0.26.0.

Fixes #41
2026-10-14 19:26:49 +00:00

77 lines
3.4 KiB
TypeScript

/**
* 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 `<name-slug>-<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<string, unknown> {
return JSON.parse(readFileSync(path.join(root, rel), 'utf8'));
}
describe('region-keys.json', () => {
const manifest = readJson('public/region-keys.json') as Record<string, string>;
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,800 entries (one per region)', () => {
expect(Object.keys(manifest)).toHaveLength(1800);
});
it('has 1,800 unique keys (no collisions)', () => {
expect(new Set(keys).size).toBe(keys.length);
expect(new Set(keys).size).toBe(1800);
});
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("keeps RU-ARK's key stable across the #31 + #41 splits and mints the island and band keys", () => {
// v0.25 (#31) split RU-ARK into main + RU-ARK-NE + RU-ARK-SZ, and v0.26
// (#41) repartitioned the mainland into the core (RU-ARK) + RU-PEO +
// RU-YAM. The MAIN feature keeps its id (and therefore its key — FNV-1a
// of the id), which is what keeps existing Nation.territory / SQLite
// ownership valid; the new features get fresh keys derived the same way
// from their ids (no within-country name-collision salt needed — the
// slugs are unique).
expect(manifest['RU-ARK']).toBe('arkhangel-sk-lhbk'); // unchanged since v0.19
expect(manifest['RU-ARK-NE']).toBe('new-earth-eikt');
expect(manifest['RU-ARK-SZ']).toBe('severnaya-zemlya-awqp');
expect(manifest['RU-PEO']).toBe('pechora-qwhs');
expect(manifest['RU-YAM']).toBe('yamal-gosw');
});
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(1800);
const manifestIds = Object.keys(manifest);
expect(new Set(manifestIds)).toEqual(new Set(featureIds));
});
});