Files
gods-eye-view/src/layers/streetLevel/registry.test.mjs
T
Said TurkseverandClaude Opus 5.5 c4ecb11f75 feat(street-level): KartaView's SINCE is its upload date, noted in the panel
KartaView's server filters by upload date only, so SINCE goes by it
throughout for KartaView: the lines (as before), the photo a click opens
and the cones, the cut-off taken as its UTC day as KartaView's startDate
(passesKartaViewFilter). Providers may declare sinceBasis: 'upload'
(validated); while one is lit the panel shows "KartaView: by upload date"
under the SINCE slider, read out with it. The gate checks the note, and
step 13's picks get 60 s on a loaded machine.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-14 15:02:14 +01:00

126 lines
3.6 KiB
JavaScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { filtersOf, requiresKeyIdFor, validateProviders } from './registry.js';
const def = (overrides = {}) => ({
id: 'mapillary',
name: 'Mapillary',
label: 'MAPILLARY',
requiresKeyId: 'mapillary',
pickPrefix: 'mly:',
colors: { coverage: '#05cb63' },
credit: { html: 'Mapillary' },
create: () => ({}),
...overrides,
});
test('a valid list is frozen in registration order', () => {
const list = validateProviders([
def(),
def({ id: 'panoramax', pickPrefix: 'pnx:', requiresKeyId: null }),
]);
assert.ok(Object.isFrozen(list));
assert.deepEqual(
list.map((p) => p.id),
['mapillary', 'panoramax'],
);
});
test('an empty provider list is valid and has no key requirement', () => {
assert.deepEqual(validateProviders([]), []);
assert.ok(Object.isFrozen(validateProviders([])));
assert.equal(requiresKeyIdFor([]), null);
});
test('non-arrays, duplicate ids and malformed ids are rejected', () => {
assert.throws(() => validateProviders(null), /array/);
assert.throws(() => validateProviders([def(), def()]), /Duplicate/);
assert.throws(() => validateProviders([def({ id: 'Bad Id' })]), /slug/);
});
test('every required field is checked by name', () => {
for (const key of [
'name',
'label',
'pickPrefix',
'colors',
'credit',
'create',
])
assert.throws(
() => validateProviders([def({ [key]: undefined })]),
new RegExp(`lacks ${key}`),
key,
);
assert.throws(() => validateProviders([def({ create: 1 })]), /create/);
assert.throws(() => validateProviders([def({ credit: {} })]), /credit/);
assert.throws(
() => validateProviders([def({ colors: { pano: '#f0f' } })]),
/coverage colour/,
);
assert.throws(
() => validateProviders([def({ pickPrefix: '' })]),
/pick prefix/,
);
});
test('overlapping pick prefixes are rejected either way round', () => {
assert.throws(
() => validateProviders([def(), def({ id: 'other', pickPrefix: 'mly:x' })]),
/overlaps/,
);
assert.throws(
() =>
validateProviders([def({ pickPrefix: 'mly:x' }), def({ id: 'other' })]),
/overlaps/,
);
assert.doesNotThrow(() =>
validateProviders([def(), def({ id: 'other', pickPrefix: 'ml:' })]),
);
});
test('requiresKeyIdFor is the shared key, or null once providers differ', () => {
assert.equal(requiresKeyIdFor([def()]), 'mapillary');
assert.equal(
requiresKeyIdFor([def(), def({ id: 'b', pickPrefix: 'b:' })]),
'mapillary',
);
assert.equal(
requiresKeyIdFor([
def(),
def({ id: 'b', pickPrefix: 'b:', requiresKeyId: null }),
]),
null,
);
assert.equal(
requiresKeyIdFor([
def(),
def({ id: 'b', pickPrefix: 'b:', requiresKeyId: 'google-maps' }),
]),
null,
);
assert.equal(requiresKeyIdFor([def({ requiresKeyId: null })]), null);
});
test('a provider may narrow the filters it honours and pick a view-cone marker', () => {
assert.deepEqual(filtersOf(def()), ['pano', 'since'], 'both by default');
const narrowed = def({
filters: [],
marker: { kind: 'view-cone', color: '#4285f4' },
});
assert.deepEqual(filtersOf(validateProviders([narrowed])[0]), []);
assert.throws(
() => validateProviders([def({ filters: ['colour'] })]),
/filters must list pano or since/,
);
assert.throws(
() => validateProviders([def({ marker: { kind: 'pegman' } })]),
/marker must be/,
);
validateProviders([def({ sinceBasis: 'upload' })]);
assert.throws(
() => validateProviders([def({ sinceBasis: 'shot' })]),
/sinceBasis must be 'capture' or 'upload'/,
);
});