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