diff options
Diffstat (limited to 'viewer/tests/overlays.test.mjs')
| -rw-r--r-- | viewer/tests/overlays.test.mjs | 147 |
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$/); +}); |
