164 lines
6.0 KiB
JavaScript
164 lines
6.0 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
runStagedMapProviders,
|
|
waitForGlobeViewportReady,
|
|
} from "../apps/catalog/src/mapProviderStartup.mjs";
|
|
|
|
function eventHarness() {
|
|
const listeners = new Set();
|
|
return {
|
|
event: {
|
|
addEventListener(listener) {
|
|
listeners.add(listener);
|
|
return () => listeners.delete(listener);
|
|
},
|
|
},
|
|
raise(...args) {
|
|
for (const listener of [...listeners]) listener(...args);
|
|
},
|
|
get size() {
|
|
return listeners.size;
|
|
},
|
|
};
|
|
}
|
|
|
|
test("providers start in base-map order", async () => {
|
|
const calls = [];
|
|
const result = await runStagedMapProviders({
|
|
loadImagery: () => calls.push("imagery"),
|
|
waitAfterImagery: () => calls.push("imagery-ready"),
|
|
loadTerrain: () => calls.push("terrain"),
|
|
waitAfterTerrain: () => calls.push("terrain-ready"),
|
|
loadBuildings: () => calls.push("buildings"),
|
|
loadDeferred: () => calls.push("deferred"),
|
|
});
|
|
|
|
assert.equal(result, "complete");
|
|
assert.deepEqual(calls, ["imagery", "imagery-ready", "terrain", "terrain-ready", "buildings", "deferred"]);
|
|
});
|
|
|
|
test("imagery failure is reported without blocking terrain or buildings", async () => {
|
|
const calls = [];
|
|
const errors = [];
|
|
await runStagedMapProviders({
|
|
loadImagery: () => { calls.push("imagery"); throw new Error("imagery failed"); },
|
|
waitAfterImagery: () => calls.push("imagery-ready"),
|
|
loadTerrain: () => calls.push("terrain"),
|
|
waitAfterTerrain: () => calls.push("terrain-ready"),
|
|
loadBuildings: () => calls.push("buildings"),
|
|
onProviderError: (provider, error) => errors.push([provider, error.message]),
|
|
});
|
|
|
|
assert.deepEqual(calls, ["imagery", "terrain", "terrain-ready", "buildings"]);
|
|
assert.deepEqual(errors, [["imagery", "imagery failed"]]);
|
|
});
|
|
|
|
test("terrain failure is reported without blocking buildings", async () => {
|
|
const calls = [];
|
|
const errors = [];
|
|
await runStagedMapProviders({
|
|
loadImagery: () => calls.push("imagery"),
|
|
waitAfterImagery: () => calls.push("imagery-ready"),
|
|
loadTerrain: () => { calls.push("terrain"); throw new Error("terrain failed"); },
|
|
waitAfterTerrain: () => calls.push("terrain-ready"),
|
|
loadBuildings: () => calls.push("buildings"),
|
|
onProviderError: (provider, error) => errors.push([provider, error.message]),
|
|
});
|
|
|
|
assert.deepEqual(calls, ["imagery", "imagery-ready", "terrain", "buildings"]);
|
|
assert.deepEqual(errors, [["terrain", "terrain failed"]]);
|
|
});
|
|
|
|
test("cancellation prevents all later stages", async () => {
|
|
const controller = new AbortController();
|
|
const calls = [];
|
|
const result = await runStagedMapProviders({
|
|
signal: controller.signal,
|
|
loadImagery: () => { calls.push("imagery"); controller.abort(); },
|
|
waitAfterImagery: () => calls.push("imagery-ready"),
|
|
loadTerrain: () => calls.push("terrain"),
|
|
loadBuildings: () => calls.push("buildings"),
|
|
});
|
|
|
|
assert.equal(result, "cancelled");
|
|
assert.deepEqual(calls, ["imagery"]);
|
|
});
|
|
|
|
test("a stalled provider times out without inserting late or blocking later stages", async () => {
|
|
const calls = [];
|
|
const errors = [];
|
|
let releaseImagery;
|
|
const stalledImagery = new Promise((resolve) => { releaseImagery = resolve; });
|
|
await runStagedMapProviders({
|
|
providerInitializationTimeoutMs: 1,
|
|
loadImagery: async ({ signal }) => {
|
|
calls.push("imagery-start");
|
|
await stalledImagery;
|
|
if (!signal.aborted) calls.push("imagery-insert");
|
|
},
|
|
loadTerrain: () => calls.push("terrain"),
|
|
loadBuildings: () => calls.push("buildings"),
|
|
onProviderError: (provider, error) => errors.push([provider, error.message]),
|
|
});
|
|
releaseImagery();
|
|
await Promise.resolve();
|
|
|
|
assert.deepEqual(calls, ["imagery-start", "terrain", "buildings"]);
|
|
assert.deepEqual(errors, [["imagery", "imagery_startup_timeout"]]);
|
|
});
|
|
|
|
test("viewport readiness waits for discovery frames and cleans listeners", async () => {
|
|
const progress = eventHarness();
|
|
const postRender = eventHarness();
|
|
const globe = { tilesLoaded: true, tileLoadProgressEvent: progress.event };
|
|
const scene = { postRender: postRender.event, requestRender() {} };
|
|
let settled = false;
|
|
const ready = waitForGlobeViewportReady({ globe, scene, timeoutMs: 100, minimumRenderFrames: 2 })
|
|
.then((reason) => { settled = true; return reason; });
|
|
|
|
postRender.raise();
|
|
await Promise.resolve();
|
|
assert.equal(settled, false);
|
|
postRender.raise();
|
|
assert.equal(await ready, "loaded");
|
|
assert.equal(progress.size, 0);
|
|
assert.equal(postRender.size, 0);
|
|
});
|
|
|
|
test("viewport readiness times out and aborts without leaking listeners", async () => {
|
|
const timeoutProgress = eventHarness();
|
|
const timeoutPostRender = eventHarness();
|
|
assert.equal(await waitForGlobeViewportReady({
|
|
globe: { tilesLoaded: false, tileLoadProgressEvent: timeoutProgress.event },
|
|
scene: { postRender: timeoutPostRender.event, requestRender() {} },
|
|
timeoutMs: 1,
|
|
}), "timeout");
|
|
assert.equal(timeoutProgress.size, 0);
|
|
assert.equal(timeoutPostRender.size, 0);
|
|
|
|
const abortProgress = eventHarness();
|
|
const abortPostRender = eventHarness();
|
|
const controller = new AbortController();
|
|
const pending = waitForGlobeViewportReady({
|
|
globe: { tilesLoaded: false, tileLoadProgressEvent: abortProgress.event },
|
|
scene: { postRender: abortPostRender.event, requestRender() {} },
|
|
signal: controller.signal,
|
|
timeoutMs: 100,
|
|
});
|
|
controller.abort();
|
|
assert.equal(await pending, "cancelled");
|
|
assert.equal(abortProgress.size, 0);
|
|
assert.equal(abortPostRender.size, 0);
|
|
});
|
|
|
|
test("the live renderer uses the staged provider coordinator", async () => {
|
|
const renderer = await readFile(new URL("../apps/catalog/src/CesiumMapRenderer.tsx", import.meta.url), "utf8");
|
|
assert.match(renderer, /runStagedMapProviders\(\{/);
|
|
assert.match(renderer, /waitForGlobeViewportReady\(\{/);
|
|
assert.match(renderer, /providerStartupAbort\.abort\(\)/);
|
|
assert.doesNotMatch(renderer, /Do not serialize provider startup/);
|
|
});
|