import './_planet.mjs'; import { test } from 'node:test'; import assert from 'node:assert/strict'; import { fakeRenderer } from './importmap.mjs'; const { FlatView, MAX_ZOOM } = await import('../js/flatmap.js'); const near = (a, b, eps) => assert.ok(Math.abs(a - b) <= eps, `${a} vs ${b}`); const ev = (type, props) => Object.assign(new Event(type, { cancelable: true }), { button: 0, pointerId: 1, deltaMode: 0, ...props }); function flat(kind, st = { lat: 20, lon: 30, zoom: 3 }) { const r = fakeRenderer(); const v = new FlatView(r, kind, () => {}); v.resize(800, 400); v.setState(st); v.activate(); return { v, canvas: r.domElement }; } for (const kind of ['equal_earth', 'plate_carree']) { test(`${kind}: state round trip, zoom clamped`, () => { const s = flat(kind).v.getState(); near(s.lat, 20, 1e-9); near(s.lon, 30, 1e-9); assert.equal(s.zoom, 3); assert.equal(flat(kind, { lat: 0, lon: 0, zoom: 1e9 }).v.getState().zoom, MAX_ZOOM); assert.equal(MAX_ZOOM, 20000); assert.equal(flat(kind, { lat: 0, lon: 0, zoom: 0 }).v.getState().zoom, 1); }); test(`${kind}: pick and toScreen agree, incl. near the seam and poles`, () => { const { v } = flat(kind, { lat: 0, lon: 0, zoom: 1 }); for (const [lat, lon] of [[0, 0], [45, 179.9], [-89, -120], [60, -179.9]]) { const q = v.toScreen(lat, lon), p = v.pick(q.x, q.y); near(p.lat, lat, 1e-6); near(p.lon, lon, 1e-6); } }); test(`${kind}: wheel zoom keeps the point under the cursor`, () => { const { v, canvas } = flat(kind, { lat: 10, lon: 10, zoom: 2 }); const before = v.pick(600, 120); canvas.dispatchEvent(ev('wheel', { deltaY: -300, clientX: 600, clientY: 120 })); assert.ok(v.getState().zoom > 2); const after = v.pick(600, 120); near(after.lat, before.lat, 1e-6); near(after.lon, before.lon, 1e-6); }); test(`${kind}: drag pans with the cursor; an inactive view ignores input`, () => { const { v, canvas } = flat(kind, { lat: 0, lon: 0, zoom: 4 }); const p = v.pick(400, 200); canvas.dispatchEvent(ev('pointerdown', { clientX: 400, clientY: 200 })); canvas.dispatchEvent(ev('pointermove', { clientX: 300, clientY: 200 })); canvas.dispatchEvent(ev('pointerup', { clientX: 300, clientY: 200 })); const q = v.pick(300, 200); near(q.lat, p.lat, 1e-6); near(q.lon, p.lon, 1e-6); v.deactivate(); const s = v.getState(); canvas.dispatchEvent(ev('wheel', { deltaY: -300, clientX: 10, clientY: 10 })); assert.deepEqual(v.getState(), s); }); } test('equal earth: outside the outline picks null', () => { assert.equal(flat('equal_earth', { lat: 0, lon: 0, zoom: 1 }).v.pick(2, 2), null); }); test('long lines are drawn along the great circle (bowing poleward), not straight in the projection', () => { const { v } = flat('plate_carree', { lat: 0, lon: 0, zoom: 1 }); v.setLines('m', [[{ lat: 40, lon: -60 }, { lat: 40, lon: 60 }]], 0xffffff); const pos = v.lines.objects.get('m').geometry.attributes.position.array; assert.ok(pos.length / 6 > 10, `${pos.length / 6} segments`); const maxLat = Math.max(...pos.filter((_, i) => i % 3 === 1)) * 180 / Math.PI; assert.ok(maxLat > 50, `peak latitude ${maxLat}`); }); test('equal earth: the centre cannot leave the outline, so the saved state reproduces the view', () => { const { v, canvas } = flat('equal_earth', { lat: 60, lon: 170, zoom: 8 }); canvas.dispatchEvent(ev('pointerdown', { clientX: 400, clientY: 200 })); canvas.dispatchEvent(ev('pointermove', { clientX: 100, clientY: 200 })); // pan east past lon 180 canvas.dispatchEvent(ev('pointerup', { clientX: 100, clientY: 200 })); const s = v.getState(), before = v.toWorld(400, 200); v.setState(s); const after = v.toWorld(400, 200); near(after.X, before.X, 1e-9); near(after.Y, before.Y, 1e-9); });