mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
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]>
237 lines
7.0 KiB
JavaScript
237 lines
7.0 KiB
JavaScript
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/');
|
||
});
|