aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/viewer/tests/regions.test.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'viewer/tests/regions.test.mjs')
-rw-r--r--viewer/tests/regions.test.mjs82
1 files changed, 82 insertions, 0 deletions
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']);
+});