Files
gods-eye-view/src/data/telegeographySubmarineCables.test.mjs
T
Aliciawque 8e68a29c50 fix(cables): preserve feature ownership across multipart geometry
Associate expanded cable and landing entities through a copied source index,
retain one reference stem per feature, and cover duplicate IDs, bundled
segments and frozen provider inputs with real Cesium regressions.
2026-10-12 21:36:55 -07:00

2006 lines
63 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import * as Cesium from 'cesium';
import { MAP_STACKS } from '../mapStackController.js';
import {
CABLE_LABEL_DEPTH_DECISION,
CABLE_OVERLAY_COLLISION_CAPACITY,
CABLE_OVERLAY_SOURCE_ID,
CABLE_REFERENCE_LABEL_WINNER_CAP,
CABLE_STEM_TIP_EPSILON_M,
CABLE_SWEEP_MOTION_EPSILON_M,
CABLE_SWEEP_MOTION_PROBE_INTERVAL_MS,
applyTranslucentMarkerBlend,
cableClassificationTypeForScene,
cableClassificationTypeForStack,
cableReferencePriority,
createCableOverlayEntry,
createCableOverlayPublisher,
createCableReferenceSweepGate,
createTeleGeographySubmarineCableLayer,
selectCableReferenceLabelWinners,
updateCableReferenceStem,
} from './telegeographySubmarineCables.js';
test('cable reference winners remain nearest-first and hard-capped', () => {
const records = Array.from({ length: 300 }, (_, index) => ({
entity: { id: `reference-${index}` },
label: `Reference ${index}`,
visible: true,
distanceM: 1000 + index,
}));
let winners = selectCableReferenceLabelWinners(records);
assert.equal(winners.length, CABLE_REFERENCE_LABEL_WINNER_CAP);
assert.equal(winners[0], records[0]);
assert.equal(winners.at(-1), records[CABLE_REFERENCE_LABEL_WINNER_CAP - 1]);
for (let index = 0; index < 80; index++) records[index].visible = false;
winners = selectCableReferenceLabelWinners(records);
assert.equal(winners.length, CABLE_REFERENCE_LABEL_WINNER_CAP);
assert.equal(
winners[0],
records[80],
'hidden references release their slots to the next nearest',
);
records[90].distanceM = 10_000_000; // beyond the 9,000 km reference range
winners = selectCableReferenceLabelWinners(records);
assert.ok(
!winners.includes(records[90]),
'out-of-range references never reach the host',
);
});
test('cable overlay entries satisfy the shared presentation contract', () => {
const tip = Cesium.Cartesian3.fromDegrees(-40, 35, 2500);
const record = {
id: 'cable-reference-0-atlantic-1',
kind: 'cable',
label: 'Atlantic Crossing-1',
tip,
distanceM: 400_000,
};
const entry = createCableOverlayEntry(record);
assert.equal(entry.id, 'cable-reference-0-atlantic-1');
assert.equal(
entry.position,
tip,
'entry stays attached to the mutable stem-tip Cartesian',
);
assert.equal(entry.variant, 'label');
assert.equal(entry.title, 'Atlantic Crossing-1');
assert.equal(entry.accent, '#39d5ff');
assert.equal(entry.collisionGroup, 'ambient-label');
assert.equal(entry.paintLane, 'ambient-label');
assert.equal(
entry.interactive,
false,
'point/stem/line picking remains Cesium-native',
);
assert.equal(entry.maxDistance, 9_000_000);
assert.equal(entry.distanceFadeStartRatio, 0.7);
assert.deepEqual(
entry.distanceScale,
{
near: 250_000,
nearValue: 1,
far: 9_000_000,
farValue: 0.62,
},
'former native scaleByDistance curve is preserved',
);
assert.equal(entry.edgeFade, 'keyhole');
assert.equal(entry.horizonCull, true);
assert.equal(entry.terrainOcclusion, false);
assert.equal(entry.verticalOnly, true);
assert.equal(entry.placement, 'above');
const landing = createCableOverlayEntry({
id: 'landing-point-reference-1-lisbon',
kind: 'landing-point',
label: 'Lisbon, Portugal',
tip,
distanceM: 100_000,
});
assert.equal(
landing.accent,
'#8fffd2',
'landing points keep their mint accent',
);
assert.equal(landing.interactive, false);
});
test('cable reference priority is nearest-first and sweep-stable', () => {
assert.ok(cableReferencePriority(1_000) > cableReferencePriority(1_000_000));
assert.ok(
cableReferencePriority(1_000_000) > cableReferencePriority(8_000_000),
);
assert.equal(
cableReferencePriority(500_000),
cableReferencePriority(500_000 + 10_000),
'50 km quantization keeps slow camera drift from reshuffling ranks',
);
assert.equal(
cableReferencePriority(undefined),
cableReferencePriority(9_000_000),
'missing distances rank like the range edge instead of jumping the queue',
);
});
test('cable overlay publisher owns the production show/publish/hide lifecycle', () => {
const calls = [];
const publisher = createCableOverlayPublisher({
host: {
setVisible: (...args) => calls.push(['visible', ...args]),
setEntries: (...args) => calls.push(['entries', ...args]),
clearSource: (...args) => calls.push(['clear', ...args]),
},
});
publisher.publish([{ id: 'ignored-before-show' }]);
publisher.show();
publisher.show();
publisher.publish([{ id: 'cable-1' }]);
publisher.hide();
publisher.publish([{ id: 'dropped-while-hidden' }]);
publisher.hide();
assert.deepEqual(calls[0], ['visible', CABLE_OVERLAY_SOURCE_ID, true]);
assert.deepEqual(calls[1].slice(0, 3), [
'entries',
CABLE_OVERLAY_SOURCE_ID,
[{ id: 'cable-1' }],
]);
assert.deepEqual(calls[1][3], {
cohortLimit: CABLE_REFERENCE_LABEL_WINNER_CAP,
collisionCapacity: CABLE_OVERLAY_COLLISION_CAPACITY,
moving: false,
});
assert.deepEqual(calls[2], ['clear', CABLE_OVERLAY_SOURCE_ID]);
assert.deepEqual(calls[3], ['visible', CABLE_OVERLAY_SOURCE_ID, false]);
assert.equal(
calls.length,
4,
'hidden publishers drop late publishes; repeat hide is a no-op',
);
// The production teardown (layer.destroy) is hide(): the publisher stays
// reusable for the layer's supported re-init-after-destroy contract. There
// is intentionally no permanent-destroy method to test.
assert.equal(publisher.destroy, undefined);
publisher.show();
publisher.publish([{ id: 'cable-2' }]);
assert.deepEqual(calls[4], ['visible', CABLE_OVERLAY_SOURCE_ID, true]);
assert.deepEqual(calls[5].slice(0, 3), [
'entries',
CABLE_OVERLAY_SOURCE_ID,
[{ id: 'cable-2' }],
]);
});
test('cable ground lines classify against exactly the active surface on every stack', () => {
// Photorealistic tiles render with the globe hidden — 3D-tile pass only.
assert.equal(
cableClassificationTypeForStack('photoreal'),
Cesium.ClassificationType.CESIUM_3D_TILE,
);
// Every globe stack renders imagery on the shown globe — terrain pass only.
for (const stackId of ['bing-aerial', 'bing-labels', 'esri-imagery', 'osm']) {
assert.equal(
cableClassificationTypeForStack(stackId),
Cesium.ClassificationType.TERRAIN,
`${stackId} must classify against terrain so cables stay visible there`,
);
}
// Unknown stack → BOTH: visible on every surface, the shipped fallback.
// A TRUTHY unknown id must reach it too — "not photoreal" is not the same
// claim as "renders on the globe", and asserting TERRAIN for an id this
// module has never heard of would hide the cables on a future 3D-tile
// stack instead of degrading to the documented safe behavior.
assert.equal(
cableClassificationTypeForStack('some-future-stack'),
Cesium.ClassificationType.BOTH,
);
assert.equal(
cableClassificationTypeForStack('photoreal-v2'),
Cesium.ClassificationType.BOTH,
);
assert.equal(
cableClassificationTypeForStack(''),
Cesium.ClassificationType.BOTH,
);
assert.equal(
cableClassificationTypeForStack(undefined),
Cesium.ClassificationType.BOTH,
);
assert.equal(
cableClassificationTypeForStack(null),
Cesium.ClassificationType.BOTH,
);
// Every id that actually ships must be a KNOWN id: a stack added to
// MAP_STACKS without a mapping here silently loses its halved command set,
// so the omission fails loudly instead of degrading quietly.
for (const stack of MAP_STACKS) {
assert.notEqual(
cableClassificationTypeForStack(stack.id),
Cesium.ClassificationType.BOTH,
`MAP_STACKS id '${stack.id}' has no cable classification mapping`,
);
}
// Boot fires no stack event; the initial value derives from live scene
// state exactly like the height-datum listeners: photoreal ⇔ globe hidden.
assert.equal(
cableClassificationTypeForScene({ globe: { show: false } }),
Cesium.ClassificationType.CESIUM_3D_TILE,
);
assert.equal(
cableClassificationTypeForScene({ globe: { show: true } }),
Cesium.ClassificationType.TERRAIN,
);
assert.equal(
cableClassificationTypeForScene(null),
Cesium.ClassificationType.BOTH,
);
});
test('a map-stack change re-classifies every cable line once, and destroy detaches the listener', async () => {
const listeners = new Map();
const eventTarget = {
addEventListener: (name, fn) => listeners.set(name, fn),
removeEventListener: (name, fn) => {
if (listeners.get(name) === fn) listeners.delete(name);
},
};
const env = await createRealCableLayerHarness({
mapStackEventTarget: eventTarget,
});
const cableSource = env.dataSources.find((ds) =>
/Submarine Cables/.test(ds.name || ''),
);
const classificationOf = (entity) =>
entity.polyline.classificationType.getValue();
// The harness scene has no globe → BOTH at init (safe unknown fallback).
const cableEntity = cableSource.entities.values[0];
assert.equal(classificationOf(cableEntity), Cesium.ClassificationType.BOTH);
const listener = listeners.get('gev:map-stack-changed');
assert.equal(
typeof listener,
'function',
'init must subscribe to the stack event',
);
listener({ detail: { activeId: 'photoreal', status: 'ready' } });
assert.equal(
classificationOf(cableEntity),
Cesium.ClassificationType.CESIUM_3D_TILE,
);
listener({ detail: { activeId: 'osm', status: 'ready' } });
assert.equal(
classificationOf(cableEntity),
Cesium.ClassificationType.TERRAIN,
);
env.layer.destroy(env.viewer);
assert.equal(
listeners.has('gev:map-stack-changed'),
false,
'destroy must detach the stack listener',
);
});
test('marker collections blend in a single translucent pass behind a loud shape invariant', async () => {
// Helper contract on a REAL Cesium cluster shape.
const ds = new Cesium.CustomDataSource('blend-probe');
let result = applyTranslucentMarkerBlend(ds);
assert.deepEqual(
result,
{ applied: 0, pending: 2, invariantFailed: false },
'pre-visualizer clusters are pending, never a shape failure',
);
ds.clustering._billboardCollection = new Cesium.BillboardCollection();
ds.clustering._pointCollection = new Cesium.PointPrimitiveCollection();
result = applyTranslucentMarkerBlend(ds);
assert.deepEqual(result, { applied: 2, pending: 0, invariantFailed: false });
assert.equal(
ds.clustering._billboardCollection.blendOption,
Cesium.BlendOption.TRANSLUCENT,
);
assert.equal(
ds.clustering._pointCollection.blendOption,
Cesium.BlendOption.TRANSLUCENT,
);
// Shape drift fails LOUDLY-detectably instead of applying blindly: a
// renamed field or a foreign object type must never be touched.
assert.equal(
applyTranslucentMarkerBlend({ clustering: {} }).invariantFailed,
true,
'missing private fields mean Cesium changed shape',
);
const wrongType = new Cesium.CustomDataSource('wrong-type');
wrongType.clustering._billboardCollection = { blendOption: 0 };
const wrongResult = applyTranslucentMarkerBlend(wrongType);
assert.equal(wrongResult.invariantFailed, true);
assert.equal(
wrongType.clustering._billboardCollection.blendOption,
0,
'foreign objects are left untouched',
);
assert.equal(applyTranslucentMarkerBlend(null).invariantFailed, true);
// ATOMICITY: a shape failure on the SECOND collection must not leave the
// FIRST one already forced. Validation of every field/type completes before
// the first assignment, so a failed probe applies nothing at all — that is
// what "leaves Cesium's default blend untouched" has to mean.
const partial = new Cesium.CustomDataSource('partial-shape');
partial.clustering._billboardCollection = new Cesium.BillboardCollection();
partial.clustering._pointCollection = { blendOption: 0 }; // renamed/reshaped
const partialResult = applyTranslucentMarkerBlend(partial);
assert.equal(partialResult.invariantFailed, true);
assert.equal(
partialResult.applied,
0,
'a failed probe reports zero applications',
);
assert.equal(
partial.clustering._billboardCollection.blendOption,
Cesium.BlendOption.OPAQUE_AND_TRANSLUCENT,
"the valid first collection keeps Cesium's default blend when the second fails",
);
assert.equal(
partial.clustering._pointCollection.blendOption,
0,
'the malformed collection is never touched',
);
// Real-layer wiring: once the visualizers have created the collections,
// the next sweep applies the single-pass blend to both marker sources.
const env = await createRealCableLayerHarness();
env.raiseSweep();
const landingSource = env.dataSources.find((source) =>
/Landing Points/.test(source.name || ''),
);
const referenceSource = env.dataSources.find((source) =>
/References/.test(source.name || ''),
);
landingSource.clustering._billboardCollection =
new Cesium.BillboardCollection();
landingSource.clustering._pointCollection =
new Cesium.PointPrimitiveCollection();
referenceSource.clustering._billboardCollection =
new Cesium.BillboardCollection();
referenceSource.clustering._pointCollection =
new Cesium.PointPrimitiveCollection();
for (const fn of env.listeners.moveEnd) fn();
env.raiseSweep();
assert.equal(
landingSource.clustering._billboardCollection.blendOption,
Cesium.BlendOption.TRANSLUCENT,
'landing billboards blend in one pass after the sweep',
);
assert.equal(
referenceSource.clustering._pointCollection.blendOption,
Cesium.BlendOption.TRANSLUCENT,
'reference points blend in one pass after the sweep',
);
env.layer.destroy(env.viewer);
// The same atomicity holds ACROSS the two marker sources: the layer probes
// both before committing either, so a reshaped reference source can never
// leave the landing source already forced to TRANSLUCENT.
const failEnv = await createRealCableLayerHarness();
const failLanding = failEnv.dataSources.find((source) =>
/Landing Points/.test(source.name || ''),
);
const failReference = failEnv.dataSources.find((source) =>
/References/.test(source.name || ''),
);
failLanding.clustering._billboardCollection =
new Cesium.BillboardCollection();
failLanding.clustering._pointCollection =
new Cesium.PointPrimitiveCollection();
delete failReference.clustering._pointCollection; // Cesium reshaped
failEnv.raiseSweep();
assert.equal(
failLanding.clustering._billboardCollection.blendOption,
Cesium.BlendOption.OPAQUE_AND_TRANSLUCENT,
'a reference-source shape failure leaves the landing source on the default blend',
);
failEnv.layer.destroy(failEnv.viewer);
});
test('cable reference sweep gate is dirty-only — no timer path', () => {
const gate = createCableReferenceSweepGate();
assert.equal(gate.shouldRun(), true, 'initial dirtiness covers enable/load');
// However many frames render, an un-dirtied gate never runs the sweep —
// the former 500 ms timer path is gone.
for (let frame = 0; frame < 200; frame++) {
assert.equal(gate.shouldRun(), false);
}
gate.markDirty();
assert.equal(gate.shouldRun(), true, 'moveEnd dirtiness runs exactly once');
assert.equal(gate.shouldRun(), false);
gate.reset();
assert.equal(gate.shouldRun(), true, 'reset restores the initial dirtiness');
});
test('the sweep gate falls back to motion probes for cameras that never emit moveEnd', () => {
let clock = 0;
const frameMs = 1000 / 60;
const origin = Cesium.Cartesian3.fromDegrees(-40, 35, 4_500_000);
const camera = { positionWC: Cesium.Cartesian3.clone(origin) };
const gate = createCableReferenceSweepGate({ now: () => clock });
const runFrames = (count, step = () => {}) => {
let sweeps = 0;
for (let frame = 0; frame < count; frame++) {
clock += frameMs;
step(frame);
if (gate.shouldRun(camera)) sweeps++;
}
return sweeps;
};
assert.equal(
gate.shouldRun(camera),
true,
'initial dirtiness still covers enable/load',
);
// PARKED: 100 s of frames on a camera that never moves must cost ZERO
// sweeps — the fallback is motion-aware, not a reinstated timer.
assert.equal(
runFrames(6000),
0,
'a parked camera costs zero sweeps however long frames flow',
);
// Sub-epsilon jitter is not motion either: the probe compares against the
// last SWEPT position, so numeric noise can never accumulate into a sweep.
const jitter = runFrames(6000, (frame) => {
camera.positionWC = Cesium.Cartesian3.add(
origin,
new Cesium.Cartesian3(
0,
0,
(frame % 2) * (CABLE_SWEEP_MOTION_EPSILON_M / 10),
),
new Cesium.Cartesian3(),
);
});
assert.equal(jitter, 0, 'sub-epsilon camera jitter never earns a sweep');
// TRACKING: a follow camera advances every frame and never emits moveEnd.
// The sweep must land inside one probe window instead of starving forever.
const timedGate = createCableReferenceSweepGate({ now: () => clock });
const timedCamera = { positionWC: Cesium.Cartesian3.clone(origin) };
assert.equal(timedGate.shouldRun(timedCamera), true, 'the gate starts dirty');
const trackingStart = clock;
let sweepAt = null;
for (let frame = 0; frame < 600 && sweepAt === null; frame++) {
clock += frameMs;
timedCamera.positionWC = Cesium.Cartesian3.add(
origin,
new Cesium.Cartesian3(0, 0, 12 * (frame + 1)), // ~0.7 km/s: a tracked jet
new Cesium.Cartesian3(),
);
if (timedGate.shouldRun(timedCamera)) sweepAt = clock;
}
assert.notEqual(
sweepAt,
null,
'a continuously moving camera must not starve the sweep',
);
assert.ok(
sweepAt - trackingStart <= CABLE_SWEEP_MOTION_PROBE_INTERVAL_MS + frameMs,
`the fallback sweep must land inside one probe window (landed at ${sweepAt - trackingStart} ms)`,
);
assert.ok(
sweepAt - trackingStart >= CABLE_SWEEP_MOTION_PROBE_INTERVAL_MS,
'and never sooner — the probe stays bounded to one sample per window',
);
// moveEnd still wins immediately: an event-dirtied gate does not wait for
// the probe window it is sitting inside.
timedGate.markDirty();
assert.equal(
timedGate.shouldRun(timedCamera),
true,
'moveEnd dirtiness bypasses the probe window',
);
assert.equal(
timedGate.shouldRun(timedCamera),
false,
'and still runs exactly once',
);
// A gate called without a camera keeps the pure dirty-only contract.
const cameraless = createCableReferenceSweepGate({ now: () => clock });
assert.equal(cameraless.shouldRun(), true);
clock += CABLE_SWEEP_MOTION_PROBE_INTERVAL_MS * 10;
assert.equal(
cameraless.shouldRun(),
false,
'no camera state means no motion fallback',
);
});
function makeStemRecord(lon = -40, lat = 35) {
const base = Cesium.Cartesian3.fromDegrees(lon, lat, 0);
const tip = Cesium.Cartesian3.fromDegrees(lon, lat, 2500);
const stemPositionBuffers = [
[base, tip],
[base, tip],
];
const setCalls = { position: 0, polyline: 0, polylineArrays: [] };
return {
record: {
entity: {
position: {
setValue: () => {
setCalls.position++;
},
},
polyline: {
positions: {
setValue: (value) => {
setCalls.polyline++;
setCalls.polylineArrays.push(value);
},
},
},
},
base,
tip,
nextTip: Cesium.Cartesian3.clone(tip),
stemPositionBuffers,
stemPositionBufferIndex: 0,
reference: { lon, lat },
},
setCalls,
stemPositionBuffers,
};
}
test('staticized stems redefine constants only on real tip changes, alternating buffers', () => {
const { record, setCalls, stemPositionBuffers } = makeStemRecord();
const canvasHeight = 900;
const fov = Math.PI / 3;
const camera = Cesium.Cartesian3.fromDegrees(-40, 35, 500_000);
assert.equal(
updateCableReferenceStem(record, camera, canvasHeight, fov),
true,
);
assert.equal(setCalls.position, 1);
assert.equal(setCalls.polyline, 1);
assert.equal(
setCalls.polylineArrays[0],
stemPositionBuffers[1],
'first real update selects the alternate buffer',
);
// Sub-epsilon camera noise must not redefine anything.
const jitter = Cesium.Cartesian3.add(
camera,
new Cesium.Cartesian3(CABLE_STEM_TIP_EPSILON_M / 10, 0, 0),
new Cesium.Cartesian3(),
);
assert.equal(
updateCableReferenceStem(record, jitter, canvasHeight, fov),
false,
);
assert.equal(setCalls.position, 1);
assert.equal(setCalls.polyline, 1);
// A real camera move flips back to the first buffer: steady state allocates
// no arrays beyond the two preallocated ones.
const moved = Cesium.Cartesian3.add(
camera,
new Cesium.Cartesian3(0, 0, 400_000),
new Cesium.Cartesian3(),
);
assert.equal(
updateCableReferenceStem(record, moved, canvasHeight, fov),
true,
);
assert.equal(setCalls.polylineArrays[1], stemPositionBuffers[0]);
assert.equal(new Set(setCalls.polylineArrays).size, 2);
// Stem height honors the shipped 700 m..85 km clamp.
const tipCarto = Cesium.Cartographic.fromCartesian(record.tip);
assert.ok(tipCarto.height >= 700 && tipCarto.height <= 85_000);
});
test('the depth decision is the dated Option-2 host migration, superseding Option 1', () => {
assert.deepEqual(CABLE_LABEL_DEPTH_DECISION, {
option: 2,
decidedAt: '2026-08-18',
supersedes: '2026-08-02',
depthTested: false,
});
});
test('cables create no native labels and no per-frame geometry callbacks', () => {
const source = [
'../../data/telegeographySubmarineCables.js',
'../../app/layers/submarineCables.js',
'../../app/layers/overlayHost.js',
'rendering.js',
'overlay.js',
'surface.js',
'ingestion.js',
'interaction.js',
'geometry.js',
'lifecycle.js',
]
.map((file) =>
readFileSync(
new URL('../layers/submarineCables/' + file, import.meta.url),
'utf8',
),
)
.join('\n');
assert.doesNotMatch(
source,
/new Cesium\.LabelGraphics/,
'reverting to native labels must fail this pin',
);
assert.doesNotMatch(
source,
/new Cesium\.CallbackProperty/,
'reverting to per-frame stem callbacks must fail this pin',
);
assert.match(
source,
/setOverlayEntries/,
'labels must flow through the shared world-overlay host',
);
assert.match(source, /record\.entity\.position\.setValue\(record\.tip\)/);
assert.match(
source,
/const stemPositionBuffers = \[\s*\[base, tip\],\s*\[base, tip\],?\s*\]/,
);
assert.match(
source,
/record\.entity\.polyline\.positions\.setValue\(stemPositions\)/,
);
});
const CABLE_FIXTURE = {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
id: 'ac1',
properties: { id: 'ac1', name: 'Atlantic Crossing-1' },
geometry: {
type: 'LineString',
coordinates: [
[-40, 35],
[-30, 40],
],
},
},
],
};
// Node has no `document`, so Point features (whose GeoJSON default marker
// needs a canvas PinBuilder) cannot load here. Landing references only need
// `properties.coordinates`, so a line geometry exercises the same path.
const LANDING_FIXTURE = {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
id: 'lisbon',
properties: {
id: 'lisbon',
name: 'Lisbon, Portugal',
coordinates: [-9.1, 38.7],
},
geometry: {
type: 'LineString',
coordinates: [
[-9.1, 38.7],
[-9.05, 38.72],
],
},
},
],
};
function fixtureForUrl(url) {
return String(url).includes('landing-point')
? LANDING_FIXTURE
: CABLE_FIXTURE;
}
function makeStubViewer(
listeners,
dataSources,
addCalls = { count: 0 },
addControl = null,
) {
return {
dataSources: {
// Mirror Cesium's real DataSourceCollection.add(): the returned promise
// resolves — and the collection mutates — on a DEFERRED tick, never in
// the caller's synchronous frame. A remove() issued before the deferred
// add settles therefore removes NOTHING and the add still materializes
// afterwards (empirically verified against Cesium). Tests may hold the
// settle window open via addControl to abort inside it, and may mark an
// add rejected-AFTER-push: Cesium pushes into the collection BEFORE
// raising dataSourceAdded, so a throwing listener rejects the promise
// after the mutation already landed.
add: (source) => {
const callIndex = addCalls.count;
addCalls.count += 1;
return new Promise((resolve, reject) => {
const settle = () => {
dataSources.push(source);
if (addControl?.rejectAfterPush?.has(callIndex)) {
reject(
new Error(
'dataSourceAdded listener threw after the push landed',
),
);
return;
}
resolve(source);
};
if (addControl?.held) addControl.pending.push(settle);
else Promise.resolve().then(settle);
});
},
remove: (source) => {
const index = dataSources.indexOf(source);
if (index >= 0) dataSources.splice(index, 1);
return index >= 0;
},
},
camera: {
positionWC: Cesium.Cartesian3.fromDegrees(-40, 35, 4_500_000),
frustum: { fov: Math.PI / 3 },
moveEnd: {
addEventListener: (fn) => {
listeners.moveEnd.add(fn);
return () => listeners.moveEnd.delete(fn);
},
},
heading: 0,
cancelFlight() {},
flyTo() {},
},
scene: {
canvas: { clientHeight: 900 },
requestRender() {},
pick() {
return null;
},
preRender: {
addEventListener: (fn) => {
listeners.preRender.add(fn);
return () => listeners.preRender.delete(fn);
},
},
},
};
}
/**
* Harness whose fetches stay PENDING until the test releases them, honoring
* the abort signal like real fetch (reject with AbortError on abort). Load
* lifecycle races are sequenced deterministically through it.
*/
function createDeferredCableLayerHarness() {
const hostCalls = [];
const listeners = { preRender: new Set(), moveEnd: new Set() };
const dataSources = [];
const addCalls = { count: 0 };
const addControl = { held: false, pending: [], rejectAfterPush: new Set() };
const pendingFetches = [];
const abortError = () =>
Object.assign(new Error('The operation was aborted'), {
name: 'AbortError',
});
const originalFetch = globalThis.fetch;
globalThis.fetch = (url, options = {}) =>
new Promise((resolve, reject) => {
const entry = {
url: String(url),
settled: false,
release() {
if (entry.settled) return;
entry.settled = true;
resolve({ ok: true, json: async () => fixtureForUrl(url) });
},
};
const signal = options?.signal;
if (signal) {
if (signal.aborted) {
entry.settled = true;
reject(abortError());
return;
}
signal.addEventListener(
'abort',
() => {
if (entry.settled) return;
entry.settled = true;
reject(abortError());
},
{ once: true },
);
}
pendingFetches.push(entry);
});
const layer = createTeleGeographySubmarineCableLayer({
overlayHost: {
setVisible: (...args) => hostCalls.push(['visible', ...args]),
setEntries: (...args) => hostCalls.push(['entries', ...args]),
clearSource: (...args) => hostCalls.push(['clear', ...args]),
},
screenSpaceEventHandlerFactory: () => ({
setInputAction() {},
destroy() {},
}),
});
const viewer = makeStubViewer(listeners, dataSources, addCalls, addControl);
layer.init(viewer);
return {
layer,
viewer,
hostCalls,
dataSources,
addCalls,
listeners,
pendingFetches,
raiseSweep() {
for (const fn of listeners.preRender) fn();
},
releaseAll() {
for (const entry of [...pendingFetches]) entry.release();
pendingFetches.length = 0;
},
/** Park every subsequent dataSources.add in its deferred settle window. */
holdAdds() {
addControl.held = true;
},
/** Make the Nth add (0-based) push into the collection, THEN reject. */
failAddAfterPush(callIndex) {
addControl.rejectAfterPush.add(callIndex);
},
/** Let the held adds mutate the collection (the deferred settle lands). */
releaseHeldAdds() {
addControl.held = false;
const pending = addControl.pending.splice(0);
for (const settle of pending) settle();
},
async settle() {
// Drain the microtask/timer interleave the load path spans.
for (let i = 0; i < 8; i++)
await new Promise((resolve) => setTimeout(resolve, 0));
},
cleanup() {
globalThis.fetch = originalFetch;
},
};
}
/**
* Gate Cesium's GeoJsonDataSource.load: each call runs the REAL load, then
* parks its resolution behind a test-owned gate. Opening gate N and aborting
* in the same synchronous frame lands the abort exactly at the caller's
* post-await ownership check for that boundary.
*/
function installGeoJsonLoadGate() {
const original = Cesium.GeoJsonDataSource.load;
const calls = [];
const gates = [];
Cesium.GeoJsonDataSource.load = function gatedLoad(...args) {
calls.push(args);
let open;
const opened = new Promise((resolve) => {
open = resolve;
});
gates.push({ open });
return original
.apply(Cesium.GeoJsonDataSource, args)
.then((result) => opened.then(() => result));
};
return {
calls,
open(index) {
gates[index]?.open();
},
/**
* Open every gate, including ones created by continuations the opening
* itself unblocks (a load calls the landing build only after the cable
* gate opens), until no new gates appear.
*/
async drainOpen(settle) {
let opened = 0;
while (opened < gates.length) {
for (; opened < gates.length; opened++) gates[opened].open();
await settle();
}
},
restore() {
Cesium.GeoJsonDataSource.load = original;
},
};
}
test('an abort landing at the post-fetch boundary stops the stale load before GeoJson work', async () => {
const env = createDeferredCableLayerHarness();
const geojson = installGeoJsonLoadGate();
try {
env.layer.enable(env.viewer);
// Release A's fetches and abort in the SAME frame: A's continuation runs
// with ownership already lost, and the post-fetch owns() check is the guard.
env.releaseAll();
env.layer.disable();
env.layer.enable(env.viewer);
await env.settle();
assert.equal(
geojson.calls.length,
0,
'stale load A must not reach GeoJsonDataSource.load',
);
env.releaseAll();
await env.settle();
await geojson.drainOpen(() => env.settle());
assert.equal(
geojson.calls.length,
2,
'load B alone performs the two GeoJson loads',
);
assert.equal(env.addCalls.count, 3);
assert.equal(env.dataSources.length, 3);
assert.equal(env.layer.getStats().count, 2);
} finally {
geojson.restore();
env.layer.destroy(env.viewer);
env.cleanup();
}
});
test('an abort landing at the post-cable-GeoJson boundary stops the stale load before the landing build', async () => {
const env = createDeferredCableLayerHarness();
const geojson = installGeoJsonLoadGate();
try {
env.layer.enable(env.viewer);
env.releaseAll();
await env.settle();
assert.equal(env.pendingFetches.length, 0);
assert.equal(
geojson.calls.length,
1,
'load A is parked on the gated cable GeoJson build',
);
// Open the cable gate and abort in the same frame: A resumes at the
// post-cable owns() check with ownership lost.
geojson.open(0);
env.layer.disable();
env.layer.enable(env.viewer);
await env.settle();
// A already cached the fetched JSON, so load B skips the network and goes
// straight to its own CABLE build; the only new GeoJson call must be that
// one — a landing build here would mean stale A ran on past its check.
assert.equal(
env.pendingFetches.length,
0,
'load B rebuilds from the cached JSON without refetching',
);
assert.equal(
geojson.calls.length,
2,
'stale load A must not start the landing GeoJson build',
);
assert.ok(
Cesium.Color.equals(
geojson.calls[1][1].stroke,
geojson.calls[0][1].stroke,
),
"the second GeoJson call is load B's cable build, not A's landing build",
);
env.releaseAll();
await env.settle();
await geojson.drainOpen(() => env.settle());
assert.equal(
geojson.calls.length,
3,
'load B adds its own two GeoJson builds',
);
assert.equal(env.addCalls.count, 3);
assert.equal(env.dataSources.length, 3);
assert.equal(env.layer.getStats().count, 2);
} finally {
geojson.restore();
env.layer.destroy(env.viewer);
env.cleanup();
}
});
test('an abort landing at the pre-add boundary stops the stale load before any dataSources.add', async () => {
const env = createDeferredCableLayerHarness();
const geojson = installGeoJsonLoadGate();
try {
env.layer.enable(env.viewer);
env.releaseAll();
await env.settle();
geojson.open(0);
await env.settle();
assert.equal(
geojson.calls.length,
2,
'load A is parked on the gated landing GeoJson build',
);
// Open the landing gate and abort in the same frame: A resumes at the
// pre-add owns() check with ownership lost.
geojson.open(1);
env.layer.disable();
env.layer.enable(env.viewer);
await env.settle();
assert.equal(
env.addCalls.count,
0,
'stale load A must never call dataSources.add',
);
env.releaseAll();
await env.settle();
await geojson.drainOpen(() => env.settle());
assert.equal(env.addCalls.count, 3, 'load B alone adds the trio');
assert.equal(env.dataSources.length, 3);
assert.equal(env.layer.getStats().count, 2);
} finally {
geojson.restore();
env.layer.destroy(env.viewer);
env.cleanup();
}
});
test('a destroy inside the deferred add window leaves zero post-destroy data sources', async () => {
const env = createDeferredCableLayerHarness();
try {
env.layer.enable(env.viewer);
env.releaseAll();
env.holdAdds();
// Let the load run until all three adds have been CALLED but none has
// settled — Cesium mutates the collection on a deferred tick, so this
// window is real, and a remove() issued inside it removes nothing.
const deadline = Date.now() + 2_000;
while (env.addCalls.count < 3 && Date.now() < deadline) {
await new Promise((resolve) => setTimeout(resolve, 0));
}
assert.equal(
env.addCalls.count,
3,
'all three adds are in their deferred window',
);
assert.equal(
env.dataSources.length,
0,
'nothing has settled into the collection yet',
);
// Destroy lands in the window: it can remove nothing, so the stale
// load's own post-add ownership check must compensate once the deferred
// adds settle.
env.layer.destroy(env.viewer);
env.releaseHeldAdds();
await env.settle();
assert.equal(
env.dataSources.length,
0,
'the deferred adds settled post-destroy and must be rolled back by their own load',
);
// The layer still honors its re-init contract afterwards.
env.layer.enable(env.viewer);
env.releaseAll();
await env.settle();
assert.equal(
env.dataSources.length,
3,
're-enable after the raced destroy loads cleanly',
);
assert.equal(env.layer.getStats().count, 2);
} finally {
env.layer.destroy(env.viewer);
env.cleanup();
}
});
test('a destroy in the deferred window with a rejected-after-push add still leaves zero sources', async () => {
const env = createDeferredCableLayerHarness();
try {
env.layer.enable(env.viewer);
env.releaseAll();
env.holdAdds();
// Cesium pushes the source into the collection BEFORE raising
// dataSourceAdded, so a throwing listener rejects the add promise AFTER
// its mutation landed. Make add #1 exactly that: pushed, then rejected;
// #2/#3 fulfill normally.
env.failAddAfterPush(0);
const deadline = Date.now() + 2_000;
while (env.addCalls.count < 3 && Date.now() < deadline) {
await new Promise((resolve) => setTimeout(resolve, 0));
}
assert.equal(
env.addCalls.count,
3,
'all three adds are in their deferred window',
);
assert.equal(
env.dataSources.length,
0,
'nothing has settled into the collection yet',
);
// Destroy lands in the window; the adds then settle as
// [rejected-after-push, fulfilled, fulfilled]. Compensation that removes
// only FULFILLED adds would strand the rejected-but-pushed source.
env.layer.destroy(env.viewer);
env.releaseHeldAdds();
await env.settle();
assert.equal(
env.dataSources.length,
0,
'a rejected add still landed its push, so compensation must remove it too',
);
} finally {
env.layer.destroy(env.viewer);
env.cleanup();
}
});
test('a direct destroy of a published layer clears and hides its host source', async () => {
const env = createDeferredCableLayerHarness();
try {
env.layer.enable(env.viewer);
env.releaseAll();
await env.settle();
assert.equal(env.dataSources.length, 3, 'layer is fully loaded');
// Drive the sweep through the layer's real preRender listener so the
// host actually holds published cable entries.
env.raiseSweep();
assert.ok(
env.hostCalls.some(([type]) => type === 'entries'),
'sweep must publish before the direct destroy',
);
env.hostCalls.length = 0;
// Destroy DIRECTLY — no disable() first. The publisher teardown must
// still clear the published source and hide it; skipping it would leave
// a visible overlay source with orphan labels.
env.layer.destroy(env.viewer);
assert.ok(
env.hostCalls.some(
([type, id]) => type === 'clear' && id === CABLE_OVERLAY_SOURCE_ID,
),
'direct destroy must clear the published host source',
);
assert.deepEqual(
env.hostCalls.at(-1),
['visible', CABLE_OVERLAY_SOURCE_ID, false],
'direct destroy must hide the host source after clearing it',
);
} finally {
env.cleanup();
}
});
test("a stale aborted load never clobbers the next load's lifecycle (A/B/C toggle race)", async () => {
const env = createDeferredCableLayerHarness();
try {
// A: enable starts a load whose fetches stay pending.
env.layer.enable(env.viewer);
assert.equal(
env.pendingFetches.length,
2,
'load A has two fetches in flight',
);
// Abort A and immediately start B before A\'s rejection lands.
env.layer.disable();
env.layer.enable(env.viewer);
await env.settle(); // A\'s stale catch/finally runs here, after B began.
// C: with A\'s stale completion having cleared nothing it does not own,
// the layer still reports a load in flight, so update() must not start a
// third load.
void env.layer.update(env.viewer);
await env.settle();
const inFlight = env.pendingFetches.filter(
(entry) => !entry.settled,
).length;
assert.equal(
inFlight,
2,
"only load B's two fetches remain in flight — no duplicate load C",
);
// Releasing everything must produce exactly one set of scene adds.
env.releaseAll();
await env.settle();
assert.equal(
env.addCalls.count,
3,
'exactly one cable/landing/reference data-source trio is added',
);
assert.equal(env.dataSources.length, 3);
const stats = env.layer.getStats();
assert.equal(stats.error, null);
assert.equal(stats.count, 2, 'one cable + one landing point loaded once');
} finally {
env.layer.destroy(env.viewer);
env.cleanup();
}
});
test('destroy mid-load stays clean and a re-enabled layer reloads exactly once', async () => {
const env = createDeferredCableLayerHarness();
try {
// Destroy while load A\'s fetches are still in flight.
env.layer.enable(env.viewer);
assert.equal(env.pendingFetches.length, 2);
env.layer.destroy(env.viewer);
// Legacy contract: the layer supports re-init after destroy. Enable D
// before A\'s aborted rejection lands.
env.layer.enable(env.viewer);
await env.settle(); // A\'s stale finally runs here.
// A\'s stale completion must not have cleared D\'s in-flight lifecycle:
// update() must not start a duplicate load E.
void env.layer.update(env.viewer);
await env.settle();
const inFlight = env.pendingFetches.filter(
(entry) => !entry.settled,
).length;
assert.equal(inFlight, 2, "only load D's two fetches remain in flight");
env.releaseAll();
await env.settle();
assert.equal(
env.addCalls.count,
3,
'the destroyed load added nothing; the re-enable added once',
);
assert.equal(env.dataSources.length, 3);
assert.equal(env.layer.getStats().count, 2);
} finally {
env.layer.destroy(env.viewer);
env.cleanup();
}
});
async function createRealCableLayerHarness({
source,
mapStackEventTarget = null,
} = {}) {
const hostCalls = [];
// The sweep gate's motion-fallback clock is FROZEN here and advanced only by
// the tests that exercise it, so every other harness test observes the pure
// event-dirty behavior with no wall-clock coupling.
let clockMs = 0;
const layer = createTeleGeographySubmarineCableLayer({
overlayHost: {
setVisible: (...args) => hostCalls.push(['visible', ...args]),
setEntries: (...args) => hostCalls.push(['entries', ...args]),
clearSource: (...args) => hostCalls.push(['clear', ...args]),
},
screenSpaceEventHandlerFactory: () => ({
setInputAction() {},
destroy() {},
}),
...(source ? { source } : {}),
mapStackEventTarget,
sweepClock: () => clockMs,
});
const listeners = { preRender: new Set(), moveEnd: new Set() };
const dataSources = [];
const viewer = {
dataSources: {
add: (source) => {
dataSources.push(source);
return Promise.resolve(source);
},
remove: (source) => {
const index = dataSources.indexOf(source);
if (index >= 0) dataSources.splice(index, 1);
return true;
},
},
camera: {
positionWC: Cesium.Cartesian3.fromDegrees(-40, 35, 4_500_000),
frustum: { fov: Math.PI / 3 },
moveEnd: {
addEventListener: (fn) => {
listeners.moveEnd.add(fn);
return () => listeners.moveEnd.delete(fn);
},
},
heading: 0,
cancelFlight() {},
flyTo() {},
},
scene: {
canvas: { clientHeight: 900 },
requestRender() {},
pick() {
return null;
},
preRender: {
addEventListener: (fn) => {
listeners.preRender.add(fn);
return () => listeners.preRender.delete(fn);
},
},
},
};
const originalFetch = globalThis.fetch;
// Node has no `document`, so Point features (whose GeoJSON default marker
// needs a canvas PinBuilder) cannot load here. Landing references only need
// `properties.coordinates`, so a line geometry exercises the same path.
globalThis.fetch = async (url) => ({
ok: true,
json: async () =>
String(url).includes('landing-point')
? {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
id: 'lisbon',
properties: {
id: 'lisbon',
name: 'Lisbon, Portugal',
coordinates: [-9.1, 38.7],
},
geometry: {
type: 'LineString',
coordinates: [
[-9.1, 38.7],
[-9.05, 38.72],
],
},
},
],
}
: {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
id: 'ac1',
properties: { id: 'ac1', name: 'Atlantic Crossing-1' },
geometry: {
type: 'LineString',
coordinates: [
[-40, 35],
[-30, 40],
],
},
},
],
},
});
try {
layer.init(viewer);
layer.enable(viewer);
// enable() fires load() without awaiting it (legacy contract); poll the
// layer's own stats until the load settles.
const deadline = Date.now() + 2_000;
while (Date.now() < deadline) {
const stats = layer.getStats();
if (stats.count > 0 || stats.error) break;
await new Promise((resolve) => setTimeout(resolve, 10));
}
assert.equal(layer.getStats().error, null, 'harness load must succeed');
} finally {
globalThis.fetch = originalFetch;
}
const raiseSweep = () => {
for (const fn of listeners.preRender) fn();
};
const advanceClock = (ms) => {
clockMs += ms;
};
return {
layer,
viewer,
hostCalls,
dataSources,
raiseSweep,
listeners,
advanceClock,
};
}
test('a real enabled cable layer publishes host entries and has zero native labels', async () => {
const env = await createRealCableLayerHarness();
env.raiseSweep();
const publishes = env.hostCalls.filter(([type]) => type === 'entries');
assert.ok(publishes.length > 0, 'sweep must publish to the shared host');
const [, sourceId, entries, options] = publishes.at(-1);
assert.equal(sourceId, CABLE_OVERLAY_SOURCE_ID);
assert.equal(entries.length, 2, 'one cable + one landing point reference');
assert.ok(entries.every((entry) => entry.variant === 'label'));
assert.deepEqual(options, {
cohortLimit: CABLE_REFERENCE_LABEL_WINNER_CAP,
collisionCapacity: CABLE_OVERLAY_COLLISION_CAPACITY,
moving: false,
});
const referenceSource = env.dataSources.find((ds) =>
/References/.test(ds.name || ''),
);
assert.ok(referenceSource, 'reference data source must exist');
const referenceEntities = referenceSource.entities.values;
assert.ok(referenceEntities.length > 0);
assert.ok(
referenceEntities.every((entity) => entity.label === undefined),
'no reference entity may carry a native LabelGraphics',
);
assert.ok(
referenceEntities.every(
(entity) =>
entity.position?.isConstant === true &&
entity.polyline?.positions?.isConstant === true,
),
'stem properties must be constants, never per-frame callbacks',
);
const stats = env.layer.getStats();
assert.equal(stats.referenceLabelCount, 2);
env.layer.disable();
assert.ok(
env.hostCalls.some(
([type, id]) => type === 'clear' && id === CABLE_OVERLAY_SOURCE_ID,
),
'disable must clear the host source',
);
assert.deepEqual(
env.hostCalls.at(-1),
['visible', CABLE_OVERLAY_SOURCE_ID, false],
'disable must hide the host source after clearing it',
);
env.layer.destroy(env.viewer);
assert.equal(
env.dataSources.length,
0,
'destroy must remove every data source',
);
});
test('stems never rebuild mid-drag; exactly one rebuild lands on moveEnd', async () => {
const env = await createRealCableLayerHarness();
env.raiseSweep(); // initial sizing at the parked camera
const referenceSource = env.dataSources.find((ds) =>
/References/.test(ds.name || ''),
);
const entity = referenceSource.entities.values[0];
let stemRebuilds = 0;
const originalSet = entity.polyline.positions.setValue.bind(
entity.polyline.positions,
);
entity.polyline.positions.setValue = (...args) => {
stemRebuilds++;
return originalSet(...args);
};
// Mid-drag: the camera moves every tick and frames keep rendering, but no
// moveEnd fires. However long this goes on, the stem constants must never
// be redefined — each redefinition rebuilt the whole batched stem
// primitive, and its unready replacement frames were the felt hitch.
const camera = env.viewer.camera.positionWC;
for (let tick = 0; tick < 30; tick++) {
env.viewer.camera.positionWC = Cesium.Cartesian3.add(
camera,
new Cesium.Cartesian3(0, 0, 100_000 * (tick + 1)),
new Cesium.Cartesian3(),
);
env.raiseSweep(); // preRender fires every frame during a drag
await new Promise((resolve) => setTimeout(resolve, 25));
}
assert.equal(
stemRebuilds,
0,
'render ticks without moveEnd must never redefine a stem',
);
// The gesture ends close over the cable's reference point (inside the
// 700 m..85 km stem clamp band, so the re-size is a real tip change):
// one moveEnd, one sweep, exactly one redefinition.
env.viewer.camera.positionWC = Cesium.Cartesian3.fromDegrees(
-35,
37.5,
400_000,
);
for (const fn of env.listeners.moveEnd) fn();
env.raiseSweep();
assert.equal(stemRebuilds, 1, 'moveEnd re-sizes the stem exactly once');
env.raiseSweep();
assert.equal(stemRebuilds, 1, 'the next frame does not re-run the sweep');
env.layer.destroy(env.viewer);
});
test('a tracked camera that never emits moveEnd is swept by the motion fallback', async () => {
const env = await createRealCableLayerHarness();
// Tracking hands over a mid-altitude follow camera. 400 km keeps the stem
// inside the 700 m..85 km clamp band, so a sweep is observable as a real
// tip re-size (at the harness's 4,500 km parking altitude every stem sits
// pinned at the 85 km clamp and no sweep could be distinguished).
env.viewer.camera.positionWC = Cesium.Cartesian3.fromDegrees(
-35,
37.5,
400_000,
);
env.raiseSweep(); // initial sizing at the tracking handoff
const referenceSource = env.dataSources.find((ds) =>
/References/.test(ds.name || ''),
);
const entity = referenceSource.entities.values[0];
let stemRebuilds = 0;
const originalSet = entity.polyline.positions.setValue.bind(
entity.polyline.positions,
);
entity.polyline.positions.setValue = (...args) => {
stemRebuilds++;
return originalSet(...args);
};
// Entity tracking / orbit: the follow camera advances every frame and
// moveEnd NEVER fires. Frames are simulated at 60 Hz on the harness clock.
const origin = env.viewer.camera.positionWC;
const frameMs = 1000 / 60;
const trackFrames = (from, to) => {
for (let frame = from; frame < to; frame++) {
env.advanceClock(frameMs);
env.viewer.camera.positionWC = Cesium.Cartesian3.add(
origin,
new Cesium.Cartesian3(0, 0, 200 * (frame + 1)),
new Cesium.Cartesian3(),
);
env.raiseSweep();
}
};
// Inside the first probe window the sweep stays event-dirty-only, so the
// batched stem primitive is not rebuilt mid-motion (the shipped tradeoff).
trackFrames(0, 60);
assert.equal(
stemRebuilds,
0,
'inside the probe window a tracked camera still costs no rebuilds',
);
// Past the window the fallback fires — exactly once, not per frame.
trackFrames(60, 130);
assert.equal(
stemRebuilds,
1,
'the tracked camera earns exactly one fallback sweep per window',
);
// Tracking stops but frames keep rendering. The motion accumulated since
// the last fallback sweep settles in exactly ONE further sweep...
const parkFrames = (count) => {
for (let frame = 0; frame < count; frame++) {
env.advanceClock(frameMs);
env.raiseSweep();
}
};
parkFrames(240); // 4 s parked: two full probe windows
assert.equal(
stemRebuilds,
2,
'a stopped camera settles its residual motion in one further sweep',
);
// ...and the parked camera then costs zero sweeps for as long as it sits.
parkFrames(3600); // 60 s parked
assert.equal(
stemRebuilds,
2,
'a settled parked camera never spends another sweep',
);
env.layer.destroy(env.viewer);
});
test('an unchanged cohort never republishes, so a parked camera stays governor-idle', async () => {
const env = await createRealCableLayerHarness();
const publishCount = () =>
env.hostCalls.filter(([type]) => type === 'entries').length;
env.raiseSweep();
assert.equal(publishCount(), 1, 'first sweep publishes the cohort');
// moveEnd with an unchanged camera re-arms the sweep; the identical winner
// set must NOT reach the host again (each publish costs a requested render).
for (const fn of env.listeners.moveEnd) fn();
env.raiseSweep();
for (const fn of env.listeners.moveEnd) fn();
env.raiseSweep();
assert.equal(publishCount(), 1, 'identical cohorts are skipped');
// A real camera move changes distances beyond the 50 km priority quantum,
// so the next sweep publishes again.
env.viewer.camera.positionWC = Cesium.Cartesian3.fromDegrees(
-40,
35,
6_500_000,
);
for (const fn of env.listeners.moveEnd) fn();
env.raiseSweep();
assert.equal(publishCount(), 2, 'a changed cohort publishes');
// disable → enable must force a republish even when the cohort is identical
// (the hide cleared the host source).
env.layer.disable();
env.layer.enable(env.viewer);
await new Promise((resolve) => setTimeout(resolve, 10));
env.raiseSweep();
assert.equal(
publishCount(),
3,
're-enable republishes after the hide cleared the source',
);
env.layer.destroy(env.viewer);
});
test('a toggle-off releases every data source and a toggle-on rebuilds them without refetching', async () => {
const env = await createRealCableLayerHarness();
assert.equal(
env.dataSources.length,
3,
'cable + landing + reference sources while on',
);
const before = env.layer.getStats().count;
assert.equal(before, 2);
// Any fetch after this point is a bug: the rebuild must come from the cache.
const originalFetch = globalThis.fetch;
let fetches = 0;
globalThis.fetch = async () => {
fetches += 1;
throw new Error('unexpected refetch');
};
try {
env.layer.disable();
assert.equal(
env.dataSources.length,
0,
'hidden entities are not free; disable must remove the sources',
);
env.layer.enable(env.viewer);
const deadline = Date.now() + 2_000;
while (Date.now() < deadline && env.dataSources.length < 3) {
await new Promise((resolve) => setTimeout(resolve, 10));
}
assert.equal(fetches, 0, 'the rebuild must not touch the network');
assert.equal(env.layer.getStats().error, null);
assert.equal(env.dataSources.length, 3, 're-enable rebuilds the trio');
assert.equal(env.layer.getStats().count, before);
const referenceSource = env.dataSources.find((ds) =>
/References/.test(ds.name || ''),
);
assert.ok(
referenceSource?.entities.values.length > 0,
'reference stems are rebuilt',
);
env.layer.destroy(env.viewer);
assert.equal(env.dataSources.length, 0);
} finally {
globalThis.fetch = originalFetch;
}
});
test('another GeoJSON source renders with its own label and rebuilds from accepted cache', async () => {
let reads = 0;
const source = {
label: 'Test cable directory',
async fetch(signal) {
signal.throwIfAborted();
reads += 1;
return { cables: CABLE_FIXTURE, landingPoints: LANDING_FIXTURE };
},
};
const env = await createRealCableLayerHarness({ source });
try {
assert.equal(env.layer.source, source.label);
assert.equal(reads, 1);
assert.equal(env.dataSources.length, 3);
assert.ok(
env.dataSources.every((item) => item.name.startsWith(source.label)),
);
env.layer.disable();
assert.equal(env.dataSources.length, 0);
env.layer.enable(env.viewer);
for (let i = 0; i < 8; i++)
await new Promise((resolve) => setTimeout(resolve, 0));
assert.equal(env.layer.getStats().count, 2);
assert.equal(env.dataSources.length, 3);
assert.equal(reads, 1);
} finally {
env.layer.destroy(env.viewer);
}
});
function multipartCableFixture() {
return [
{
type: 'Feature',
properties: {
id: 'alpha',
name: 'Alpha cable',
color: '#ff0000',
coordinates: [-40, 35],
},
geometry: {
type: 'MultiLineString',
coordinates: [
[
[-40, 35],
[-39, 36],
],
[
[-38, 37],
[-37, 38],
],
[
[-36, 39],
[-35, 40],
],
],
},
},
{
type: 'Feature',
properties: {
id: 'beta',
name: 'Beta cable',
color: '#0000ff',
coordinates: [140, -35],
},
geometry: {
type: 'LineString',
coordinates: [
[140, -35],
[141, -34],
],
},
},
];
}
function geometrySource(cables, landingPoints = []) {
return {
label: 'Multipart test source',
async fetch() {
return {
cables: { type: 'FeatureCollection', features: cables },
landingPoints: { type: 'FeatureCollection', features: landingPoints },
};
},
};
}
test('multipart association accepts frozen provider collections without modifying properties', async () => {
const payload = {
cables: { type: 'FeatureCollection', features: multipartCableFixture() },
landingPoints: {
type: 'FeatureCollection',
features: multipartCableFixture(),
},
};
payload.cables.features[0].properties.__gevSourceFeatureIndex =
'provider value';
const before = structuredClone(payload);
const freeze = (value) => {
if (value && typeof value === 'object') {
for (const child of Object.values(value)) freeze(child);
Object.freeze(value);
}
return value;
};
freeze(payload);
const env = await createRealCableLayerHarness({
source: { label: 'Frozen multipart source', fetch: async () => payload },
});
try {
for (const dataSource of env.dataSources.slice(0, 2)) {
assert.deepEqual(
dataSource.entities.values.map(
(entity) => entity.__gevTeleGeography?.label,
),
['Alpha cable', 'Alpha cable', 'Alpha cable', 'Beta cable'],
);
}
assert.equal(env.dataSources[2].entities.values.length, 4);
assert.deepEqual(payload, before);
const entity = env.dataSources[0].entities.values[0];
assert.equal(entity.properties.__gevSourceFeatureIndex.getValue(), 0);
assert.match(entity.description.getValue(), /__gevSourceFeatureIndex/);
} finally {
env.layer.destroy(env.viewer);
}
});
test('multipart cables retain their own styling, picking and culling on every segment', async () => {
const features = multipartCableFixture();
const before = JSON.stringify(features);
const env = await createRealCableLayerHarness({
source: geometrySource(features),
});
try {
const entities = env.dataSources[0].entities.values;
assert.equal(entities.length, 4);
for (const [index, entity] of entities.entries()) {
const feature = features[index < 3 ? 0 : 1];
const info = entity.__gevTeleGeography;
assert.equal(info?.label, feature.properties.name, entity.id);
assert.deepEqual(info.reference, {
lon: feature.properties.coordinates[0],
lat: feature.properties.coordinates[1],
});
assert.deepEqual(
entity.polyline.material.color.getValue(),
Cesium.Color.fromCssColorString(feature.properties.color).withAlpha(
0.92,
),
);
assert.equal(entity.polyline.width.getValue(), 2.5);
}
env.raiseSweep();
assert.deepEqual(
entities.map((entity) => entity.show),
[true, true, true, false],
);
assert.equal(
env.dataSources[2].entities.values.length,
2,
'one reference per feature',
);
assert.equal(
JSON.stringify(features),
before,
'source features stay unchanged',
);
} finally {
env.layer.destroy(env.viewer);
}
});
test('multipart association supports missing and repeated IDs without guessing Cesium suffixes', async () => {
const features = multipartCableFixture();
features[1].id = 'alpha_2';
delete features[1].properties.id;
const generated = structuredClone(features[1]);
delete generated.id;
generated.properties.name = 'Generated cable';
features.push(generated);
const repeated = structuredClone(features[1]);
repeated.properties.id = 'alpha';
repeated.properties.name = 'Repeated ID cable';
features.push(repeated);
const env = await createRealCableLayerHarness({
source: geometrySource(features),
});
try {
const entities = env.dataSources[0].entities.values;
assert.deepEqual(
entities.map((entity) => entity.id),
['alpha', 'alpha_2', 'alpha_3', 'alpha_2_2', 'cable-2', 'alpha_4'],
);
assert.deepEqual(
entities.map((entity) => entity.__gevTeleGeography?.label),
[
'Alpha cable',
'Alpha cable',
'Alpha cable',
'Beta cable',
'Generated cable',
'Repeated ID cable',
],
);
assert.deepEqual(
env.dataSources[2].entities.values.map(
(entity) => entity.__gevTeleGeography.featureId,
),
['alpha', 'alpha_2', 'cable-2', 'alpha'],
);
} finally {
env.layer.destroy(env.viewer);
}
});
test('multipart landing geometry keeps every pick with one reference per feature', async () => {
const features = multipartCableFixture();
const env = await createRealCableLayerHarness({
source: geometrySource([], features),
});
try {
const entities = env.dataSources[1].entities.values;
assert.deepEqual(
entities.map((entity) => entity.__gevTeleGeography?.label),
['Alpha cable', 'Alpha cable', 'Alpha cable', 'Beta cable'],
);
assert.ok(
entities.every(
(entity) => entity.__gevTeleGeography.kind === 'landing-point',
),
);
assert.equal(env.dataSources[2].entities.values.length, 2);
} finally {
env.layer.destroy(env.viewer);
}
});
test('every bundled cable segment is associated with its source feature', async () => {
const { features } = JSON.parse(
readFileSync(
new URL(
'./local_data/telegeography_submarine_cables/cable-geo.json',
import.meta.url,
),
'utf8',
),
);
// Some bundled features share a cable ID but have different references.
const expandedFeatures = features.flatMap((feature) =>
feature.geometry.coordinates.map(() => feature),
);
assert.ok(expandedFeatures.length > features.length);
const env = await createRealCableLayerHarness({
source: geometrySource(features),
});
try {
const entities = env.dataSources[0].entities.values;
assert.equal(entities.length, expandedFeatures.length);
for (const [index, entity] of entities.entries()) {
const feature = expandedFeatures[index];
assert.equal(
entity.__gevTeleGeography?.label,
feature.properties.name,
entity.id,
);
assert.deepEqual(entity.__gevTeleGeography.reference, {
lon: feature.properties.coordinates[0],
lat: feature.properties.coordinates[1],
});
}
assert.equal(env.dataSources[2].entities.values.length, features.length);
} finally {
env.layer.destroy(env.viewer);
}
});