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']);
});