Files
gods-eye-view/src/ui/mapillaryPresentation.test.mjs
T
Said TurkseverandClaude Fable 5.1 f6f8fd8fa8 test(mapillary): provider, source, presentation, markup and QA-harness coverage
scripts/qa-mapillary.mjs drives a real browser through the rail panel,
the keyless gate, coverage, a ready-made feature plan, the viewer and its
expanded dialog at 1440×900 and 1280×800.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-23 15:44:32 +01:00

237 lines
7.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from 'node:assert/strict';
import test from 'node:test';
import {
mapillaryImageUrl,
presentMapillaryPanel,
} from './mapillaryPresentation.js';
function snapshot(overrides = {}) {
const base = {
enabled: false,
keyRequired: false,
planner: true,
plannerModel: 'claude-opus-5',
coverage: {
zoom: null,
kind: null,
filter: { pano: 'all', sinceMs: null },
legend: [
{ key: 'recent', label: 'Recent (≤2 yr)', color: '#05cb63' },
{ key: 'older', label: 'Older', color: '#2e7d5b' },
{ key: 'pano', label: '360°', color: '#ff4fd8' },
{ key: 'selected', label: 'Selected', color: '#00d4ff' },
],
loading: false,
sequences: 0,
hint: '',
error: null,
},
sequence: { selectedId: null, images: 0, loading: false },
features: {
total: 0,
title: '',
loading: false,
progress: { done: 0, tiles: 0 },
counts: [],
truncated: false,
failed: 0,
iconMode: false,
hasBbox: false,
selectedId: null,
bbox: null,
},
street: {
open: false,
follow: false,
loading: false,
error: null,
imageId: null,
position: null,
bearing: null,
isPano: false,
capturedAt: null,
sequenceId: null,
creator: null,
renderMode: 'letterbox',
feature: null,
highlight: null,
},
query: {
busy: false,
stage: 'idle',
prompt: '',
place: null,
answer: '',
error: null,
title: '',
usage: null,
history: [],
},
};
return deepMerge(base, overrides);
}
function deepMerge(target, source) {
const out = { ...target };
for (const [key, value] of Object.entries(source)) {
out[key] =
value &&
typeof value === 'object' &&
!Array.isArray(value) &&
target[key] &&
typeof target[key] === 'object'
? deepMerge(target[key], value)
: value;
}
return out;
}
test('a missing Mapillary token gates every control and names the variable', () => {
const view = presentMapillaryPanel(snapshot({ keyRequired: true }));
assert.equal(view.keyless.visible, true);
assert.match(view.keyless.text, /MAPILLARY_CLIENT_TOKEN/);
assert.equal(view.controlsDisabled, true);
assert.deepEqual(view.status, { text: 'KEY REQUIRED', tone: 'warn' });
});
test('without an Anthropic key the query box explains itself and stays disabled', () => {
const view = presentMapillaryPanel(
snapshot({ planner: false, enabled: true }),
);
assert.equal(view.query.inputDisabled, true);
assert.equal(view.query.suggestionsDisabled, true);
assert.equal(view.query.hintWarn, true);
assert.match(view.query.hint, /Anthropic key/);
assert.ok(view.query.placeholder.length < 40, 'placeholder fits the field');
assert.equal(view.keyless.visible, false);
assert.equal(view.controlsDisabled, false);
});
test('a running query turns ASK into STOP and shows indeterminate progress per stage', () => {
const planning = presentMapillaryPanel(
snapshot({ enabled: true, query: { busy: true, stage: 'planning' } }),
);
assert.equal(planning.query.submitLabel, 'STOP');
assert.equal(planning.query.submitIsStop, true);
assert.deepEqual(planning.progress, {
visible: true,
indeterminate: true,
percent: 0,
label: 'Planning…',
});
assert.equal(planning.status.text, 'AI · PLANNING');
const resolving = presentMapillaryPanel(
snapshot({
enabled: true,
query: { busy: true, stage: 'resolving', place: 'Sacramento, CA' },
}),
);
assert.equal(resolving.progress.label, 'Finding Sacramento, CA…');
const fetching = presentMapillaryPanel(
snapshot({
enabled: true,
query: { busy: true, stage: 'fetching' },
features: { loading: true, progress: { done: 3, tiles: 12 }, total: 410 },
}),
);
assert.equal(fetching.progress.indeterminate, false);
assert.equal(fetching.progress.percent, 25);
assert.equal(fetching.progress.label, '3/12 tiles · 410 found');
});
test('idle copy depends on whether the layer is on', () => {
assert.match(
presentMapillaryPanel(snapshot()).answer,
/Turn on Street Level/,
);
assert.match(
presentMapillaryPanel(snapshot({ enabled: true })).answer,
/Click a green line/,
);
const view = presentMapillaryPanel(
snapshot({ enabled: true, query: { error: 'Planner unavailable' } }),
);
assert.equal(view.error, 'Planner unavailable');
assert.equal(view.answer, '');
});
test('result chips cap at eight and describe the overflow', () => {
const counts = Array.from({ length: 11 }, (_, i) => ({
value: `object--v${i}`,
label: `Class ${i}`,
color: '#fff',
count: 100 - i,
}));
const view = presentMapillaryPanel(
snapshot({ enabled: true, features: { total: 900, counts } }),
);
assert.equal(view.results.visible, true);
assert.equal(view.results.chips.length, 8);
assert.equal(view.results.more.count, 3);
assert.match(view.results.more.title, /Class 8 92, Class 9 91, Class 10 90/);
assert.equal(view.results.all.length, 11);
});
test('legend passes through in the layer’s order', () => {
const view = presentMapillaryPanel(snapshot());
assert.deepEqual(
view.legend.map((entry) => entry.key),
['recent', 'older', 'pano', 'selected'],
);
});
test('the meta line never mixes the visible-sequence count with the selected sequence', () => {
const browsing = presentMapillaryPanel(
snapshot({ enabled: true, coverage: { zoom: 14, sequences: 812 } }),
);
assert.match(browsing.meta, /^812 sequences in view · click a line/);
const selected = presentMapillaryPanel(
snapshot({
enabled: true,
coverage: { zoom: 14, sequences: 812 },
sequence: { selectedId: 'abc', images: 33 },
}),
);
assert.equal(selected.meta, '33 images in this sequence · Esc clears');
assert.doesNotMatch(selected.meta, /sequences in view/);
});
test('viewer caption reads "Image by" left and date right, with a deep link', () => {
const view = presentMapillaryPanel(
snapshot({
enabled: true,
street: {
open: true,
imageId: '1814275685699406',
creator: 'mapfool',
capturedAt: Date.UTC(2023, 9, 8),
bearing: 93.4,
isPano: true,
feature: { label: 'Fire hydrant', imageCount: 5 },
highlight: { count: 2 },
},
}),
);
assert.equal(
view.viewer.captionLeft,
'Fire hydrant · 5 sightings · 2 detections outlined · Image by mapfool',
);
assert.equal(view.viewer.captionRight, '360° · 93° · 2023-10-08');
assert.equal(
view.viewer.link,
'https://www.mapillary.com/app/?pKey=1814275685699406&focus=photo',
);
assert.equal(view.viewer.follow.disabled, false);
assert.equal(view.wantsOpen, true);
});
test('image deep links match the mapillary.com share format', () => {
assert.equal(
mapillaryImageUrl(1814275685699406),
'https://www.mapillary.com/app/?pKey=1814275685699406&focus=photo',
);
assert.equal(mapillaryImageUrl(' '), 'https://www.mapillary.com/app/');
});