worldmap-viewer

git clone https://git.godosa.eu/worldmap-viewer

master

raw · 6251 bytes

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$/);
});