From d1966b9751e8b17214bbc8a95eb50c2f5f076e00 Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Thu, 12 Nov 2026 17:56:51 -0800 Subject: [PATCH] Clear a view-bounded layer's error when the camera returns to the drawn view A failed request for the view in between left its error set, and the early return for the already drawn view never cleared it, so the drawn features showed with an error state. --- src/library/layers.js | 5 ++++- src/library/viewLayer.test.mjs | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/library/layers.js b/src/library/layers.js index 64ee55247..8cc5b4afc 100644 --- a/src/library/layers.js +++ b/src/library/layers.js @@ -764,9 +764,12 @@ export function createLibraryViewLayer( // Back on the view already drawn: a request for the view in between is // obsolete and must not replace it. if (key === loadedKey && dataSource) { - if (controller) { + // What is drawn is this view's, so an error from the view in between + // no longer applies. + if (controller || error) { abort(); loading = false; + error = null; render(); } return; diff --git a/src/library/viewLayer.test.mjs b/src/library/viewLayer.test.mjs index 53f9bd65a..47ce79c4c 100644 --- a/src/library/viewLayer.test.mjs +++ b/src/library/viewLayer.test.mjs @@ -157,6 +157,40 @@ test('returning to the drawn view cancels the request for the view in between', await layer.destroy(viewer); }); +test('returning to the drawn view clears an error from the view in between', async () => { + let call = 0; + const layer = createLibraryViewLayer(entry, { + source: { + loadDataset: async () => { + call += 1; + if (call === 2) throw new Error('HTTP 503'); + return { features: [polygon], truncated: false }; + }, + }, + cesium: fakeCesium, + debounceMs: 0, + }); + const { viewer, listeners } = fakeViewer({ height: 2000 }); + const moveTo = async (lon, lat) => { + viewer.camera.positionWC = Cesium.Cartesian3.fromDegrees(lon, lat, 2000); + viewer.camera.pickEllipsoid = () => + Cesium.Cartesian3.fromDegrees(lon, lat, 0); + viewer.camera.computeViewRectangle = () => + Cesium.Rectangle.fromDegrees(lon - 1, lat - 1, lon + 1, lat + 1); + // The camera settles: the layer's moveEnd listener schedules a load. + for (const listener of listeners) listener(); + await new Promise((resolve) => setTimeout(resolve, 5)); + }; + await layer.init(viewer); + await layer.enable(viewer); + await moveTo(-77.2, 38.95); + assert.ok(layer.getStats().error); + await moveTo(-77.03, 38.89); + assert.equal(layer.getStats().error, null); + assert.equal(layer.getStats().count, 1); + await layer.destroy(viewer); +}); + test('the row legend labels each colour and counts what is in view', async () => { const { colorByLegend } = await import('./layers.js'); const bridges = libraryEntry('ntad-national-bridges');