From 3443c1c65e9f1753e1e656b35d08416c1fa298f2 Mon Sep 17 00:00:00 2001 From: godosa Date: Wed, 7 Oct 2026 00:14:38 +0200 Subject: worldmap-viewer: initial public history --- viewer/tests/regions.test.mjs | 82 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 viewer/tests/regions.test.mjs (limited to 'viewer/tests/regions.test.mjs') diff --git a/viewer/tests/regions.test.mjs b/viewer/tests/regions.test.mjs new file mode 100644 index 0000000..d7a9d65 --- /dev/null +++ b/viewer/tests/regions.test.mjs @@ -0,0 +1,82 @@ +import './_planet.mjs'; +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { closesOutline, crossesItself, regionPayload, statusText, outlineLines } from '../js/regions.js'; + +test('clicking near the first point closes the outline', () => { + assert.equal(closesOutline({ x: 100, y: 100 }, { x: 108, y: 105 }), true); + assert.equal(closesOutline({ x: 100, y: 100 }, { x: 130, y: 100 }), false); + assert.equal(closesOutline(null, { x: 1, y: 1 }), false); +}); + +test('self-crossing outlines are refused; unwraps across ±180°', () => { + const sq = [[0, 0], [0, 2], [2, 2], [2, 0]].map(([lat, lon]) => ({ lat, lon })); + const bow = [[0, 0], [2, 2], [0, 2], [2, 0]].map(([lat, lon]) => ({ lat, lon })); + assert.equal(crossesItself(sq), false); + assert.equal(crossesItself(bow), true); + const dateline = [[0, 179], [0, -179], [2, -179], [2, 179]].map(([lat, lon]) => ({ lat, lon })); + assert.equal(crossesItself(dateline), false); +}); + +test('payload, status text, outline lines', () => { + assert.deepEqual(regionPayload(' Gorge ', [{ lat: 1, lon: 2 }, { lat: 3, lon: 4 }, { lat: 5, lon: 6 }]), + { name: 'Gorge', outline: [[1, 2], [3, 4], [5, 6]] }); + assert.equal(statusText({ state: 'building', stage: 'erosion', fraction: 0.456 }), 'Refining… 46% (erosion)'); + assert.equal(statusText({ state: 'ready' }), 'Refined areas are up to date'); + assert.match(statusText({ state: 'failed', error: 'boom' }), /failed.*boom/i); + assert.equal(statusText({ state: 'queued' }), 'Queued…'); + const lines = outlineLines([{ outline: [[1, 2], [3, 4], [5, 6]] }]); + assert.equal(lines.length, 1); + assert.deepEqual(lines[0].at(-1), { lat: 1, lon: 2 }, 'closed'); +}); + +test('status shows the tiles being saved ahead after a build', async () => { + const { statusText } = await import('../js/regions.js'); + assert.equal(statusText({ state: 'ready', tiles: { done: 45, total: 100 } }), 'Refined areas are up to date · saving tiles 45%'); + assert.equal(statusText({ state: 'ready', tiles: { done: 100, total: 100 } }), 'Refined areas are up to date'); + assert.equal(statusText({ state: 'idle', tiles: { done: 3, total: 10 } }), 'Saving region tiles 30%'); +}); + +test('outline area on a 2× Earth-radius world, also across ±180°', async () => { + const { outlineAreaKm2 } = await import('../js/regions.js'); + const box = (la0, lo0, d) => [{ lat: la0, lon: lo0 }, { lat: la0, lon: lo0 + d }, { lat: la0 + d, lon: lo0 + d }, { lat: la0 + d, lon: lo0 }]; + const want = 12742 ** 2 * (10 * Math.PI / 180) * Math.sin(10 * Math.PI / 180); + assert.ok(Math.abs(outlineAreaKm2(box(0, 20, 10)) / want - 1) < 1e-9); + assert.ok(Math.abs(outlineAreaKm2(box(0, 175, 10).map(p => ({ ...p, lon: p.lon > 180 ? p.lon - 360 : p.lon }))) / want - 1) < 1e-9); + assert.ok(Math.abs(outlineAreaKm2([...box(0, 20, 10)].reverse()) / want - 1) < 1e-9, 'either winding'); +}); + +test('tiles switch whenever the server names a different URL', async () => { + const { needsTileSwitch } = await import('../js/regions.js'); + assert.equal(needsTileSwitch({ url: '/tiles/a/x' }, { url: '/tiles/b/x' }), true); + assert.equal(needsTileSwitch({ url: '/tiles/a/x' }, { url: '/tiles/a/x' }), false); + assert.equal(needsTileSwitch({ url: '/tiles/a/x' }, undefined), false); +}); + +test('outline clicks: add, close on the first point, and a double-click (a second click on the last point) closes', async () => { + const { outlineClick } = await import('../js/regions.js'); + const first = { x: 100, y: 100 }, last = { x: 300, y: 200 }; + assert.equal(outlineClick(null, null, { x: 5, y: 5 }, 0), 'add'); + assert.equal(outlineClick(first, last, { x: 400, y: 400 }, 3), 'add'); + assert.equal(outlineClick(first, last, { x: 103, y: 99 }, 3), 'close'); + assert.equal(outlineClick(first, last, { x: 301, y: 202 }, 3), 'close', 'double-click on the last point'); + assert.equal(outlineClick(first, last, { x: 301, y: 202 }, 2), 'ignore', 'too few points: no duplicate either'); +}); + +test('a poll tells which era it asked about: an answer for the era before a switch is not taken for this one', async () => { + const { FakeEl, installFakeDom } = await import('./fakedom.mjs'); + installFakeDom(); + const { RegionsPanel } = await import('../js/regionspanel.js'); + let q = '?era=a', release; + const seen = []; + globalThis.fetch = () => new Promise(r => { release = () => r({ ok: true, status: 200, + json: async () => ({ regions: [], status: { state: 'ready' }, tiles: { url: 'a' } }) }); }); + const p = new RegionsPanel(new FakeEl('section'), { onDraft() {}, onDrawing() {}, onRegions() {}, + onStatus: (s, t, was, asked) => seen.push(asked), eraQuery: () => q }); + const done = p.poll(); + q = '?era=b'; + release(); + await done; + clearTimeout(p.timer); + assert.deepEqual(seen, ['?era=a']); +}); -- cgit