// src/data/earthquakes.test.mjs // Focused tests for the pure analyst-record mapper (analyst query engine seam). // Pure function — no viewer/DOM needed; imported directly. import { test } from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import * as Cesium from 'cesium'; import { EARTHQUAKE_OVERLAY_COHORT_LIMIT, EARTHQUAKE_OVERLAY_COLLISION_CAPACITY, createEarthquakeOverlayEntry, createEarthquakesLayer, mapAnalystRecord, selectEarthquakeOverlayCohort, } from './earthquakes.js'; import { DataLayerManager } from './manager.js'; import { getRenderGovernorDiagnostics, installRenderGovernor, _resetRenderGovernorForTest, } from '../renderGovernor.js'; const FULL_RAW = { id: 'us7000abcd', mag: 5.2, place: '42 km SW of Anchorage, Alaska', time: 1_753_600_000_000, depth: 41.7, lat: 61.02, lon: -150.41, }; test('earthquake analyst record: full record maps every contract field', () => { const r = mapAnalystRecord(FULL_RAW, 3); assert.deepEqual(r, { id: 'us7000abcd', magnitude: 5.2, depthKm: 41.7, lat: 61.02, lon: -150.41, timeMs: 1_753_600_000_000, place: '42 km SW of Anchorage, Alaska', }); }); test('earthquake analyst record: missing USGS id falls back to index-based id', () => { assert.equal(mapAnalystRecord({ ...FULL_RAW, id: null }, 3).id, 'QUAKE-0003'); assert.equal(mapAnalystRecord({ ...FULL_RAW, id: ' ' }, 41).id, 'QUAKE-0041'); assert.equal(mapAnalystRecord(undefined).id, 'QUAKE-0000'); }); test('earthquake analyst record: missing fields become null, never NaN/undefined', () => { const r = mapAnalystRecord({ id: 'us1', mag: NaN, depth: undefined, place: '' }, 0); assert.equal(r.magnitude, null); assert.equal(r.depthKm, null); assert.equal(r.place, null); for (const [key, value] of Object.entries(r)) { assert.notEqual(value, undefined, `${key} must not be undefined`); if (typeof value === 'number') assert.ok(Number.isFinite(value), `${key} must not be NaN`); } }); test('earthquake analyst record: output is JSON-safe (no Cesium types)', () => { const r = mapAnalystRecord(FULL_RAW, 0); assert.deepEqual(JSON.parse(JSON.stringify(r)), r); }); test('earthquake overlay copy keeps source-side magnitude formatting and bounded priority', () => { const position = Cesium.Cartesian3.fromDegrees(-150.41, 61.02); const entry = createEarthquakeOverlayEntry({ id: 'us7000abcd', position, magnitude: 5.24, accent: '#ff0000', }); assert.equal(entry.title, 'M5.2'); assert.equal(entry.position, position); assert.equal(entry.variant, 'label'); assert.equal(entry.paintLane, 'ambient-label'); assert.equal(entry.collisionGroup, 'ambient-label'); assert.equal(entry.protected, undefined); assert.equal(entry.edgeFade, 'keyhole'); assert.equal(entry.horizonCull, true); const entries = Array.from({ length: EARTHQUAKE_OVERLAY_COHORT_LIMIT + 20 }, (_, index) => ({ id: `quake-${String(index).padStart(3, '0')}`, priority: index, })); const cohort = selectEarthquakeOverlayCohort(entries); assert.equal(cohort.length, EARTHQUAKE_OVERLAY_COHORT_LIMIT); assert.equal(cohort[0].id, `quake-${EARTHQUAKE_OVERLAY_COHORT_LIMIT + 19}`); assert.equal(cohort.at(-1).id, 'quake-020'); }); test('real earthquake lifecycle publishes host labels while runtime entities carry no label graphic', async () => { const originalFetch = globalThis.fetch; const hostCalls = []; const dataSources = []; const overlayHost = { setEntries: (...args) => hostCalls.push(['entries', ...args]), setVisible: (...args) => hostCalls.push(['visible', ...args]), clearSource: (...args) => hostCalls.push(['clear', ...args]), }; const viewer = { dataSources: { add(dataSource) { dataSources.push(dataSource); return dataSource; }, remove(dataSource) { const index = dataSources.indexOf(dataSource); if (index >= 0) dataSources.splice(index, 1); return index >= 0; }, }, }; globalThis.fetch = async () => ({ ok: true, json: async () => ({ features: [ { id: 'us-runtime-1', geometry: { coordinates: [-150.41, 61.02, 41.7] }, properties: { mag: 5.24, place: 'Runtime One', time: 1_753_600_000_000 }, }, { id: 'us-runtime-2', geometry: { coordinates: [139.7, 35.6, 310] }, properties: { mag: 3.01, place: 'Runtime Two', time: 1_753_600_100_000 }, }, ], }), }); const layer = createEarthquakesLayer({ overlayHost }); try { layer.init(viewer); layer.enable(viewer); await layer.update(viewer); const entities = dataSources[0].entities.values; assert.equal(entities.length, 2, 'runtime guard requires populated real source entities'); assert.ok(entities.every((entity) => entity.label === undefined)); const publication = hostCalls.find(([type]) => type === 'entries'); assert.ok(publication, 'real update path must publish the overlay source'); assert.deepEqual(publication[2].map(({ title }) => title), ['M5.2', 'M3.0']); assert.deepEqual(publication[3], { cohortLimit: EARTHQUAKE_OVERLAY_COHORT_LIMIT, collisionCapacity: EARTHQUAKE_OVERLAY_COLLISION_CAPACITY, moving: false, }); layer.disable(viewer); assert.equal(dataSources[0].show, false); assert.deepEqual(hostCalls.slice(-2), [ ['clear', 'earthquakes'], ['visible', 'earthquakes', false], ]); layer.destroy(viewer); assert.equal(dataSources.length, 0); assert.deepEqual(hostCalls.slice(-2), [ ['clear', 'earthquakes'], ['visible', 'earthquakes', false], ]); } finally { globalThis.fetch = originalFetch; } }); // ── Perf pin: the 2026-08-20 earthquakes frame-rate cliff ──────────────────── // Every disc is a CLAMP_TO_GROUND ellipse. When its axes were a // `CallbackProperty`, Cesium re-tessellated all 58 ground primitives on EVERY // frame: 32.4 ms/frame and 30 fps for 58 contacts, against 1.4 ms/60 fps with // the layer off. Static axes restored 60 fps. Both halves of the fix are // pinned — the runtime property shape, and the source-level guards. test('quake disc axes are STATIC — a per-frame callback re-tessellates ground geometry', async () => { const originalFetch = globalThis.fetch; const dataSources = []; const viewer = { dataSources: { add(dataSource) { dataSources.push(dataSource); return dataSource; }, remove() { return true; }, }, }; const layer = createEarthquakesLayer({ overlayHost: { setEntries() {}, setVisible() {}, clearSource() {} }, }); globalThis.fetch = async () => ({ ok: true, json: async () => ({ features: [{ id: 'us-static-1', geometry: { coordinates: [-122.4, 37.79, 8.2] }, properties: { mag: 5.5, place: 'Static One', time: 1_753_600_000_000 }, }], }), }); try { layer.init(viewer); layer.enable(viewer); await layer.update(viewer); const [entity] = dataSources[0].entities.values; assert.ok(entity.ellipse, 'the disc is an ellipse graphic'); for (const axis of ['semiMajorAxis', 'semiMinorAxis']) { const property = entity.ellipse[axis]; assert.equal( property instanceof Cesium.CallbackProperty, false, `${axis} must not be a CallbackProperty — it rebuilds the ground primitive every frame`, ); assert.equal(property.isConstant, true, `${axis} must be a constant property`); // Magnitude 5.5 → 2^5.5 * 1000 m, unchanged by the dropped pulse. assert.equal(property.getValue(Cesium.JulianDate.now()), Math.pow(2, 5.5) * 1000); } } finally { globalThis.fetch = originalFetch; layer.destroy(viewer); } }); // Dropping the continuous-render hold is only safe if new poll data still reaches // the screen. In idle mode nothing repaints on its own, so the manager's one-shot // request after each tick is now load-bearing for this layer. Wires the REAL layer // into the REAL manager with the governor installed, rather than trusting the // arrangement by inspection. test('a quake poll still reaches the screen with the render loop idle', async () => { const originalFetch = globalThis.fetch; const renderRequests = []; const dataSources = []; const viewer = { scene: { requestRenderMode: false, requestRender: () => renderRequests.push(Date.now()) }, dataSources: { add(dataSource) { dataSources.push(dataSource); return dataSource; }, remove() { return true; }, }, }; const layer = createEarthquakesLayer({ overlayHost: { setEntries() {}, setVisible() {}, clearSource() {} }, }); globalThis.fetch = async () => ({ ok: true, json: async () => ({ features: [{ id: 'us-idle-1', geometry: { coordinates: [-122.4, 37.79, 8.2] }, properties: { mag: 4.2, place: 'Idle One', time: 1_753_600_000_000 }, }], }), }); _resetRenderGovernorForTest(); installRenderGovernor(viewer); // Installing the governor enters idle mode, which itself paints one settling // frame. Drop that from the baseline or the enable assertion below can pass on // the install alone, without the manager ever requesting anything. renderRequests.length = 0; const manager = new DataLayerManager(viewer); // updateInterval -1 keeps the poll loop unarmed; we drive one tick by hand. manager.register({ ...layer, updateInterval: -1 }); try { await manager.setEnabled('earthquakes', true, { origin: 'test' }); // The whole point of the change: enabling quakes must not pin the loop on. assert.equal(getRenderGovernorDiagnostics().mode, 'idle', 'quakes must not force continuous render'); assert.deepEqual(getRenderGovernorDiagnostics().holds, []); assert.equal(viewer.scene.requestRenderMode, true, 'the governor really is in idle mode'); // ...and the poll that populated the discs must still have asked for a frame, // because in idle mode nothing repaints on its own. assert.ok(dataSources[0].entities.values.length > 0, 'the enable poll produced discs'); assert.ok(renderRequests.length > 0, 'enabling the layer must request a render in idle mode'); // Named, not merely counted: the frame has to come from the manager's // visibility request, not from some incidental repaint. assert.ok( getRenderGovernorDiagnostics().recentRequests.some(({ reason }) => reason === 'layer-visibility'), 'the enable frame must be the manager\'s layer-visibility request', ); // A LATER poll must request its own frame too — the enable-time // 'layer-visibility' request cannot cover refreshes that arrive minutes later. const beforeRefresh = renderRequests.length; await manager._runPeriodicUpdate('earthquakes', manager.layers.get('earthquakes')); assert.ok( renderRequests.length > beforeRefresh, 'each refresh tick must request its own render while the loop is idle', ); const reasons = getRenderGovernorDiagnostics().recentRequests.map(({ reason }) => reason); assert.ok( reasons.includes('layer-tick:earthquakes'), `the tick request must be attributed to the layer, got ${JSON.stringify(reasons)}`, ); } finally { globalThis.fetch = originalFetch; await manager.setEnabled('earthquakes', false, { origin: 'test' }).catch(() => {}); _resetRenderGovernorForTest(); } }); test('the earthquakes layer installs no per-frame callback and no continuous-render hold', () => { const source = ['index', 'model', 'source'].map(name => readFileSync(new URL(`../layers/earthquakes/${name}.js`, import.meta.url), 'utf8')).join('\n'); assert.doesNotMatch( source, /new Cesium\.CallbackProperty/, 'reverting the discs to a per-frame axis callback must fail this pin', ); assert.doesNotMatch( source, /holdContinuousRender/, 'static discs have no per-frame animator, so the layer must not pin the render loop on', ); }); test('earthquake refresh reports failure and clears it only after a successful response', async () => { const originalFetch = globalThis.fetch; const dataSources = []; const viewer = { dataSources: { add(dataSource) { dataSources.push(dataSource); return dataSource; }, remove() { return true; }, }, }; const layer = createEarthquakesLayer({ overlayHost: { setEntries() {}, setVisible() {}, clearSource() {} }, }); try { layer.init(viewer); layer.enable(viewer); globalThis.fetch = async () => ({ ok: false, status: 503 }); assert.equal(await layer.update(viewer), false); assert.equal(layer.getStats().error, 'USGS HTTP 503'); globalThis.fetch = async () => ({ ok: true, json: async () => ({ features: [] }), }); assert.equal(await layer.update(viewer), true); assert.equal(layer.getStats().error, null); assert.ok(Number.isFinite(layer.getStats().lastUpdate)); } finally { globalThis.fetch = originalFetch; layer.destroy(viewer); } }); test('malformed earthquake refresh preserves entities, overlays, count and timestamp', async () => { const originalFetch = globalThis.fetch; const dataSources = []; const publications = []; const viewer = { dataSources: { add(source) { dataSources.push(source); }, remove() { return true; }, } }; const layer = createEarthquakesLayer({ overlayHost: { setEntries(...args) { publications.push(args); }, setVisible() {}, clearSource() {}, } }); const good = { id: 'good', geometry: { type: 'Point', coordinates: [10, 20, 5] }, properties: { mag: 4, time: 100, place: 'Fixture' } }; const respond = (features) => { globalThis.fetch = async () => ({ ok: true, json: async () => ({ features }) }); }; try { layer.init(viewer); layer.enable(viewer); respond([good]); assert.equal(await layer.update(viewer), true); const entity = dataSources[0].entities.values[0]; const stats = layer.getStats(); for (const properties of ['invalid', 123, [], true]) { const bad = { ...good, id: 'bad-properties', properties }; for (const features of [[bad], [good, bad]]) { respond(features); assert.equal(await layer.update(viewer), false); assert.equal(dataSources[0].entities.values.length, 1); assert.equal(dataSources[0].entities.values[0], entity); assert.equal(layer.getStats().count, stats.count); assert.equal(layer.getStats().lastUpdate, stats.lastUpdate); assert.equal(layer.getStats().error, 'Malformed USGS response'); assert.equal(publications.length, 1); } } for (const bad of [null, { ...good, geometry: null }, { ...good, geometry: { type: 'LineString', coordinates: [1, 2] } }, { ...good, geometry: { coordinates: [181, 20] } }, { ...good, geometry: { coordinates: [10, -91] } }, { ...good, geometry: { coordinates: [null, 20] } }, { ...good, properties: { mag: '4' } }, { ...good, properties: { mag: Infinity } }, good]) { respond([good, bad]); assert.equal(await layer.update(viewer), false); assert.equal(dataSources[0].entities.values.length, 1); assert.equal(dataSources[0].entities.values[0], entity); assert.equal(layer.getStats().count, stats.count); assert.equal(layer.getStats().lastUpdate, stats.lastUpdate); assert.equal(layer.getStats().error, 'Malformed USGS response'); assert.equal(publications.length, 1); } respond([{ ...good, properties: { mag: null } }]); assert.equal(await layer.update(viewer), true); assert.equal(dataSources[0].entities.values.length, 0); assert.equal(layer.getStats().error, null); assert.equal(publications.at(-1)[1].length, 0); respond([good]); assert.equal(await layer.update(viewer), true); assert.equal(dataSources[0].entities.values.length, 1); } finally { globalThis.fetch = originalFetch; layer.destroy(viewer); } });