raw · 6251 bytes
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 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$/); }); |