aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/viewer/tests/overlays.test.mjs
diff options
context:
space:
mode:
authorgodosa <godosa@godosa.eu>2026-10-07 00:14:38 +0200
committergodosa <godosa@godosa.eu>2026-10-07 00:14:38 +0200
commit3443c1c65e9f1753e1e656b35d08416c1fa298f2 (patch)
tree4e43236f460145a4d75d1b4616dcb7aa6ef08f51 /viewer/tests/overlays.test.mjs
downloadworldmap-viewer-3443c1c65e9f1753e1e656b35d08416c1fa298f2.tar.gz
worldmap-viewer-3443c1c65e9f1753e1e656b35d08416c1fa298f2.zip
worldmap-viewer: initial public history
Diffstat (limited to 'viewer/tests/overlays.test.mjs')
-rw-r--r--viewer/tests/overlays.test.mjs147
1 files changed, 147 insertions, 0 deletions
diff --git a/viewer/tests/overlays.test.mjs b/viewer/tests/overlays.test.mjs
new file mode 100644
index 0000000..d6f3d7c
--- /dev/null
+++ b/viewer/tests/overlays.test.mjs
@@ -0,0 +1,147 @@
+import './_planet.mjs';
+import { test } from 'node:test';
+import assert from 'node:assert/strict';
+
+const fc = { type: 'FeatureCollection', features: [
+ { type: 'Feature', properties: { order: 1 }, geometry: { type: 'LineString', coordinates: [[0, 0], [1, 1]] } },
+ { type: 'Feature', properties: { order: 4 }, geometry: { type: 'LineString', coordinates: [[5, 5], [6, 6]] } },
+] };
+let fetches = 0;
+globalThis.fetch = async () => { fetches++; return new Response(JSON.stringify(fc), { status: 200 }); };
+const { Overlays } = await import('../js/overlays.js');
+
+const flush = () => new Promise(r => setTimeout(r, 0));
+const box = { replaceChildren() {} };
+const defs = [{ id: 'coast', name: 'Coastline', file: '/data/geo/coast.geojson' },
+ { id: 'rivers', name: 'Rivers', file: '/data/geo/rivers.geojson' }];
+const fakeView = () => ({ lines: new Map(), setLines(id, l) { this.lines.set(id, l); },
+ removeLines(id) { this.lines.delete(id); }, toScreen: () => null });
+
+test('enabled overlays reach the view; rivers show the orders that stay apart at the scale', async () => {
+ const o = new Overlays(defs, box), v = fakeView();
+ o.setEnabled(['coast', 'rivers', 'graticule']);
+ o.attach(v);
+ await flush();
+ assert.deepEqual([...v.lines.keys()].sort(), ['coast', 'graticule', 'rivers']);
+ assert.equal(v.lines.get('rivers').length, 1); // no scale yet: the largest order only
+ o.setScale(0.01); // a first-order segment (≈ 157 km) spans far more than 8 px
+ await flush();
+ assert.equal(v.lines.get('rivers').length, 2);
+ o.setScale(1000);
+ await flush();
+ assert.equal(v.lines.get('rivers').length, 1);
+ assert.equal(v.lines.get('graticule').length, 18);
+ o.setEnabled(['coast']);
+ await flush();
+ assert.deepEqual([...v.lines.keys()], ['coast']);
+});
+
+test('GeoJSON fetched once; a newly attached view gets everything; extras always shown', async () => {
+ fetches = 0;
+ const o = new Overlays(defs, box), a = fakeView(), b = fakeView();
+ o.setEnabled(['coast']);
+ o.attach(a);
+ await flush();
+ o.setExtra('measure', [[{ lat: 0, lon: 0 }, { lat: 1, lon: 1 }]]);
+ o.attach(b);
+ await flush();
+ assert.equal(fetches, 1);
+ assert.deepEqual([...b.lines.keys()].sort(), ['coast', 'measure']);
+ o.setExtra('measure', []);
+ assert.deepEqual(b.lines.get('measure'), []);
+});
+
+test('list offers meta overlays plus graticule, refined regions, vents and pins', () => {
+ assert.deepEqual(new Overlays(defs, box).list().map(o => o.id), ['coast', 'rivers', 'graticule', 'regions', 'vents', 'pins']);
+});
+
+test('vent markers carry a title and class, show only with their overlay, and restyle when re-clustered', async () => {
+ const { FakeEl, installFakeDom } = await import('./fakedom.mjs');
+ installFakeDom();
+ const mbox = new FakeEl('div'), o = new Overlays(defs, mbox);
+ o.attach({ ...fakeView(), toScreen: () => ({ x: 1, y: 2 }) });
+ o.setMarkers('vent', [{ lat: 1, lon: 2, label: '', cls: 't0', title: 'black smoker' }]);
+ const [m] = mbox.kids;
+ assert.match(m.className, /marker vent t0/);
+ assert.equal(m.attrs.title, 'black smoker');
+ assert.equal(m.style.display, 'none', 'vents overlay off');
+ o.setEnabled(['vents']);
+ assert.equal(m.style.display, '');
+ o.setMarkers('vent', [{ lat: 1, lon: 2, label: '', cls: 't2', title: 'diffuse' }]);
+ assert.match(mbox.kids[0].className, /t2/);
+});
+
+test('refined-region outlines draw while their overlay is on', () => {
+ const o = new Overlays(defs, box), v = fakeView();
+ o.attach(v);
+ const line = [{ lat: 1, lon: 2 }, { lat: 3, lon: 4 }, { lat: 1, lon: 2 }];
+ o.setRegions([line]);
+ assert.ok(!v.lines.has('regions'), 'off: nothing drawn');
+ o.setEnabled(['regions']);
+ assert.deepEqual(v.lines.get('regions'), [line]);
+ o.setEnabled([]);
+ assert.ok(!v.lines.has('regions'));
+});
+
+test('asks the app to redraw after lines change, including after async loads', async () => {
+ let n = 0;
+ const o = new Overlays(defs, box, () => n++), v = fakeView();
+ o.setEnabled(['coast']);
+ o.attach(v);
+ const before = n;
+ await flush();
+ assert.ok(v.lines.has('coast'));
+ assert.ok(n > before, 'redraw requested after the GeoJSON arrived');
+ const k = n;
+ o.setEnabled([]);
+ assert.ok(n > k, 'redraw requested after removal');
+});
+
+test('markers with onClick are clickable; the rest let the pointer through', async () => {
+ const { FakeEl, installFakeDom } = await import('./fakedom.mjs');
+ installFakeDom();
+ let hit = null;
+ const mbox = new FakeEl('div'), o = new Overlays(defs, mbox);
+ o.attach(fakeView());
+ o.setMarkers('pin', [{ lat: 1, lon: 2, label: 'A', onClick: () => { hit = 'A'; } }]);
+ o.setMarkers('measure', [{ lat: 3, lon: 4, label: '1' }]);
+ const [pin, meas] = mbox.kids;
+ assert.match(pin.className, /clickable/);
+ assert.doesNotMatch(meas.className, /clickable/);
+ pin.fire('click');
+ assert.equal(hit, 'A');
+});
+
+test('suppressed overlays leave the view and come back', async () => {
+ const o = new Overlays(defs, box), v = fakeView();
+ o.setEnabled(['coast']);
+ o.attach(v);
+ await flush();
+ o.setSuppressed(['coast', 'lakes']);
+ assert.equal(v.lines.has('coast'), false);
+ o.setSuppressed([]);
+ await flush();
+ assert.equal(v.lines.has('coast'), true);
+});
+
+test('moving a marker keeps its DOM node (sky play moves the sun 20× a second)', async () => {
+ const { FakeEl, installFakeDom } = await import('./fakedom.mjs');
+ installFakeDom();
+ const mbox = new FakeEl('div'), o = new Overlays(defs, mbox);
+ o.attach(fakeView());
+ o.setMarkers('sun', [{ lat: 1, lon: 2, label: '☉' }]);
+ const node = mbox.kids[0];
+ o.setMarkers('sun', [{ lat: 3, lon: 4, label: '☉' }]);
+ assert.equal(mbox.kids[0], node);
+ assert.deepEqual([o.markers[0].lat, o.markers[0].lon], [3, 4]);
+});
+
+test('new definitions drop the loaded overlays (another era)', async () => {
+ const { FakeEl, installFakeDom } = await import('./fakedom.mjs');
+ installFakeDom();
+ const o = new Overlays([{ id: 'coast', name: 'Coast', file: '/data/geo/coast.geojson?era=a' }], new FakeEl('div'));
+ o.cache.set('coast', Promise.resolve([]));
+ o.setDefs([{ id: 'coast', name: 'Coast', file: '/data/geo/coast.geojson?era=b' }]);
+ assert.equal(o.cache.size, 0);
+ assert.match(o.defs[0].file, /era=b$/);
+});