Files
gods-eye-view/src/view/index.test.mjs
T
Said Turksever 3b6111be06 Open views naming the retired Snow style on Normal
createView threw on style 'snow', so an embed host or a
show_in_gods_eye_view call holding a view from before Snow was retired
was refused outright, camera and layers included. viewFromParams read
style=snow as the recipient's own style, while the app opened the same
link on Normal.

Both now read a retired style as Normal; truly unknown names still
throw. The retired list moves to a browser-free module shared by the
view API, the share codec and visual settings.
2026-10-29 11:34:19 +00:00

154 lines
4.6 KiB
JavaScript

import assert from 'node:assert/strict';
import test from 'node:test';
import {
STYLE_URL_NAMES,
createView,
viewFromParams,
viewToParams,
viewUrl,
} from './index.js';
test('a view round-trips through share-link parameters', () => {
const view = createView({
camera: {
lat: 35.68,
lon: 139.76,
altitude_m: 300000,
heading_deg: 15,
pitch_deg: -45,
},
layers: ['earthquakes', 'ais-live-vessels'],
style: 'retro',
map: 'osm',
follow: { kind: 'aircraft', id: 'AE1234' },
});
const params = viewToParams(view);
assert.equal(params.get('style'), 'crt');
assert.equal(params.get('map'), 'osm');
assert.deepEqual(viewFromParams(params), view);
assert.deepEqual(view.layers, ['ais-live-vessels', 'earthquakes', 'flights']);
const satellite = createView({
camera: { lat: 0, lon: 0 },
follow: { kind: 'satellite', id: '25544' },
});
assert.deepEqual(viewFromParams(viewToParams(satellite)).follow, {
kind: 'satellite',
id: '25544',
});
});
test('views have defaults, bounds, and reject what links cannot carry', () => {
const view = createView({ camera: { lat: 91, lon: 10, altitude_m: 1 } });
assert.deepEqual(view.camera, {
lat: 90,
lon: 10,
altitude_m: 50,
heading_deg: 0,
pitch_deg: -90,
});
assert.equal(view.style, null);
const params = viewToParams(view);
assert.equal(params.has('style'), false);
assert.equal(params.has('l'), false);
assert.throws(() => createView({ camera: {} }), /lat and lon/);
assert.throws(
() => createView({ camera: { lat: 0, lon: 0 }, style: 'sepia' }),
/Unknown view style/,
);
assert.throws(
() =>
createView({
camera: { lat: 0, lon: 0 },
follow: { kind: 'ship', id: '1' },
}),
/Cannot follow a ship/,
);
assert.equal(viewFromParams(new URLSearchParams('style=crt')), null);
assert.equal(
new URL(viewUrl('https://maps.example/app', view)).hash.slice(1),
params.toString(),
);
});
test('views use the same style names as the app share links', async () => {
const source = await import('node:fs/promises').then((fs) =>
fs.readFile(new URL('../sharelink.js', import.meta.url), 'utf8'),
);
assert.match(source, /const STYLE_TO_URL = STYLE_URL_NAMES;/);
assert.equal(STYLE_URL_NAMES.retro, 'crt');
});
test('annotations travel in the link, keeping only what the app draws', async () => {
const { annotationsFromParams } = await import('./index.js');
const view = createView({
camera: { lat: 25, lon: 121 },
annotations: [
{ type: 'pin', target: 'Taipei 101', label: 'Taipei 101', color: 'red' },
{
type: 'arrow',
latitude: 24,
longitude: 119,
toLatitude: 25,
toLongitude: 121,
},
{
type: 'route',
points: [
{ latitude: 1, longitude: 2 },
{ latitude: 3, longitude: 4 },
],
screenX: 0.5,
},
{ type: 'pin' },
{ type: 'sparkle', target: 'x' },
{ type: 'label', latitude: 200, longitude: 0, target: 'Here' },
],
});
assert.equal(view.annotations.length, 4);
assert.equal(view.annotations[2].screenX, undefined);
assert.deepEqual(view.annotations[3], { type: 'label', target: 'Here' });
const params = viewToParams(view);
assert.ok(params.has('an'));
assert.deepEqual(viewFromParams(params), view);
assert.deepEqual(
annotationsFromParams(new URLSearchParams('an=not json')),
[],
);
const many = createView({
camera: { lat: 0, lon: 0 },
annotations: Array.from({ length: 30 }, () => ({
type: 'label',
target: 'x'.repeat(200),
label: 'y'.repeat(120),
})),
});
assert.equal(many.annotations.length, 24);
// Too long for a link: the view keeps them, the link leaves them out.
assert.equal(viewToParams(many).has('an'), false);
});
test('a view naming the retired Snow style opens on Normal', () => {
const camera = { lat: 1, lon: 2 };
assert.equal(createView({ camera, style: 'snow' }).style, 'normal');
const view = viewFromParams(new URLSearchParams('lat=1&lon=2&style=snow'));
assert.equal(view.style, 'normal');
assert.equal(viewToParams(view).get('style'), 'normal');
});
test('inherited and unknown style keys do not corrupt a restored view', () => {
for (const style of [
'constructor',
'__proto__',
'toString',
'hasOwnProperty',
'unknown',
]) {
const view = viewFromParams(
new URLSearchParams({ lat: '10', lon: '20', style, map: 'osm' }),
);
assert.equal(view.style, null);
assert.equal(view.map, 'osm');
assert.equal(view.camera.lat, 10);
}
});