From 299a5943a01b754b5a268d6fc4d14551fbfbdd12 Mon Sep 17 00:00:00 2001 From: German Khokhlov Date: Tue, 3 Nov 2026 19:46:31 +0400 Subject: [PATCH] Ignore a late image or error from a street-level provider the user switched away from --- src/layers/streetLevel/edgeCases.test.mjs | 91 +++++++++++++++++++++++ src/layers/streetLevel/index.js | 11 ++- 2 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 src/layers/streetLevel/edgeCases.test.mjs diff --git a/src/layers/streetLevel/edgeCases.test.mjs b/src/layers/streetLevel/edgeCases.test.mjs new file mode 100644 index 000000000..bca078618 --- /dev/null +++ b/src/layers/streetLevel/edgeCases.test.mjs @@ -0,0 +1,91 @@ +// Edge cases from docs/migration/planet-separation/layers.md (street-level): +// without a paid source, a late answer from a provider the user switched away +// from is ignored and the lit provider keeps the panel. +import assert from 'node:assert/strict'; +import test from 'node:test'; +import * as Cesium from 'cesium'; +import { createStreetLevelLayer } from './index.js'; +import { + fakeCesiumViewer, + fakeStreetLevelProvider as fakeProvider, +} from '../../testSupport/streetLevelFakes.mjs'; + +const settle = () => new Promise((resolve) => setTimeout(resolve, 0)); + +function deferred() { + let resolve; + const promise = new Promise((done) => (resolve = done)); + return { promise, resolve }; +} + +/** Two keyless providers; the first answers its nearest lookup on demand. */ +async function twoProviders(t) { + const saved = globalThis.document; + const drawing = new Proxy({}, { get: () => () => ({ addColorStop() {} }) }); + globalThis.document = Object.assign(new EventTarget(), { + createElement: () => ({ getContext: () => drawing }), + }); + t.mock.method( + Cesium.GroundPolylinePrimitive, + 'initializeTerrainHeights', + async () => {}, + ); + const lookups = []; + const previous = fakeProvider({ + nearestImage: (point, { signal } = {}) => { + const answer = deferred(); + lookups.push({ point, signal, answer }); + return answer.promise; + }, + }); + const lit = fakeProvider({ + id: 'panoramax', + pickPrefix: 'pmx:', + nearestImage: async () => 'pmx-1', + }); + const viewer = fakeCesiumViewer(); + const layer = createStreetLevelLayer({ providers: [previous, lit] }); + layer.init(viewer); + layer.enable(viewer); + layer.attachViewerHost({}); + t.after(() => { + layer.destroy(); + globalThis.document = saved; + }); + await settle(); + return { layer, previous, lit, lookups }; +} + +test('edge:street-level: a late nearest answer from the previous provider is ignored', async (t) => { + const { layer, previous, lit, lookups } = await twoProviders(t); + const stale = layer.openNearest({ lat: 38.58, lon: -121.49 }); + await settle(); + layer.setProviderEnabled('panoramax', true); + assert.equal(await layer.openNearest({ lat: 38.58, lon: -121.49 }), true); + lookups[0].answer.resolve('mly-late'); + assert.equal(await stale, false); + await settle(); + assert.deepEqual(previous.calls.open, []); + assert.deepEqual(lit.calls.open, ['pmx-1']); + const { street } = layer.getUIState(); + assert.equal(street.providerId, 'panoramax'); + assert.equal(street.error, null); + assert.equal(street.loading, false); +}); + +test('edge:street-level: a switched-off provider cannot open an image or post an error late', async (t) => { + const { layer, previous, lit } = await twoProviders(t); + const late = previous.context().actions; + layer.setProviderEnabled('panoramax', true); + assert.equal(await layer.openNearest({ lat: 1, lon: 2 }), true); + // The darkened provider's own work (a sequence step, a cone it had queued) + // finishes after the switch. + assert.equal(await late.openImage('mly-late'), false); + late.reportError('Sequence images unavailable'); + await settle(); + assert.deepEqual(previous.calls.open, []); + assert.deepEqual(lit.calls.open, ['pmx-1']); + const { street } = layer.getUIState(); + assert.equal(street.providerId, 'panoramax'); + assert.equal(street.error, null); +}); diff --git a/src/layers/streetLevel/index.js b/src/layers/streetLevel/index.js index 926db23db..bb69ff043 100644 --- a/src/layers/streetLevel/index.js +++ b/src/layers/streetLevel/index.js @@ -100,9 +100,14 @@ export function createStreetLevelLayer({ waitingForView: () => waitingForView, notify, actions: { - /** `{frame: false}` opens it without moving the camera. */ + /** + * `{frame: false}` opens it without moving the camera. A provider + * switched off (or a layer turned off) answers late: ignored. + */ openImage: (imageId, options) => - openImage(entry.def.id, imageId, options), + state.enabled && entry.on + ? openImage(entry.def.id, imageId, options) + : Promise.resolve(false), /** This provider's image nearest a point the user picked on the map. */ openNearest: (point) => layer.openNearest(point, { @@ -115,6 +120,8 @@ export function createStreetLevelLayer({ */ reportError: (message) => { if (message) { + // Another source owns the panel now; its late failure is not news. + if (!state.enabled || !entry.on) return; state.street.error = entry.reportedError = message; } else { if (!entry.reportedError) return;