diff --git a/scripts/format-scope.json b/scripts/format-scope.json index 53d6a5b67..8cb8b89a8 100644 --- a/scripts/format-scope.json +++ b/scripts/format-scope.json @@ -937,7 +937,6 @@ "src/testSupport/panoramaxFixtures.mjs", "src/layers/streetLevel/photoViewer.test.mjs", "src/layers/streetLevel/limiter.test.mjs", - "src/layers/streetLevel/panorama.test.mjs", "src/layers/streetLevel/lineCoverage.test.mjs", "src/testSupport/kartaViewFakes.mjs", "src/testSupport/googleStreetViewFakes.mjs", diff --git a/scripts/qa-street-level.mjs b/scripts/qa-street-level.mjs index 9c8c55072..7c131a220 100644 --- a/scripts/qa-street-level.mjs +++ b/scripts/qa-street-level.mjs @@ -1276,7 +1276,10 @@ async function main() { }, ) .catch(() => null); - assert.ok(aimed, 'the line is pickable over the other’s pictures'); + assert.ok( + aimed, + 'the line is pickable over the other’s pictures', + ); at = await aimed.jsonValue(); } const parked = await camera(); diff --git a/src/layers/streetLevel/limiter.test.mjs b/src/layers/streetLevel/limiter.test.mjs index c9fabef68..1137fe2f6 100644 --- a/src/layers/streetLevel/limiter.test.mjs +++ b/src/layers/streetLevel/limiter.test.mjs @@ -70,7 +70,11 @@ test('a task already aborted when its turn comes is skipped', async () => { const gate = createLimiter(1); const first = held(log, 'a'); const running = gate.run(first.task); - const signal = { aborted: false, reason: new Error('gone'), addEventListener() {} }; + const signal = { + aborted: false, + reason: new Error('gone'), + addEventListener() {}, + }; const queued = gate.run(held(log, 'b').task, { signal }); signal.aborted = true; first.finish(); diff --git a/src/layers/streetLevel/lineCoverage.test.mjs b/src/layers/streetLevel/lineCoverage.test.mjs index db4b5449f..51ac366c3 100644 --- a/src/layers/streetLevel/lineCoverage.test.mjs +++ b/src/layers/streetLevel/lineCoverage.test.mjs @@ -217,17 +217,29 @@ test('overview points take the provider’s size, each its own', async (t) => { test('a point budget skips lines past it, and a shorter one later may still fit', async (t) => { const { state, coverage } = setup(t, { - tuning: { ...TUNING, maxTiles: 1, maxSequences: 10, maxPoints: 5, keepOrder: true }, + tuning: { + ...TUNING, + maxTiles: 1, + maxSequences: 10, + maxPoints: 5, + keepOrder: true, + }, decoded: (centre) => { const line = (id, n) => ({ id, parts: [ - Array.from({ length: n }, (_, i) => [centre.lon - 0.001 + i * 0.0001, centre.lat]), + Array.from({ length: n }, (_, i) => [ + centre.lon - 0.001 + i * 0.0001, + centre.lat, + ]), ], capturedAt: 0, isPano: false, }); - return { sequences: [line('three', 3), line('four', 4), line('two', 2)], overview: [] }; + return { + sequences: [line('three', 3), line('four', 4), line('two', 2)], + overview: [], + }; }, }); coverage.refresh(); diff --git a/src/layers/streetLevel/photoViewer.js b/src/layers/streetLevel/photoViewer.js index 94da38f47..b4fb67398 100644 --- a/src/layers/streetLevel/photoViewer.js +++ b/src/layers/streetLevel/photoViewer.js @@ -178,6 +178,8 @@ export function createPhotoViewer({ let documentOf = null; /** A context loss's look direction, for the reopen of that photo. */ let restoreView = null; + /** The panel's viewer box, where Esc is listened for (null: the root). */ + let escapeTarget = null; /** Width of the panorama texture on screen (0: the preview). */ let panoWidth = 0; /** The sharper panorama on its way, for which open (resizes come in bursts). */ @@ -450,6 +452,7 @@ export function createPhotoViewer({ } function onKeyDown(event) { + if (!escapeTarget) onEscapeKey(event); if ( event.defaultPrevented || event.altKey || @@ -528,7 +531,10 @@ export function createPhotoViewer({ root.append(picture, canvas, prevButton, nextButton); host.append(root); listen(root, 'keydown', onKeyDown); - listen(host.closest?.('.sl-viewer-wrap') ?? root, 'keydown', onEscapeKey); + // The viewer's box in the panel; without one, the root's own handler + // takes Esc (one listener per element and type). + escapeTarget = host.closest?.('.sl-viewer-wrap') ?? null; + if (escapeTarget) listen(escapeTarget, 'keydown', onEscapeKey); applyRenderMode(); } @@ -564,9 +570,7 @@ export function createPhotoViewer({ if (size[0] <= panoWidth) return; if (panoComing?.seq === seq && size[0] <= panoComing.width) return; const sharp = sources.panoSharp(target, size, { - expanded: Boolean( - root?.closest?.('.sl-viewer-wrap-expanded'), - ), + expanded: Boolean(root?.closest?.('.sl-viewer-wrap-expanded')), }); if (!sharp?.url) return; const coming = { seq, width: size[0], controller: new AbortController() }; diff --git a/src/layers/streetLevel/photoViewer.test.mjs b/src/layers/streetLevel/photoViewer.test.mjs index 6f29e711e..78397a632 100644 --- a/src/layers/streetLevel/photoViewer.test.mjs +++ b/src/layers/streetLevel/photoViewer.test.mjs @@ -178,7 +178,9 @@ test('a sharp panorama still downloading when another photo opens is aborted', a url.endsWith('-sharp') ? new Promise((_, reject) => { signals.push(options.signal); - options.signal?.addEventListener('abort', () => reject(new Error('aborted'))); + options.signal?.addEventListener('abort', () => + reject(new Error('aborted')), + ); }) : Promise.resolve({ src: url }), createRenderer: () => ({ @@ -278,9 +280,8 @@ test('docked, Esc anywhere in the viewer’s box (toolbar, caption) is offered t }); test('a picture file or bitmap past any real photo’s size is refused', async (t) => { - const { loadImage, MAX_IMAGE_BYTES, MAX_IMAGE_SIDE } = await import( - './photoViewer.js' - ); + const { loadImage, MAX_IMAGE_BYTES, MAX_IMAGE_SIDE } = + await import('./photoViewer.js'); const saved = { fetch: globalThis.fetch, createImageBitmap: globalThis.createImageBitmap, diff --git a/src/layers/streetLevel/providers/panoramax/decode.js b/src/layers/streetLevel/providers/panoramax/decode.js index d628dec5c..c75a01768 100644 --- a/src/layers/streetLevel/providers/panoramax/decode.js +++ b/src/layers/streetLevel/providers/panoramax/decode.js @@ -78,8 +78,7 @@ export function decodeCoverageTile(bytes, address) { const props = feature.properties || {}; const parts = []; let length = 0; - const tolerance = - (SIMPLIFY_TOLERANCE_UNITS * sequences.extent) / 4096; + const tolerance = (SIMPLIFY_TOLERANCE_UNITS * sequences.extent) / 4096; for (const line of feature.loadGeometry()) { if (line.length < 2) continue; for (let k = 1; k < line.length; k++) diff --git a/src/layers/streetLevel/providers/panoramax/decode.test.mjs b/src/layers/streetLevel/providers/panoramax/decode.test.mjs index 159cf3884..104471a1e 100644 --- a/src/layers/streetLevel/providers/panoramax/decode.test.mjs +++ b/src/layers/streetLevel/providers/panoramax/decode.test.mjs @@ -191,8 +191,15 @@ test('a z15 tile’s pictures of one sequence become cones; other sequences’ a }); test('lines are simplified within a few tile units, ends and corners kept', () => { - const zigzag = Array.from({ length: 1000 }, (_, i) => ({ x: i, y: i % 2 ? 1 : 0 })); - assert.deepEqual(simplifyLine(zigzag, 4), [zigzag[0], zigzag.at(-1)], 'a 1-unit wobble goes'); + const zigzag = Array.from({ length: 1000 }, (_, i) => ({ + x: i, + y: i % 2 ? 1 : 0, + })); + assert.deepEqual( + simplifyLine(zigzag, 4), + [zigzag[0], zigzag.at(-1)], + 'a 1-unit wobble goes', + ); const corner = [ { x: 0, y: 0 }, { x: 50, y: 1 }, @@ -221,6 +228,9 @@ test('a decoded GPS track keeps a fraction of its points', () => { encodePanoramaxTile(tile, { sequences: [{ id: 'gps', parts: [track] }] }), tile, ).sequences; - assert.ok(sequence.parts[0].length < 100, `${sequence.parts[0].length} points`); + assert.ok( + sequence.parts[0].length < 100, + `${sequence.parts[0].length} points`, + ); assert.ok(sequence.parts[0].length > 5); }); diff --git a/src/layers/streetLevel/providers/panoramax/policy.test.mjs b/src/layers/streetLevel/providers/panoramax/policy.test.mjs index 6d1455bfb..770f52a26 100644 --- a/src/layers/streetLevel/providers/panoramax/policy.test.mjs +++ b/src/layers/streetLevel/providers/panoramax/policy.test.mjs @@ -227,7 +227,12 @@ test('hostile item data: a malformed link escape, null coordinates, a huge produ }); test('the instance holding a picture is named (its via host), never as its author', () => { - const item = stacItem({ id: 'a', lon: 1, lat: 2, producer: 'someone@ign.example.fr' }); + const item = stacItem({ + id: 'a', + lon: 1, + lat: 2, + producer: 'someone@ign.example.fr', + }); item.links.push({ rel: 'via', href: 'https://panoramax.ign.fr' }); assert.equal(normalizeItem(item).creator, null); assert.equal(normalizeItem(item).via, 'panoramax.ign.fr'); diff --git a/src/layers/streetLevel/providers/panoramax/provider.test.mjs b/src/layers/streetLevel/providers/panoramax/provider.test.mjs index f8c57b159..8cf3911d9 100644 --- a/src/layers/streetLevel/providers/panoramax/provider.test.mjs +++ b/src/layers/streetLevel/providers/panoramax/provider.test.mjs @@ -543,7 +543,12 @@ test('a broken neighbour z15 tile only thins the cones; an unreadable home tile const box = tileBounds(home.tile.x, home.tile.y, 15); const at = { lon: box.west + 0.0003, lat: box.north - 0.0002 }; const items = [0, 1, 2].map((i) => - stacItem({ id: `c-${i}`, sequenceId: 'c', lon: at.lon + i * 0.00005, lat: at.lat }), + stacItem({ + id: `c-${i}`, + sequenceId: 'c', + lon: at.lon + i * 0.00005, + lat: at.lat, + }), ); const tiles = pictureTiles(items); const broken = new TextEncoder().encode('oops'); @@ -835,7 +840,10 @@ test('a search hit outside the radius (a bounding box’s corner) is not the nea }); test('a line clicked from low down still searches its own sequence within 50 m', async () => { - const items = [...sequence('other', 1, { offset: 2 }), ...sequence('clicked', 1, { offset: 30 })]; + const items = [ + ...sequence('other', 1, { offset: 2 }), + ...sequence('clicked', 1, { offset: 30 }), + ]; const { instance, source } = setup({ items }); instance.init({ camera: { positionCartographic: { height: 300 } }, @@ -885,7 +893,9 @@ test('a filter that hides every density dot says so', async (t) => { grid: [{ lon: ORIGIN.lon, lat: ORIGIN.lat, pano: 0, flat: 5 }], }); env.instance.activate(env.viewer); - assert.ok(await until(() => env.instance.coverageStats().hint === FILTERED_HINT)); + assert.ok( + await until(() => env.instance.coverageStats().hint === FILTERED_HINT), + ); }); test('dots are drawn at their own sizes, and the busiest is the biggest', async (t) => { diff --git a/src/layers/streetLevel/providers/panoramax/source.test.mjs b/src/layers/streetLevel/providers/panoramax/source.test.mjs index b54134147..63eb6ff9e 100644 --- a/src/layers/streetLevel/providers/panoramax/source.test.mjs +++ b/src/layers/streetLevel/providers/panoramax/source.test.mjs @@ -338,7 +338,9 @@ test('a Retry-After is honoured up to five minutes', async () => { headers: { 'retry-after': header }, })).fetchImpl, }); - return source.getTile({ x: 0, y: 0, z: 0 }).catch((error) => error.retryAfterSec); + return source + .getTile({ x: 0, y: 0, z: 0 }) + .catch((error) => error.retryAfterSec); }; assert.equal(await wait('3600'), 300); assert.equal(await wait('99999999999'), 300); diff --git a/src/layers/streetLevel/providers/panoramax/viewer.test.mjs b/src/layers/streetLevel/providers/panoramax/viewer.test.mjs index c5c1fa19b..78c39f316 100644 --- a/src/layers/streetLevel/providers/panoramax/viewer.test.mjs +++ b/src/layers/streetLevel/providers/panoramax/viewer.test.mjs @@ -179,10 +179,19 @@ test('a 360° picture shows its SD equirectangular image, then HD shrunk to 4,09 test('HD panoramas too big to decode, or no bigger than SD, are never fetched; nor while docked', () => { const size = [4096, 2048]; const expanded = { expanded: true }; - assert.equal(PANORAMAX_SOURCES.panoSharp(PHOTOS.pano, size, { expanded: false }), null); + assert.equal( + PANORAMAX_SOURCES.panoSharp(PHOTOS.pano, size, { expanded: false }), + null, + ); assert.equal(PANORAMAX_SOURCES.panoSharp(PHOTOS.pano, size), null); - assert.equal(PANORAMAX_SOURCES.panoSharp(PHOTOS.bigPano, size, expanded), null); - assert.equal(PANORAMAX_SOURCES.panoSharp(PHOTOS.smallPano, size, expanded), null); + assert.equal( + PANORAMAX_SOURCES.panoSharp(PHOTOS.bigPano, size, expanded), + null, + ); + assert.equal( + PANORAMAX_SOURCES.panoSharp(PHOTOS.smallPano, size, expanded), + null, + ); assert.deepEqual( PANORAMAX_SOURCES.panoSharp({ ...PHOTOS.pano, width: 3000 }, size, expanded) .resize, diff --git a/src/tooling/streetLevelQa.test.mjs b/src/tooling/streetLevelQa.test.mjs index bdd6cf9ac..564cecadc 100644 --- a/src/tooling/streetLevelQa.test.mjs +++ b/src/tooling/streetLevelQa.test.mjs @@ -816,12 +816,17 @@ test('Panoramax fixtures: two sequences on one street, searches honour sequence const boxed = ask( `bbox=${at.lon - d},${at.lat - d},${at.lon + d},${at.lat + d}&limit=100`, ); - assert.ok(boxed.some((p) => p.lon < at.lon) && boxed.some((p) => p.lon > at.lon)); + assert.ok( + boxed.some((p) => p.lon < at.lon) && boxed.some((p) => p.lon > at.lon), + ); const placed = ask( `place_position=${at.lon},${at.lat}&place_distance=0-30&limit=50`, ); assert.ok(placed.length > 0); - assert.ok(placed.every((p) => p.lon <= at.lon + 1e-7), 'none it is behind'); + assert.ok( + placed.every((p) => p.lon <= at.lon + 1e-7), + 'none it is behind', + ); const one = ask(`ids=${at.id}&limit=1`); assert.equal(one[0].id, at.id); // Its pictures, and only those, are served; HD downloads are counted. diff --git a/src/ui/streetLevelPresentation.test.mjs b/src/ui/streetLevelPresentation.test.mjs index 5fba550f7..7dbf18179 100644 --- a/src/ui/streetLevelPresentation.test.mjs +++ b/src/ui/streetLevelPresentation.test.mjs @@ -555,6 +555,9 @@ test('the caption names an image’s own licence after its author, and leaves it caption({ creator: 'Fixture Mapper', license: 'CC BY-SA 4.0' }), 'Image by Fixture Mapper · CC BY-SA 4.0', ); - assert.equal(caption({ license: 'Licence Ouverte 2.0' }), 'Licence Ouverte 2.0'); + assert.equal( + caption({ license: 'Licence Ouverte 2.0' }), + 'Licence Ouverte 2.0', + ); assert.equal(caption({ creator: 'Someone' }), 'Image by Someone'); });