mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-11 13:52:19 +00:00
Build test engines from the shared fake instead of literals
Tests that made their own engine object now use createFakeEngine() with its scripting hooks or an override of the one member under test, so they follow the contract when it changes. 125 such literals in 74 test files go down to two that are not globe engines on purpose; report-qa-harness counts them, and the baseline keeps the number from growing.
This commit is contained in:
1 parent
3c304502a0
commit
225c1c25c7
76 files changed
+668
-564
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createAnnotationEngine } from './annotationEngine.js';
|
||||
@@ -69,7 +70,7 @@ test('the marks survive a globe leaving and are drawn again on the next one', as
|
||||
|
||||
log.length = 0;
|
||||
engine.attachRenderer({
|
||||
engine: {},
|
||||
engine: createFakeEngine(),
|
||||
renderer: recordingRenderer(log, 'next'),
|
||||
});
|
||||
assert.deepEqual(log, ['next add Capitol']);
|
||||
@@ -88,8 +89,14 @@ test('detaching twice and attaching over an attached globe release each drawing
|
||||
});
|
||||
engine.detachRenderer();
|
||||
engine.detachRenderer();
|
||||
engine.attachRenderer({ engine: {}, renderer: recordingRenderer(log, 'b') });
|
||||
engine.attachRenderer({ engine: {}, renderer: recordingRenderer(log, 'c') });
|
||||
engine.attachRenderer({
|
||||
engine: createFakeEngine(),
|
||||
renderer: recordingRenderer(log, 'b'),
|
||||
});
|
||||
engine.attachRenderer({
|
||||
engine: createFakeEngine(),
|
||||
renderer: recordingRenderer(log, 'c'),
|
||||
});
|
||||
assert.deepEqual(log, ['a destroy', 'b destroy']);
|
||||
engine.destroy();
|
||||
});
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
// Behavioural tests for the draw tool: the module is driven against fake DOM
|
||||
// and viewer doubles and its OBSERVABLE effects are asserted — which lease it
|
||||
// holds, which viewer input actions it borrowed and gave back, what it leaves
|
||||
@@ -975,7 +976,7 @@ test('a page without the Draw controls gets no draw tool', () => {
|
||||
try {
|
||||
let connected = false;
|
||||
const tool = initDrawTool({
|
||||
engine: {},
|
||||
engine: createFakeEngine(),
|
||||
annotations: fakeAnnotations(),
|
||||
connect: () => {
|
||||
connected = true;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
import { installDomClasses } from '../testSupport/domClasses.mjs';
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
@@ -127,10 +128,9 @@ function fakeViewer(lon, lat) {
|
||||
},
|
||||
};
|
||||
// The Cesium engine's credits over the viewer's credit display.
|
||||
const engine = {
|
||||
credits: createCesiumCredits(viewer).credits,
|
||||
render: { request: () => scene.requestRender() },
|
||||
};
|
||||
const engine = createFakeEngine();
|
||||
engine.credits = createCesiumCredits(viewer).credits;
|
||||
engine.render.request = () => scene.requestRender();
|
||||
enginesOf.set(viewer, engine);
|
||||
return { dataSources, frames, viewer, engine };
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import * as geo from '../geo/index.js';
|
||||
@@ -9,18 +10,11 @@ test('a depth pick on high terrain close to the camera is kept', () => {
|
||||
const camera = geo.cartesian3.fromDegrees(86.9, 27.9, 4030);
|
||||
const ground = geo.cartesian3.fromDegrees(86.9, 27.9, 4000);
|
||||
const seaLevel = geo.cartesian3.fromDegrees(86.9, 27.9, 0);
|
||||
const engine = {
|
||||
host: { size: () => ({ width: 100, height: 100 }) },
|
||||
picker: {
|
||||
pickPositionSupported: true,
|
||||
pickPosition: () => geo.cartesian3.clone(ground),
|
||||
pickGlobe: () => geo.cartesian3.clone(ground),
|
||||
},
|
||||
camera: {
|
||||
livePosition: camera,
|
||||
pickEllipsoid: () => geo.cartesian3.clone(seaLevel),
|
||||
},
|
||||
};
|
||||
const engine = createFakeEngine({ width: 100, height: 100 });
|
||||
engine.fake.setPose({ position: camera });
|
||||
engine.fake.script('pickPosition', () => geo.cartesian3.clone(ground));
|
||||
engine.fake.script('pickGlobe', () => geo.cartesian3.clone(ground));
|
||||
engine.fake.script('pickEllipsoid', () => geo.cartesian3.clone(seaLevel));
|
||||
const picked = pickWorldFromScreen(engine, 0.5, 0.5);
|
||||
assert.ok(
|
||||
Math.abs(picked.height - 4000) < 1,
|
||||
@@ -29,35 +23,35 @@ test('a depth pick on high terrain close to the camera is kept', () => {
|
||||
});
|
||||
|
||||
test('a near-camera depth artifact falls back to terrain before sea level', () => {
|
||||
const engine = {
|
||||
host: { size: () => ({ width: 100, height: 100 }) },
|
||||
picker: {
|
||||
pickPositionSupported: true,
|
||||
pickPosition: () => geo.cartesian3.fromDegrees(86.9, 27.9, 4029),
|
||||
pickGlobe: () => geo.cartesian3.fromDegrees(86.9, 27.9, 4000),
|
||||
},
|
||||
camera: {
|
||||
livePosition: geo.cartesian3.fromDegrees(86.9, 27.9, 4030),
|
||||
pickEllipsoid: () => geo.cartesian3.fromDegrees(86.9, 27.9, 0),
|
||||
},
|
||||
};
|
||||
const engine = createFakeEngine({ width: 100, height: 100 });
|
||||
engine.fake.setPose({
|
||||
position: geo.cartesian3.fromDegrees(86.9, 27.9, 4030),
|
||||
});
|
||||
engine.fake.script('pickPosition', () =>
|
||||
geo.cartesian3.fromDegrees(86.9, 27.9, 4029),
|
||||
);
|
||||
engine.fake.script('pickGlobe', () =>
|
||||
geo.cartesian3.fromDegrees(86.9, 27.9, 4000),
|
||||
);
|
||||
engine.fake.script('pickEllipsoid', () =>
|
||||
geo.cartesian3.fromDegrees(86.9, 27.9, 0),
|
||||
);
|
||||
assert.ok(Math.abs(pickWorldFromScreen(engine, 0.5, 0.5).height - 4000) < 1);
|
||||
});
|
||||
|
||||
test('a far camera still treats a near-plane depth hit as a miss', () => {
|
||||
// 100 km up: a preview drawn at the near plane reads back 50 m away.
|
||||
const camera = geo.cartesian3.fromDegrees(86.9, 27.9, 100_000);
|
||||
const engine = {
|
||||
host: { size: () => ({ width: 100, height: 100 }) },
|
||||
picker: {
|
||||
pickPositionSupported: true,
|
||||
pickPosition: () => geo.cartesian3.fromDegrees(86.9, 27.9, 99_950),
|
||||
pickGlobe: () => geo.cartesian3.fromDegrees(86.9, 27.9, 4000),
|
||||
},
|
||||
camera: {
|
||||
livePosition: camera,
|
||||
pickEllipsoid: () => geo.cartesian3.fromDegrees(86.9, 27.9, 0),
|
||||
},
|
||||
};
|
||||
const engine = createFakeEngine({ width: 100, height: 100 });
|
||||
engine.fake.setPose({ position: camera });
|
||||
engine.fake.script('pickPosition', () =>
|
||||
geo.cartesian3.fromDegrees(86.9, 27.9, 99_950),
|
||||
);
|
||||
engine.fake.script('pickGlobe', () =>
|
||||
geo.cartesian3.fromDegrees(86.9, 27.9, 4000),
|
||||
);
|
||||
engine.fake.script('pickEllipsoid', () =>
|
||||
geo.cartesian3.fromDegrees(86.9, 27.9, 0),
|
||||
);
|
||||
assert.ok(Math.abs(pickWorldFromScreen(engine, 0.5, 0.5).height - 4000) < 1);
|
||||
});
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
@@ -111,7 +112,7 @@ test('startup shares one promise and teardown keeps data alive for controls', as
|
||||
|
||||
test('later components receive the scene engine beside its viewer', async () => {
|
||||
const viewer = { name: 'viewer' };
|
||||
const engine = { kind: 'cesium' };
|
||||
const engine = createFakeEngine({ kind: 'cesium' });
|
||||
const seen = [];
|
||||
const receive =
|
||||
(phase) =>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
// A candidate engine that fails after the shell attached to it (a later
|
||||
// part: data, Cockpit clouds) must leave the shell detached, so the working
|
||||
// engine attaches again.
|
||||
@@ -29,7 +30,7 @@ test('a candidate failing in a later part leaves the shell ready for the working
|
||||
const cesium = {
|
||||
kind: 'cesium',
|
||||
viewer: null,
|
||||
engine: { kind: 'cesium' },
|
||||
engine: createFakeEngine({ kind: 'cesium' }),
|
||||
catalog,
|
||||
};
|
||||
await sceneHost.attach(cesium);
|
||||
@@ -49,7 +50,7 @@ test('a candidate failing in a later part leaves the shell ready for the working
|
||||
const navara = {
|
||||
kind: 'navara',
|
||||
viewer: null,
|
||||
engine: { kind: 'navara' },
|
||||
engine: createFakeEngine({ kind: 'navara' }),
|
||||
catalog,
|
||||
};
|
||||
await assert.rejects(sceneHost.attach(navara), /layer attachment failed/);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
@@ -12,15 +13,13 @@ import { createView } from '../view/index.js';
|
||||
|
||||
const fakeEngine = () => {
|
||||
const flights = [];
|
||||
return {
|
||||
flights,
|
||||
camera: {
|
||||
flyTo(options) {
|
||||
flights.push(options);
|
||||
options.complete();
|
||||
},
|
||||
},
|
||||
const engine = createFakeEngine();
|
||||
engine.flights = flights;
|
||||
engine.camera.flyTo = (options) => {
|
||||
flights.push(options);
|
||||
options.complete();
|
||||
};
|
||||
return engine;
|
||||
};
|
||||
const fakeLayers = (enabled) => ({
|
||||
getAll: () =>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
@@ -23,7 +24,9 @@ test('the scene engine is built on the viewer and destroyed by the scene lifetim
|
||||
defer: (dispose) => cleanups.push(dispose),
|
||||
createEngine(target, options) {
|
||||
calls.push([target, options]);
|
||||
return { kind: 'cesium', destroy: () => calls.push('destroy') };
|
||||
const engine = createFakeEngine();
|
||||
engine.destroy = () => calls.push('destroy');
|
||||
return engine;
|
||||
},
|
||||
});
|
||||
assert.equal(engine.kind, 'cesium');
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
import '../testSupport/registerDom.mjs';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
@@ -73,6 +74,8 @@ function boot(
|
||||
};
|
||||
},
|
||||
createStandaloneApplication: ({ engineKind }) => {
|
||||
const engine = createFakeEngine({ kind: engineKind });
|
||||
engine.camera.completeFlight = () => flushes.push('flight');
|
||||
const styleManager = {
|
||||
initialRestorePromise,
|
||||
shareLinkManager: { flush: () => flushes.push('share link') },
|
||||
@@ -92,9 +95,7 @@ function boot(
|
||||
}),
|
||||
getComponents: () => ({
|
||||
scene: {
|
||||
engine: {
|
||||
camera: { completeFlight: () => flushes.push('flight') },
|
||||
},
|
||||
engine: engine,
|
||||
},
|
||||
controls: { styleManager },
|
||||
}),
|
||||
|
||||
@@ -42,10 +42,8 @@ test('Navara draws the page session board and its switch takes only the drawing'
|
||||
const lifetime = new AbortController();
|
||||
const layerSession = { lifetime };
|
||||
const { views, meshes } = fakeViews();
|
||||
const engine = {
|
||||
...createFakeEngine({ kind: 'navara' }),
|
||||
extensions: { navara: views },
|
||||
};
|
||||
const engine = createFakeEngine({ kind: 'navara' });
|
||||
engine.extensions = { navara: views };
|
||||
// As src/app/tools.js attaches the board on the backend's scene.
|
||||
const attach = () => {
|
||||
const cleanups = [];
|
||||
|
||||
@@ -1,21 +1,12 @@
|
||||
import { createFakeEngine } from './globe/testing/fakeEngine.js';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { CelestialRing } from './celestialRing.js';
|
||||
|
||||
test('full globe focus uses the engine without a Cesium global', () => {
|
||||
let flight;
|
||||
const engine = {
|
||||
host: { canvas: { clientWidth: 1200, clientHeight: 800 } },
|
||||
render: { request() {} },
|
||||
camera: {
|
||||
pose: () => ({
|
||||
cartographic: { longitude: 0, latitude: 0 },
|
||||
heading: 0,
|
||||
fovy: Math.PI / 3,
|
||||
}),
|
||||
flyTo: (options) => { flight = options; },
|
||||
},
|
||||
};
|
||||
const engine = createFakeEngine({ canvas: { clientWidth: 1200, clientHeight: 800 }, fovy: Math.PI / 3 });
|
||||
engine.camera.flyTo = (options) => { flight = options; };
|
||||
const ring = Object.assign(Object.create(CelestialRing.prototype), {
|
||||
_engine: engine,
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import test from 'node:test';
|
||||
@@ -3142,11 +3143,8 @@ test('Bhote Koshi republishes its evidence cards to the incoming engine overlay'
|
||||
globalThis.window = { setTimeout: () => 1, clearTimeout() {} };
|
||||
const first = eventViewer();
|
||||
const second = eventViewer();
|
||||
const engineB = {
|
||||
id: 'B',
|
||||
host: { canvas: second.scene.canvas },
|
||||
camera: second.camera,
|
||||
};
|
||||
const engineB = createFakeEngine({ canvas: second.scene.canvas });
|
||||
engineB.id = 'B';
|
||||
const hosts = new Map();
|
||||
const hostFor = (engine) => {
|
||||
const key = engine ?? 'A';
|
||||
@@ -3216,16 +3214,11 @@ async function navaraEvidenceCard({ sample, signal }) {
|
||||
installEventDom();
|
||||
globalThis.window = { setTimeout: () => 1, clearTimeout() {} };
|
||||
const entries = [];
|
||||
const engine = {
|
||||
terrain: 3500,
|
||||
host: { canvas: eventViewer().scene.canvas },
|
||||
surface: {
|
||||
terrainHeight: () => engine.terrain,
|
||||
sampleTerrainMostDetailed: sample,
|
||||
},
|
||||
maps: { terrainRevision: () => 1 },
|
||||
camera: { livePosition: Cesium.Cartesian3.fromDegrees(0, 0, 1e7) },
|
||||
};
|
||||
const engine = createFakeEngine({ canvas: eventViewer().scene.canvas });
|
||||
engine.terrain = 3500;
|
||||
engine.fake.script('terrainHeight', () => engine.terrain);
|
||||
engine.fake.script('sampleTerrainMostDetailed', sample);
|
||||
engine.maps.terrainRevision = () => 1;
|
||||
const layer = buildEventLayer({
|
||||
eventLoader: async () => event,
|
||||
imageLoader: async (url) => ({ url }),
|
||||
@@ -3330,11 +3323,9 @@ test('Navara evidence anchors without a terrain sample follow the terrain near t
|
||||
assert.ok(Math.abs(height() - pack) < 0.01, 'the pack elevation');
|
||||
card.position();
|
||||
assert.ok(Math.abs(height() - pack) < 0.01, 'a far camera keeps it');
|
||||
engine.camera.livePosition = Cesium.Cartesian3.fromDegrees(
|
||||
first.lon,
|
||||
first.lat,
|
||||
6000,
|
||||
);
|
||||
engine.fake.setPose({
|
||||
position: Cesium.Cartesian3.fromDegrees(first.lon, first.lat, 6000),
|
||||
});
|
||||
engine.terrain = 2000;
|
||||
assert.ok(card.position(), 'the first card is shown');
|
||||
assert.ok(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
// Edge cases for bhote-koshi-locator: a boundary that resolves late, after its
|
||||
// request was cancelled, does not change the live scene; both engines show the
|
||||
// same area and the same target.
|
||||
@@ -42,11 +43,8 @@ function locator(engine) {
|
||||
remove: (source) => shown.delete(source),
|
||||
},
|
||||
};
|
||||
const navara = {
|
||||
kind: 'navara',
|
||||
render: { request: (reason) => renders.push(reason) },
|
||||
credits: { add() {}, remove() {} },
|
||||
};
|
||||
const navara = createFakeEngine({ kind: 'navara' });
|
||||
navara.render.request = (reason) => renders.push(reason);
|
||||
const layer = createBhoteKoshiLocatorLayer({
|
||||
boundaryResolver: (signal) => {
|
||||
const lookup = deferred();
|
||||
@@ -176,11 +174,7 @@ test('edge:bhote-koshi-locator: an engine swap while the boundary resolves lands
|
||||
const enabling = layer.enable(viewer);
|
||||
layer.detachRenderer();
|
||||
layer.attachRenderer({
|
||||
engine: {
|
||||
kind: 'navara',
|
||||
render: { request() {} },
|
||||
credits: { add() {}, remove() {} },
|
||||
},
|
||||
engine: createFakeEngine({ kind: 'navara' }),
|
||||
});
|
||||
lookup.resolve({ ring: LIVE_RING });
|
||||
assert.equal(await enabling, true);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
@@ -94,10 +95,9 @@ test('OpenStreetMap has one generic data credit with separate tile and names dis
|
||||
// A Cesium engine's credits over a viewer double's credit display.
|
||||
async function cesiumCreditsEngine(viewer) {
|
||||
const { createCesiumCredits } = await import('../globe/cesium/credits.js');
|
||||
return {
|
||||
return Object.assign(createFakeEngine(), {
|
||||
credits: createCesiumCredits(viewer).credits,
|
||||
render: { request() {} },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
test('inline OSM attribution persists until its last display owner leaves', async (t) => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { LayerLifecycle } from './lifecycle.js';
|
||||
@@ -46,7 +47,7 @@ function catalog(...modules) {
|
||||
};
|
||||
}
|
||||
|
||||
const engine = (name) => ({ kind: 'test', name });
|
||||
const engine = (name) => Object.assign(createFakeEngine({ kind: 'test' }), { name });
|
||||
const firstViewer = { name: 'first viewer' };
|
||||
const secondViewer = { name: 'second viewer' };
|
||||
|
||||
@@ -86,6 +87,7 @@ const detaches = (calls) => calls.filter((call) => call[0] === 'detach');
|
||||
const attaches = (calls) => calls.filter((call) => call[0] === 'attach');
|
||||
|
||||
test('one failing detachRenderer leaves the others released and the manager unbound', async () => {
|
||||
const second = engine('second');
|
||||
let failures = 1;
|
||||
const broken = sceneLayer('broken', {
|
||||
detach() {
|
||||
@@ -122,16 +124,17 @@ test('one failing detachRenderer leaves the others released and the manager unbo
|
||||
// The next scene binds and both retained layers attach to it.
|
||||
await bindCatalog(
|
||||
manager,
|
||||
engine('second'),
|
||||
second,
|
||||
catalog(...modules),
|
||||
);
|
||||
assert.equal(manager.viewer, secondViewer);
|
||||
assert.deepEqual(attaches(broken.calls).at(-1), ['attach', engine('second')]);
|
||||
assert.deepEqual(attaches(after.calls).at(-1), ['attach', engine('second')]);
|
||||
assert.deepEqual(attaches(broken.calls).at(-1), ['attach', second]);
|
||||
assert.deepEqual(attaches(after.calls).at(-1), ['attach', second]);
|
||||
assert.equal(manager.isEnabled('broken'), true);
|
||||
});
|
||||
|
||||
test('bindCatalog after a failed detach retries the release before attaching', async () => {
|
||||
const second = engine('second');
|
||||
let failures = 1;
|
||||
const broken = sceneLayer('broken', {
|
||||
detach() {
|
||||
@@ -146,13 +149,13 @@ test('bindCatalog after a failed detach retries the release before attaching', a
|
||||
|
||||
await bindCatalog(
|
||||
manager,
|
||||
engine('second'),
|
||||
second,
|
||||
catalog(...modules),
|
||||
);
|
||||
assert.deepEqual(broken.calls.slice(-3), [
|
||||
['detach', firstViewer],
|
||||
['detach', firstViewer],
|
||||
['attach', engine('second')],
|
||||
['attach', second],
|
||||
]);
|
||||
// The scene is settled: the next detach releases the second viewer once.
|
||||
await manager.detachScene();
|
||||
@@ -419,6 +422,7 @@ test('an explicit refresh between scenes ends false when its caller aborts', asy
|
||||
});
|
||||
|
||||
test('a layer enabled between scenes initializes on the next scene', async () => {
|
||||
const second = engine('second');
|
||||
const late = sceneLayer('late');
|
||||
const present = sceneLayer('present');
|
||||
const { manager, modules } = await boundManager(present);
|
||||
@@ -429,9 +433,9 @@ test('a layer enabled between scenes initializes on the next scene', async () =>
|
||||
const enabled = manager.setEnabled('late', true, { origin: 'user' });
|
||||
await tick();
|
||||
assert.deepEqual(late.calls, [], 'no init against a missing scene');
|
||||
await bindCatalog(manager, engine('second'), catalog(...all));
|
||||
await bindCatalog(manager, second, catalog(...all));
|
||||
assert.equal(await enabled, true);
|
||||
assert.deepEqual(late.calls.slice(0, 2), ['init', ['attach', engine('second')]]);
|
||||
assert.deepEqual(late.calls.slice(0, 2), ['init', ['attach', second]]);
|
||||
});
|
||||
|
||||
test('a detach does not deadlock on an enable parked for the next scene', async () => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { readLayerSource } from '../testSupport/readLayerSource.mjs';
|
||||
import { test } from 'node:test';
|
||||
@@ -77,14 +78,14 @@ const {
|
||||
} = militaryAwarenessLayer;
|
||||
const viewerEngines = new WeakMap();
|
||||
const viewerEngine = (viewer) => {
|
||||
if (!viewerEngines.has(viewer)) viewerEngines.set(viewer, {
|
||||
kind: 'cesium', viewer,
|
||||
render: {
|
||||
request: () => viewer.scene.requestRender?.(),
|
||||
setContinuous: (enabled) => { viewer.scene.requestRenderMode = !enabled; },
|
||||
isContinuous: () => viewer.scene.requestRenderMode === false,
|
||||
},
|
||||
});
|
||||
if (!viewerEngines.has(viewer)) {
|
||||
const engine = createFakeEngine();
|
||||
engine.viewer = viewer;
|
||||
engine.render.request = () => viewer.scene.requestRender?.();
|
||||
engine.render.setContinuous = (enabled) => { viewer.scene.requestRenderMode = !enabled; };
|
||||
engine.render.isContinuous = () => viewer.scene.requestRenderMode === false;
|
||||
viewerEngines.set(viewer, engine);
|
||||
}
|
||||
return viewerEngines.get(viewer);
|
||||
};
|
||||
|
||||
|
||||
@@ -12,11 +12,8 @@ import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
/** A layer on the fake engine, whose imagery drapes where `surface()` says. */
|
||||
function setup(overlay = OSM_OVERLAYS[0]) {
|
||||
let surface = 'globe';
|
||||
const fake = createFakeEngine({ kind: 'cesium' });
|
||||
const engine = {
|
||||
...fake,
|
||||
imagery: { ...fake.imagery, surface: () => surface },
|
||||
};
|
||||
const engine = createFakeEngine({ kind: 'cesium' });
|
||||
engine.imagery.surface = () => surface;
|
||||
const layer = createOsmOverlayLayer(overlay);
|
||||
layer.init(null, { engine });
|
||||
return {
|
||||
@@ -120,15 +117,15 @@ test('destroy and a renderer detach remove the owned imagery', () => {
|
||||
|
||||
test('on Navara an overlay drapes through engine.maps and follows the globe', () => {
|
||||
const added = [];
|
||||
const engineWith = () => ({
|
||||
maps: {
|
||||
addOverlay(options) {
|
||||
const entry = { options, removed: false };
|
||||
added.push(entry);
|
||||
return () => (entry.removed = true);
|
||||
},
|
||||
},
|
||||
});
|
||||
const engineWith = () => {
|
||||
const engine = createFakeEngine({ kind: 'navara' });
|
||||
engine.maps.addOverlay = (options) => {
|
||||
const entry = { options, removed: false };
|
||||
added.push(entry);
|
||||
return () => (entry.removed = true);
|
||||
};
|
||||
return engine;
|
||||
};
|
||||
const [seaMarks] = createOsmOverlayLayers();
|
||||
seaMarks.init(null, { engine: engineWith() });
|
||||
assert.equal(added.length, 0, 'nothing until enabled');
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
|
||||
@@ -11,7 +12,7 @@ globalThis.CustomEvent ||= class CustomEvent {
|
||||
globalThis.dispatchEvent ||= () => true;
|
||||
|
||||
const { registerEntityContext } = await import('./contextStore.js');
|
||||
const engine = {};
|
||||
const engine = createFakeEngine();
|
||||
const { pickRegistryFor } = await import('./pickRegistry.js');
|
||||
const { registerPickOwner, unregisterPickOwner, findPickOwner } = pickRegistryFor(engine);
|
||||
const { resolvePickedContext, pickedKindForLayer } = await import('./pickedContext.js');
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
@@ -1638,7 +1639,8 @@ test('Radio recovery preserves closer altitude and caps extreme full-globe zoom'
|
||||
|
||||
test('Radio camera navigation yields to every live tracked-entity owner', () => {
|
||||
let following = null;
|
||||
const engine = { camera: { following: () => following } };
|
||||
const engine = createFakeEngine();
|
||||
engine.camera.following = () => following;
|
||||
assert.equal(radioCameraNavigationAllowed(engine), true);
|
||||
for (const gevTrackedId of ['flights:abc123', 'military:def456']) {
|
||||
const trackedEntity = { gevTrackedId };
|
||||
|
||||
@@ -221,9 +221,11 @@ test('cable paths drape on exactly the active surface on every stack', () => {
|
||||
|
||||
// Boot fires no stack event; the first drape comes from the engine:
|
||||
// photoreal tiles shown ⇔ the globe hidden.
|
||||
const engine = (photoreal) => ({
|
||||
maps: { photorealActive: () => photoreal },
|
||||
});
|
||||
const engine = (photoreal) => {
|
||||
const globe = createFakeEngine();
|
||||
globe.maps.photorealActive = () => photoreal;
|
||||
return globe;
|
||||
};
|
||||
assert.equal(cableDrapeForEngine(engine(true)), 'tiles');
|
||||
assert.equal(cableDrapeForEngine(engine(false)), 'terrain');
|
||||
assert.equal(cableDrapeForEngine(null), 'both');
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
import { readLayerSource } from '../testSupport/readLayerSource.mjs';
|
||||
// src/data/trackedModelRegime.test.mjs
|
||||
//
|
||||
@@ -172,16 +173,12 @@ function viewerAtHeight(height, { tilesLoaded = true, sampleHeight = () => undef
|
||||
trackedEntity: undefined,
|
||||
entities: { remove() {} },
|
||||
};
|
||||
viewer.engine = {
|
||||
isDestroyed: () => false,
|
||||
surface: {
|
||||
tilesLoaded: () => viewer.tileset.tilesLoaded === true,
|
||||
sampleHeightDetailed(place, { exclude } = {}) {
|
||||
const h = viewer.scene.sampleHeight(place, exclude);
|
||||
return Number.isFinite(h) ? { height: h, source: 'tiles' } : undefined;
|
||||
},
|
||||
},
|
||||
};
|
||||
viewer.engine = createFakeEngine();
|
||||
viewer.engine.fake.script('tilesLoaded', () => viewer.tileset.tilesLoaded === true);
|
||||
viewer.engine.fake.script('sampleHeightDetailed', (place, { exclude } = {}) => {
|
||||
const h = viewer.scene.sampleHeight(place, exclude);
|
||||
return Number.isFinite(h) ? { height: h, source: 'tiles' } : undefined;
|
||||
});
|
||||
return viewer;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createTrackedReadout } from './trackedReadout.js';
|
||||
@@ -19,18 +20,8 @@ test('followed aircraft card anchors to the model the person sees (gevVisualPosi
|
||||
position: () => native.gevDisplayPosition(),
|
||||
},
|
||||
};
|
||||
const listeners = [];
|
||||
const engine = {
|
||||
camera: {
|
||||
following: () => handle,
|
||||
followChanged: {
|
||||
addEventListener: (f) => {
|
||||
listeners.push(f);
|
||||
return () => {};
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const engine = createFakeEngine();
|
||||
engine.camera.following = () => handle;
|
||||
let entries = null;
|
||||
const overlay = {
|
||||
setEntries(_id, e) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readLayerSource } from '../testSupport/readLayerSource.mjs';
|
||||
import { mkdtempSync, rmSync } from 'node:fs';
|
||||
@@ -214,68 +215,38 @@ test('traffic timing pairs real ordering to the scheduling change and guards re-
|
||||
const changed = eventChannel();
|
||||
let longitude = -97.74;
|
||||
const cartographic = { latitude: Math.PI / 6, longitude: 0, height: 5000 };
|
||||
const camera = {
|
||||
moveEnd,
|
||||
changed,
|
||||
percentageChanged: 0.5,
|
||||
livePosition: { x: 0, y: 0, z: 0 },
|
||||
pose: () => ({ cartographic: { ...cartographic }, heading: 0, pitch: -Math.PI / 2 }),
|
||||
viewRectangle() {
|
||||
const west = longitude * Math.PI / 180;
|
||||
return {
|
||||
south: 30 * Math.PI / 180,
|
||||
west,
|
||||
north: 30.02 * Math.PI / 180,
|
||||
east: west + 0.02 * Math.PI / 180,
|
||||
};
|
||||
},
|
||||
pickEllipsoid: () => undefined,
|
||||
};
|
||||
// A zero-size canvas has no reticle footprint; the view rectangle bounds the fetch.
|
||||
const engine = createFakeEngine({
|
||||
width: 0,
|
||||
height: 0,
|
||||
canvas: { clientWidth: 0, clientHeight: 0, width: 0, height: 0 },
|
||||
});
|
||||
engine.camera.moveEnd = moveEnd;
|
||||
engine.camera.changed = changed;
|
||||
engine.camera.pose = () => ({ cartographic: { ...cartographic }, heading: 0, pitch: -Math.PI / 2 });
|
||||
engine.fake.script('viewRectangle', () => {
|
||||
const west = longitude * Math.PI / 180;
|
||||
return {
|
||||
south: 30 * Math.PI / 180,
|
||||
west,
|
||||
north: 30.02 * Math.PI / 180,
|
||||
east: west + 0.02 * Math.PI / 180,
|
||||
};
|
||||
});
|
||||
engine.fake.script('pickEllipsoid', undefined);
|
||||
engine.fake.script('worldToWindow', undefined);
|
||||
engine.frames.preUpdate = preUpdate;
|
||||
engine.frames.preRender = preRender;
|
||||
engine.frames.postRender = postRender;
|
||||
engine.projector.viewProjection = (result = new Array(16).fill(0)) => result;
|
||||
engine.projector.horizonVisible = () => true;
|
||||
engine.picker.pickPositionSupported = false;
|
||||
engine.surface.supported = false;
|
||||
const setLongitude = (next) => {
|
||||
longitude = next;
|
||||
cartographic.longitude = next * Math.PI / 180;
|
||||
};
|
||||
setLongitude(longitude);
|
||||
const noSet = () => ({
|
||||
set() {},
|
||||
setCount() {},
|
||||
commit() {},
|
||||
replace() {},
|
||||
show() {},
|
||||
destroy() {},
|
||||
});
|
||||
// An engine on a zero-size canvas: no pick ray (no reticle footprint),
|
||||
// so the fetch is bounded by the view rectangle.
|
||||
const engine = {
|
||||
kind: 'cesium',
|
||||
host: {
|
||||
canvas: { clientWidth: 0, clientHeight: 0, width: 0, height: 0 },
|
||||
size: () => ({ width: 0, height: 0 }),
|
||||
},
|
||||
camera,
|
||||
frames: { preUpdate, preRender, postRender },
|
||||
projector: {
|
||||
worldToWindow: () => undefined,
|
||||
viewProjection: (result = new Array(16).fill(0)) => result,
|
||||
horizonVisible: () => true,
|
||||
},
|
||||
picker: { pickPositionSupported: false },
|
||||
surface: {
|
||||
supported: false,
|
||||
terrainHeight: () => 0,
|
||||
sampleHeight: () => undefined,
|
||||
tilesLoaded: () => true,
|
||||
},
|
||||
maps: {
|
||||
photorealActive: () => false,
|
||||
current: () => ({ baseImagery: null, photoreal: null }),
|
||||
},
|
||||
render: { request() {} },
|
||||
credits: { addStatic: () => () => {} },
|
||||
movers: { createSet: noSet },
|
||||
ground: { createSet: noSet },
|
||||
destroy() {},
|
||||
};
|
||||
|
||||
performance.clearMarks();
|
||||
performance.clearMeasures();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../testing/fakeEngine.js';
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
@@ -42,16 +43,13 @@ function harness() {
|
||||
},
|
||||
};
|
||||
const pickers = new Set();
|
||||
const engine = {
|
||||
render: { request: () => requests++ },
|
||||
camera: { livePosition: { x: 0, y: 0, z: 0 }, pickRay: () => null },
|
||||
picker: {
|
||||
pickPosition: () => undefined,
|
||||
registerMeshPicker(pick) {
|
||||
pickers.add(pick);
|
||||
return () => pickers.delete(pick);
|
||||
},
|
||||
},
|
||||
const engine = createFakeEngine({ kind: 'navara' });
|
||||
engine.render.request = () => requests++;
|
||||
engine.camera.pickRay = () => null;
|
||||
engine.fake.script('pickPosition', undefined);
|
||||
engine.picker.registerMeshPicker = (pick) => {
|
||||
pickers.add(pick);
|
||||
return () => pickers.delete(pick);
|
||||
};
|
||||
const port = createNavaraMeshes(view, {
|
||||
overlay,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../testing/fakeEngine.js';
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
@@ -140,7 +141,7 @@ test('pack meshes draw in the overlay and test the scene depth', () => {
|
||||
camera: {},
|
||||
picker: { registerMeshPicker: () => () => {} },
|
||||
});
|
||||
const engine = { meshes: port.meshes, render: { request() {} } };
|
||||
const engine = Object.assign(createFakeEngine(), { meshes: port.meshes });
|
||||
createPackRenderer(engine).image(
|
||||
{ id: 'p', placement: { bounds: [0, 0, 0.01, 0.01], height: 300 } },
|
||||
{},
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { createFakeEngine } from '../../globe/testing/fakeEngine.js';
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { createAircraftSceneBinding } from './sceneBinding.js';
|
||||
import { LayerBindings } from '../../ui/layerBindings.js';
|
||||
import { createEngineEvent } from '../../globe/contract.js';
|
||||
import { readLayerSource } from '../../testSupport/readLayerSource.mjs';
|
||||
|
||||
function observedShell(t) {
|
||||
@@ -11,13 +11,14 @@ function observedShell(t) {
|
||||
t.after(() => {
|
||||
globalThis.window = previous;
|
||||
});
|
||||
const followChanged = createEngineEvent();
|
||||
const engine = createFakeEngine();
|
||||
const followChanged = engine.camera.followChanged;
|
||||
const shell = {
|
||||
generation: 1,
|
||||
followChanged,
|
||||
};
|
||||
const bindings = new LayerBindings({
|
||||
engine: { camera: { followChanged } },
|
||||
engine: engine,
|
||||
services: {},
|
||||
operations: {
|
||||
_stampNavigation() {
|
||||
|
||||
@@ -111,10 +111,8 @@ function viewerAt(height) {
|
||||
trackedEntity: undefined,
|
||||
entities: { remove() {} },
|
||||
};
|
||||
viewer.engine = {
|
||||
isDestroyed: () => false,
|
||||
surface: { tilesLoaded: () => true, sampleHeightDetailed: () => undefined },
|
||||
};
|
||||
viewer.engine = createFakeEngine();
|
||||
viewer.engine.fake.script('sampleHeightDetailed', undefined);
|
||||
return viewer;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../../globe/testing/fakeEngine.js';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import * as geo from '../../geo/index.js';
|
||||
@@ -50,7 +51,7 @@ function harness(layer, { groundM = null, track = [] } = {}) {
|
||||
_trailPositions: [],
|
||||
_trailBackfillToken: 0,
|
||||
_renderer: {
|
||||
engine: {},
|
||||
engine: createFakeEngine(),
|
||||
follow: { onChanged: () => () => {} },
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../../globe/testing/fakeEngine.js';
|
||||
// The ALPR paint lane must initialize in an insecure context (plain HTTP over
|
||||
// the HOST=0.0.0.0 LAN opt-in), where crypto.randomUUID does not exist.
|
||||
import { test } from 'node:test';
|
||||
@@ -34,7 +35,7 @@ test('ALPR overlay init works without crypto.randomUUID', (t) => {
|
||||
},
|
||||
},
|
||||
},
|
||||
engine: {},
|
||||
engine: createFakeEngine(),
|
||||
markers: new Map(),
|
||||
syncMarker() {},
|
||||
sampleHeight() {},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../../globe/testing/fakeEngine.js';
|
||||
// CCTV icon thinning must use the camera's real vertical FOV. In a 16:9
|
||||
// window with Cesium's default fov 60deg the vertical FOV is about 36deg;
|
||||
// main read it from viewer.camera.frustum.fovy. Two cameras 17 m apart seen
|
||||
@@ -33,7 +34,7 @@ test('icon thinning uses the camera vertical FOV, not a pi/3 fallback', () => {
|
||||
distance: () => 2000,
|
||||
cameraPose: () => pose,
|
||||
fovy: () => pose.fovy,
|
||||
engine: { camera: { pose: () => pose } },
|
||||
engine: createFakeEngine({ fovy: FOVY_16_9 }),
|
||||
};
|
||||
const { refreshHorizonCulling } = createRendering({
|
||||
state,
|
||||
|
||||
@@ -82,7 +82,7 @@ test('edge:earthquakes: an expired record is dropped and a duplicate never forks
|
||||
FIXED_SAMPLE,
|
||||
WITH_DUPLICATE,
|
||||
]);
|
||||
layer.init(null, { engine: {} });
|
||||
layer.init(null, { engine: createFakeEngine() });
|
||||
layer.enable();
|
||||
assert.equal(await layer.update(), true);
|
||||
assert.equal(await layer.update(), false);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../../globe/testing/fakeEngine.js';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createViewport } from './viewport.js';
|
||||
@@ -22,11 +23,14 @@ function harness() {
|
||||
const moveEnd = signal(),
|
||||
preRender = signal(),
|
||||
requests = [];
|
||||
const engine = createFakeEngine();
|
||||
engine.camera.moveEnd = moveEnd;
|
||||
engine.frames.preRender = preRender;
|
||||
const layerState = {
|
||||
_enabled: true,
|
||||
_destroyed: false,
|
||||
_view: { camera: {} },
|
||||
_renderer: { engine: { camera: { moveEnd }, frames: { preRender } } },
|
||||
_renderer: { engine: engine },
|
||||
};
|
||||
const viewport = createViewport({
|
||||
layerState,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../../globe/testing/fakeEngine.js';
|
||||
import {
|
||||
element,
|
||||
kmz,
|
||||
@@ -74,7 +75,7 @@ function harness(t) {
|
||||
let listener = null;
|
||||
const layer = createGhostMapsLayer({
|
||||
storage: null,
|
||||
createRenderer: (engine) => fakeRenderer(engine.name, live),
|
||||
createRenderer: (engine) => fakeRenderer(engine.kind, live),
|
||||
source: {
|
||||
getCatalog: async () => catalog('v1'),
|
||||
getFile: (_id, version) => files.get(version).promise,
|
||||
@@ -84,7 +85,7 @@ function harness(t) {
|
||||
},
|
||||
},
|
||||
});
|
||||
layer.init(null, { engine: { name: 'cesium' } });
|
||||
layer.init(null, { engine: createFakeEngine({ kind: 'cesium' }) });
|
||||
layer.enable();
|
||||
t.after(() => layer.destroy());
|
||||
const file = (version) => {
|
||||
@@ -113,7 +114,7 @@ test('edge:s2-ghostmaps: an edition and engine change mid-parse shows only the s
|
||||
await new Promise(setImmediate);
|
||||
push(catalog('v3'));
|
||||
layer.detachRenderer();
|
||||
await layer.attachRenderer({ engine: { name: 'navara' } });
|
||||
await layer.attachRenderer({ engine: createFakeEngine({ kind: 'navara' }) });
|
||||
v2.resolve(kmz('edition two'));
|
||||
v3.resolve(kmz('edition three'));
|
||||
// Let the queued syncs settle.
|
||||
@@ -135,7 +136,7 @@ test('edge:s2-ghostmaps: a bad archive keeps the working edition on the globe',
|
||||
push(catalog('v2'));
|
||||
// The engine changes while the broken file is still on its way.
|
||||
layer.detachRenderer();
|
||||
await layer.attachRenderer({ engine: { name: 'navara' } });
|
||||
await layer.attachRenderer({ engine: createFakeEngine({ kind: 'navara' }) });
|
||||
broken.resolve(new Uint8Array([1, 2, 3, 4]));
|
||||
for (let i = 0; i < 20; i += 1) await new Promise(setImmediate);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../../globe/testing/fakeEngine.js';
|
||||
import {
|
||||
element,
|
||||
kmz,
|
||||
@@ -58,10 +59,10 @@ for (const action of ['expire', 'disable']) {
|
||||
onClick: () => () => {},
|
||||
draw(id, features) {
|
||||
const gate = deferred();
|
||||
if (engine.name === 'navara') gates.push(gate);
|
||||
if (engine.kind === 'navara') gates.push(gate);
|
||||
else gate.resolve();
|
||||
const paint = {
|
||||
engine: engine.name,
|
||||
engine: engine.kind,
|
||||
names: [...features.values()].map((feature) => feature.name),
|
||||
dropped: [],
|
||||
ready: gate.promise,
|
||||
@@ -84,7 +85,7 @@ for (const action of ['expire', 'disable']) {
|
||||
getFile: async () => kmz('old event'),
|
||||
},
|
||||
});
|
||||
layer.init(null, { engine: { name: 'cesium' } });
|
||||
layer.init(null, { engine: createFakeEngine({ kind: 'cesium' }) });
|
||||
layer.enable();
|
||||
t.after(() => layer.destroy());
|
||||
await layer.update();
|
||||
@@ -92,7 +93,9 @@ for (const action of ['expire', 'disable']) {
|
||||
|
||||
// Engine switch: the new drawing is still decoding its icons.
|
||||
layer.detachRenderer();
|
||||
const attached = layer.attachRenderer({ engine: { name: 'navara' } });
|
||||
const attached = layer.attachRenderer({
|
||||
engine: createFakeEngine({ kind: 'navara' }),
|
||||
});
|
||||
await new Promise(setImmediate);
|
||||
assert.equal(gates.length, 1);
|
||||
// The event leaves its age window; the expiry pass runs meanwhile.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../../globe/testing/fakeEngine.js';
|
||||
import { createGnssDisplay } from './render/display.js';
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
@@ -82,18 +83,15 @@ const threeHealthy = () => [
|
||||
const flush = () => new Promise((resolve) => setImmediate(resolve));
|
||||
|
||||
test('a globe-wide rectangle uses the visible screen centre as the query anchor', () => {
|
||||
const anchor = defaultGnssViewAnchor({
|
||||
host: { size: () => ({ width: 1000, height: 800 }) },
|
||||
camera: {
|
||||
pickEllipsoid(pixel) {
|
||||
assert.deepEqual(pixel, { x: 500, y: 400 });
|
||||
return geo.cartesian3.fromDegrees(-97, 30);
|
||||
},
|
||||
viewRectangle: () => {
|
||||
throw new Error('centre hit must not compute a rectangle');
|
||||
},
|
||||
},
|
||||
const engine = createFakeEngine({ width: 1000, height: 800 });
|
||||
engine.fake.script('pickEllipsoid', (pixel) => {
|
||||
assert.deepEqual(pixel, { x: 500, y: 400 });
|
||||
return geo.cartesian3.fromDegrees(-97, 30);
|
||||
});
|
||||
engine.fake.script('viewRectangle', () => {
|
||||
throw new Error('centre hit must not compute a rectangle');
|
||||
});
|
||||
const anchor = defaultGnssViewAnchor(engine);
|
||||
assert.ok(Math.abs(anchor.latitude - 30) < 1e-8);
|
||||
assert.ok(Math.abs(anchor.longitude + 97) < 1e-8);
|
||||
});
|
||||
@@ -189,7 +187,10 @@ test('movement during a pending refresh follows up at the newest view; disable c
|
||||
test('the layer requires a snapshot source and cannot be initialized twice', () => {
|
||||
assert.throws(() => createGnssIntegrityLayer({}), TypeError);
|
||||
const { layer } = harness(async () => ({ rows: [] }));
|
||||
assert.throws(() => layer.init(null, { engine: {} }), /already/);
|
||||
assert.throws(
|
||||
() => layer.init(null, { engine: createFakeEngine() }),
|
||||
/already/,
|
||||
);
|
||||
});
|
||||
|
||||
test('an enabled refresh bins the snapshot into coloured ground cells', async () => {
|
||||
@@ -694,19 +695,15 @@ test('the cells stay out of picks', async () => {
|
||||
test('view anchor falls back to rectangle, then nadir, then no camera', () => {
|
||||
assert.equal(defaultGnssViewAnchor({}), null);
|
||||
const radians = geo.math.toRadians;
|
||||
const engine = {
|
||||
host: { size: () => ({ width: 1000, height: 800 }) },
|
||||
camera: {
|
||||
pickEllipsoid: () => undefined,
|
||||
viewRectangle: () => ({
|
||||
west: radians(20),
|
||||
south: radians(40),
|
||||
east: radians(22),
|
||||
north: radians(42),
|
||||
}),
|
||||
pose: () => assert.fail('a rectangle must not read the pose'),
|
||||
},
|
||||
};
|
||||
const engine = createFakeEngine({ width: 1000, height: 800 });
|
||||
engine.fake.script('pickEllipsoid', undefined);
|
||||
engine.fake.script('viewRectangle', {
|
||||
west: radians(20),
|
||||
south: radians(40),
|
||||
east: radians(22),
|
||||
north: radians(42),
|
||||
});
|
||||
engine.camera.pose = () => assert.fail('a rectangle must not read the pose');
|
||||
const rectangleAnchor = defaultGnssViewAnchor(engine);
|
||||
assert.ok(Math.abs(rectangleAnchor.latitude - 41) < 1e-10);
|
||||
assert.ok(Math.abs(rectangleAnchor.longitude - 21) < 1e-10);
|
||||
|
||||
@@ -33,7 +33,7 @@ test('a bundled file delivered between planets keeps multipart IDs and is read o
|
||||
lod() {},
|
||||
}),
|
||||
);
|
||||
layer.init(null, { engine: {} });
|
||||
layer.init(null, { engine: createFakeEngine() });
|
||||
const enabled = layer.enable();
|
||||
await new Promise(setImmediate);
|
||||
layer.detachRenderer();
|
||||
@@ -57,9 +57,9 @@ test('a bundled file delivered between planets keeps multipart IDs and is read o
|
||||
await enabled;
|
||||
assert.equal(layer.getAnalystRecords().length, 2);
|
||||
assert.equal(captures.length, 0);
|
||||
await layer.attachRenderer({ engine: {} });
|
||||
await layer.attachRenderer({ engine: createFakeEngine() });
|
||||
layer.detachRenderer();
|
||||
await layer.attachRenderer({ engine: {} });
|
||||
await layer.attachRenderer({ engine: createFakeEngine() });
|
||||
assert.equal(captures[0], captures[1]);
|
||||
assert.deepEqual(
|
||||
captures[0].records.map((r) => r.id),
|
||||
@@ -89,13 +89,13 @@ test('a released scene stops waiting for a stalled dataset load, which stays in
|
||||
lod() {},
|
||||
}),
|
||||
);
|
||||
layer.init(null, { engine: {} });
|
||||
layer.init(null, { engine: createFakeEngine() });
|
||||
const enabled = layer.enable();
|
||||
await new Promise(setImmediate);
|
||||
layer.detachRenderer();
|
||||
const scene = new AbortController();
|
||||
const attaching = layer.attachRenderer({
|
||||
engine: {},
|
||||
engine: createFakeEngine(),
|
||||
signal: scene.signal,
|
||||
});
|
||||
scene.abort();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../../globe/testing/fakeEngine.js';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import * as geo from '../../geo/index.js';
|
||||
@@ -26,14 +27,12 @@ test('orbital ground focus reads the drawn height and the engine pick id', () =>
|
||||
test('focus frames a selected installation through the globe engine camera', () => {
|
||||
const record = { id: 'osm:node:12', latitude: 70, longitude: 179.8 };
|
||||
let flight = null;
|
||||
const engine = createFakeEngine();
|
||||
engine.camera.flyToSphere = (sphere, options) => {
|
||||
flight = { sphere, options };
|
||||
};
|
||||
const state = {
|
||||
engine: {
|
||||
camera: {
|
||||
flyToSphere: (sphere, options) => {
|
||||
flight = { sphere, options };
|
||||
},
|
||||
},
|
||||
},
|
||||
engine: engine,
|
||||
recordById: new Map([[record.id, record]]),
|
||||
};
|
||||
const controls = createControls({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../../globe/testing/fakeEngine.js';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import * as Cesium from 'cesium';
|
||||
@@ -162,10 +163,12 @@ test('an aborting real ingestion cannot clear the next scheduled view key', asyn
|
||||
test('Navara installation ingestion loads from its engine camera without a viewer', async () => {
|
||||
const { createIngestion } = await import('./ingestion.js');
|
||||
const rectangle = Cesium.Rectangle.fromDegrees(-123, 37, -122, 38);
|
||||
const engine = createFakeEngine({ kind: 'navara' });
|
||||
engine.fake.script('viewRectangle', () => rectangle);
|
||||
const state = {
|
||||
enabled: true,
|
||||
viewer: null,
|
||||
engine: { camera: { viewRectangle: () => rectangle } },
|
||||
engine: engine,
|
||||
records: [],
|
||||
recordById: new Map(),
|
||||
coverage: { kind: 'viewport' },
|
||||
@@ -242,18 +245,16 @@ test('the engine camera drives installation viewport requests without a viewer',
|
||||
east: Cesium.Math.toRadians(-178),
|
||||
north: Cesium.Math.toRadians(16),
|
||||
};
|
||||
const engine = createFakeEngine();
|
||||
engine.fake.script('viewRectangle', () => rectangle);
|
||||
engine.camera.pose = () => ({
|
||||
position: { x: 1, y: 2, z: 3 },
|
||||
direction: { x: -1, y: 0, z: 0 },
|
||||
});
|
||||
const state = {
|
||||
enabled: true,
|
||||
viewer: null,
|
||||
engine: {
|
||||
camera: {
|
||||
viewRectangle: () => rectangle,
|
||||
pose: () => ({
|
||||
position: { x: 1, y: 2, z: 3 },
|
||||
direction: { x: -1, y: 0, z: 0 },
|
||||
}),
|
||||
},
|
||||
},
|
||||
engine: engine,
|
||||
};
|
||||
const loads = [];
|
||||
const parts = {
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import { createFakeEngine } from '../../globe/testing/fakeEngine.js';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createEngineEvent } from '../../globe/contract.js';
|
||||
import { createLaunchHover } from './render/display.js';
|
||||
|
||||
// What the hover reads of an engine: its canvas, drillPick and camera motion.
|
||||
function hoverEngine(drillPick, style = { cursor: '' }) {
|
||||
return {
|
||||
host: { canvas: { style } },
|
||||
picker: { drillPick },
|
||||
camera: { moveStart: createEngineEvent(), moveEnd: createEngineEvent() },
|
||||
};
|
||||
const engine = createFakeEngine({ canvas: { style } });
|
||||
engine.fake.script('drillPick', drillPick);
|
||||
return engine;
|
||||
}
|
||||
|
||||
function fakeTimers() {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../../globe/testing/fakeEngine.js';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import * as geo from '../../geo/index.js';
|
||||
@@ -10,12 +11,11 @@ test('selected pad frame uses neutral geometry and the physical orbit epoch', ()
|
||||
epoch;
|
||||
const state = {
|
||||
orbitNow: worldTimeFor('satellites'),
|
||||
_engine: {
|
||||
camera: {
|
||||
livePosition: geo.cartesian3.fromDegrees(launch.lon, launch.lat, 500),
|
||||
height: () => 500,
|
||||
},
|
||||
},
|
||||
_engine: createFakeEngine({
|
||||
longitude: geo.math.toRadians(launch.lon),
|
||||
latitude: geo.math.toRadians(launch.lat),
|
||||
altitude: 500,
|
||||
}),
|
||||
_enabled: true,
|
||||
_selectedLaunchId: 'qa',
|
||||
_launches: [launch],
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../../globe/testing/fakeEngine.js';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createLifecycle } from './lifecycle.js';
|
||||
@@ -8,7 +9,7 @@ test('missions preserve the satellite target before their focus flight releases
|
||||
let target = 25544,
|
||||
snapshot = null;
|
||||
const lifecycle = createLifecycle({
|
||||
state: { _engine: {} },
|
||||
state: { _engine: createFakeEngine() },
|
||||
services: { render: {} },
|
||||
getRenderer: () => ({ setVisible() {} }),
|
||||
parts: {
|
||||
@@ -46,7 +47,7 @@ test('missions begin their entry flight while the satellite catalog loads', asyn
|
||||
});
|
||||
let flew = false;
|
||||
const lifecycle = createLifecycle({
|
||||
state: { _engine: {} },
|
||||
state: { _engine: createFakeEngine() },
|
||||
services: { render: {} },
|
||||
getRenderer: () => ({ setVisible() {} }),
|
||||
parts: {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../../globe/testing/fakeEngine.js';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createState } from './state.js';
|
||||
@@ -48,9 +49,7 @@ test('changing replay speed during the countdown keeps its real-time liftoff', (
|
||||
|
||||
test('resuming a replay restores its retained chase heading', () => {
|
||||
const { state } = replayRig();
|
||||
state._engine = {
|
||||
frames: { preUpdate: { addEventListener: () => () => {} } },
|
||||
};
|
||||
state._engine = createFakeEngine();
|
||||
state._launches = [{ id: 'mission' }];
|
||||
state._replayCameraLaunchId = 'mission';
|
||||
state._replayCameraHeading = 1.2;
|
||||
|
||||
@@ -26,7 +26,7 @@ test('ground snapping excludes the aircraft by its engine pick id', async () =>
|
||||
},
|
||||
frustumTester: () => () => true,
|
||||
horizon: () => ({ isPointVisible: () => true }),
|
||||
engine: {},
|
||||
engine: createFakeEngine(),
|
||||
},
|
||||
};
|
||||
const exclusions = [];
|
||||
|
||||
@@ -728,14 +728,12 @@ function failingMapController() {
|
||||
const controller = new MapSourceController({
|
||||
registry,
|
||||
initialStack: 'photoreal',
|
||||
engine: {
|
||||
kind: 'cesium',
|
||||
render: { request() {} },
|
||||
engine: Object.assign(createFakeEngine(), {
|
||||
maps: createCesiumMaps(viewer, {
|
||||
credits: createCesiumCredits(viewer).credits,
|
||||
createImageryLayer: (provider) => ({ provider }),
|
||||
}).maps,
|
||||
},
|
||||
}),
|
||||
onError: () => {},
|
||||
});
|
||||
const calls = [];
|
||||
|
||||
@@ -93,7 +93,7 @@ test('restored orbit rings receive their current rotation before the first frame
|
||||
},
|
||||
});
|
||||
lifecycle.methods.attachRenderer({
|
||||
engine: {},
|
||||
engine: createFakeEngine(),
|
||||
viewRestored: new Promise(() => {}),
|
||||
});
|
||||
assert.equal(rotated, 1);
|
||||
|
||||
@@ -13,21 +13,16 @@ import { createFakeEngine } from '../../globe/testing/fakeEngine.js';
|
||||
// the near plane, as the engines answer).
|
||||
function cameraEngine(scene) {
|
||||
const eye = new C.Cartesian3();
|
||||
return {
|
||||
host: {
|
||||
size: () => ({
|
||||
width: scene.canvas.clientWidth,
|
||||
height: scene.canvas.clientHeight,
|
||||
}),
|
||||
},
|
||||
projector: {
|
||||
worldToWindow(point, result) {
|
||||
C.Matrix4.multiplyByPoint(scene.camera.viewMatrix, point, eye);
|
||||
if (eye.z >= -scene.camera.frustum.near) return undefined;
|
||||
return C.SceneTransforms.worldToWindowCoordinates(scene, point, result);
|
||||
},
|
||||
},
|
||||
};
|
||||
const engine = createFakeEngine({
|
||||
width: scene.canvas.clientWidth,
|
||||
height: scene.canvas.clientHeight,
|
||||
});
|
||||
engine.fake.script('worldToWindow', (point, result) => {
|
||||
C.Matrix4.multiplyByPoint(scene.camera.viewMatrix, point, eye);
|
||||
if (eye.z >= -scene.camera.frustum.near) return undefined;
|
||||
return C.SceneTransforms.worldToWindowCoordinates(scene, point, result);
|
||||
});
|
||||
return engine;
|
||||
}
|
||||
|
||||
const bounds = { west: 0, east: 0.01, south: 0, north: 0.01 };
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../../globe/testing/fakeEngine.js';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import * as geo from '../../geo/index.js';
|
||||
@@ -35,32 +36,35 @@ test('oblique horizon bearings are bounded and tile caps hold at city latitudes'
|
||||
]) {
|
||||
// An engine-shaped stub: the camera's pose and its ellipsoid and ray
|
||||
// picks (the sky above the 300 px line), and the canvas.
|
||||
const engine = {
|
||||
camera: {
|
||||
pose: () => ({
|
||||
cartographic: geo.cartographic.fromDegrees(lon, lat, 300),
|
||||
heading: 0,
|
||||
pitch: -Math.PI / 12,
|
||||
}),
|
||||
pickEllipsoid(p) {
|
||||
if (p.y < 300) return undefined;
|
||||
return geo.cartesian3.fromDegrees(
|
||||
lon + (p.x - 640) / 100000,
|
||||
lat + (800 - p.y) / 50000,
|
||||
0,
|
||||
);
|
||||
},
|
||||
pickRay: () => ({
|
||||
origin: geo.cartesian3.create(),
|
||||
direction: geo.cartesian3.create(
|
||||
-Math.sin((lat * Math.PI) / 180) * Math.cos((lon * Math.PI) / 180),
|
||||
-Math.sin((lat * Math.PI) / 180) * Math.sin((lon * Math.PI) / 180),
|
||||
Math.cos((lat * Math.PI) / 180),
|
||||
),
|
||||
}),
|
||||
},
|
||||
host: { canvas: { width: 1280, height: 800 } },
|
||||
};
|
||||
const engine = createFakeEngine({
|
||||
longitude: geo.math.toRadians(lon),
|
||||
latitude: geo.math.toRadians(lat),
|
||||
altitude: 300,
|
||||
canvas: { width: 1280, height: 800 },
|
||||
});
|
||||
engine.fake.setPose({
|
||||
longitude: geo.math.toRadians(lon),
|
||||
latitude: geo.math.toRadians(lat),
|
||||
height: 300,
|
||||
heading: 0,
|
||||
pitch: -Math.PI / 12,
|
||||
});
|
||||
engine.fake.script('pickEllipsoid', (p) => {
|
||||
if (p.y < 300) return undefined;
|
||||
return geo.cartesian3.fromDegrees(
|
||||
lon + (p.x - 640) / 100000,
|
||||
lat + (800 - p.y) / 50000,
|
||||
0,
|
||||
);
|
||||
});
|
||||
engine.camera.pickRay = () => ({
|
||||
origin: geo.cartesian3.create(),
|
||||
direction: geo.cartesian3.create(
|
||||
-Math.sin((lat * Math.PI) / 180) * Math.cos((lon * Math.PI) / 180),
|
||||
-Math.sin((lat * Math.PI) / 180) * Math.sin((lon * Math.PI) / 180),
|
||||
Math.cos((lat * Math.PI) / 180),
|
||||
),
|
||||
});
|
||||
const result = createReticleFootprint()(engine, 300);
|
||||
assert.ok(result.coverage.coarse.length <= 16);
|
||||
assert.ok(result.coverage.detail.length <= 16);
|
||||
|
||||
@@ -3695,12 +3695,12 @@ test('actual Traffic, Bikeshare and Transit lifecycles retain sensitivity in eve
|
||||
// Traffic claims through its engine's camera, as bikeshare does: the
|
||||
// viewer's. The stub has what binding its scene subscribes to.
|
||||
const listeners = { addEventListener: () => noop };
|
||||
const trafficEngine = {
|
||||
camera: app.viewer.camera,
|
||||
frames: { preRender: listeners, postRender: listeners },
|
||||
projector: { viewProjection: noop },
|
||||
surface: { tilesLoaded: () => false },
|
||||
};
|
||||
const trafficEngine = createFakeEngine();
|
||||
trafficEngine.camera = app.engine.camera;
|
||||
trafficEngine.frames.preRender = listeners;
|
||||
trafficEngine.frames.postRender = listeners;
|
||||
trafficEngine.projector.viewProjection = noop;
|
||||
trafficEngine.fake.script('tilesLoaded', false);
|
||||
const traffic = trafficLifecycle({
|
||||
state: { _engine: trafficEngine, _loadGeneration: 0 },
|
||||
services: {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../../globe/testing/fakeEngine.js';
|
||||
// Edge cases for wind: on a fixed
|
||||
// grid direction, speed and legend agree whichever way the centre is picked,
|
||||
// and an empty field is never presented as calm.
|
||||
@@ -24,15 +25,13 @@ const options = {
|
||||
status: 'Model forecast',
|
||||
};
|
||||
/** Inspect either the current map centre or a captured position. */
|
||||
const viaEngine = (snapshot, point) =>
|
||||
inspectWindAtCenter(
|
||||
snapshot,
|
||||
{
|
||||
camera: { pickEllipsoid: () => point },
|
||||
host: { canvas: { clientWidth: 800, clientHeight: 600 } },
|
||||
},
|
||||
options,
|
||||
);
|
||||
const viaEngine = (snapshot, point) => {
|
||||
const engine = createFakeEngine({
|
||||
canvas: { clientWidth: 800, clientHeight: 600 },
|
||||
});
|
||||
engine.fake.script('pickEllipsoid', point);
|
||||
return inspectWindAtCenter(snapshot, engine, options);
|
||||
};
|
||||
const viaPosition = (snapshot, point) =>
|
||||
inspectWindAtCenter(snapshot, null, { ...options, position: point });
|
||||
const windLine = (reading) =>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../../globe/testing/fakeEngine.js';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import * as geo from '../../geo/index.js';
|
||||
@@ -27,10 +28,10 @@ test('center inspection uses a fixed forecast reading without double-converting
|
||||
values: Float32Array.from([20, 20, 20, 20]),
|
||||
},
|
||||
};
|
||||
const engine = {
|
||||
camera: { pickEllipsoid: () => geo.cartesian3.fromDegrees(0, 0) },
|
||||
host: { canvas: { clientWidth: 800, clientHeight: 600 } },
|
||||
};
|
||||
const engine = createFakeEngine({
|
||||
canvas: { clientWidth: 800, clientHeight: 600 },
|
||||
});
|
||||
engine.fake.script('pickEllipsoid', () => geo.cartesian3.fromDegrees(0, 0));
|
||||
const result = inspectWindAtCenter(snapshot, engine, {
|
||||
overlay: 'temperature',
|
||||
units: 'km/h',
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../../globe/testing/fakeEngine.js';
|
||||
// src/layers/wind/inspectionMarker.test.mjs — the wind inspection marker
|
||||
// follows only its captured point, renders in its container, and releases
|
||||
// its frame listener.
|
||||
@@ -47,15 +48,13 @@ function fixture(t) {
|
||||
requestRender() {},
|
||||
},
|
||||
};
|
||||
const engine = {
|
||||
host: { canvas: viewer.scene.canvas },
|
||||
frames: { postRender: viewer.scene.postRender },
|
||||
projector: {
|
||||
worldToWindow: viewer.scene.cartesianToCanvasCoordinates,
|
||||
horizonVisible: () => visibility.visible,
|
||||
},
|
||||
render: { request() {} },
|
||||
};
|
||||
const engine = createFakeEngine({ canvas: viewer.scene.canvas });
|
||||
engine.frames.postRender = viewer.scene.postRender;
|
||||
engine.fake.script(
|
||||
'worldToWindow',
|
||||
viewer.scene.cartesianToCanvasCoordinates,
|
||||
);
|
||||
engine.projector.horizonVisible = () => visibility.visible;
|
||||
const store = useWindInspectionStore(pinia);
|
||||
visibility.visible = true;
|
||||
const owner = createWindInspectionMarker({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
import { createCesiumMaps } from '../globe/cesium/maps.js';
|
||||
import { createCesiumCredits } from '../globe/cesium/credits.js';
|
||||
import test from 'node:test';
|
||||
@@ -72,7 +73,7 @@ function fixture(registry, options = {}) {
|
||||
});
|
||||
const controller = new MapSourceController({
|
||||
registry,
|
||||
engine: { kind: 'cesium', render: { request() {} }, maps: mapsOwner.maps },
|
||||
engine: Object.assign(createFakeEngine(), { maps: mapsOwner.maps }),
|
||||
onChange: (state) => changes.push(state),
|
||||
...options,
|
||||
});
|
||||
|
||||
@@ -2070,7 +2070,7 @@ test('a caught run failure resolves as run-failed, not success (#971)', async ()
|
||||
test('a failed scene-pack rebind releases the partial renderer and a stopped shot unblocks', async () => {
|
||||
const director = Object.create(SceneDirector.prototype);
|
||||
const drawn = new Set();
|
||||
director.engine = { host: { container: {} } };
|
||||
director.engine = createFakeEngine();
|
||||
director._dataPacks = {
|
||||
setScene(viewer) {
|
||||
if (!viewer) drawn.clear();
|
||||
@@ -2088,7 +2088,7 @@ test('a failed scene-pack rebind releases the partial renderer and a stopped sho
|
||||
assert.throws(
|
||||
() =>
|
||||
director.attachScene(
|
||||
{ engine: { host: { container: {} } } },
|
||||
{ engine: createFakeEngine() },
|
||||
{},
|
||||
() => true,
|
||||
),
|
||||
@@ -2100,7 +2100,7 @@ test('a failed scene-pack rebind releases the partial renderer and a stopped sho
|
||||
abort.abort();
|
||||
assert.equal(await stopped, false);
|
||||
const waiting = director._waitForScene();
|
||||
director.engine = { host: {} };
|
||||
director.engine = createFakeEngine();
|
||||
director.styleManager = {};
|
||||
director.resumeScene();
|
||||
assert.equal(await waiting, true);
|
||||
|
||||
+33
-17
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from './globe/testing/fakeEngine.js';
|
||||
import { test, beforeEach } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
@@ -242,7 +243,9 @@ test('scope appearance follows direct state changes and install/destroy lifecycl
|
||||
setScopeMaskEnabled(false);
|
||||
assert.equal(dom.documentElement.dataset.scopeMask, 'off');
|
||||
delete dom.documentElement.dataset.scopeMask;
|
||||
installScopeMask({ host: { container: dom.container } });
|
||||
installScopeMask(
|
||||
createFakeEngine({ container: dom.container, altitude: 14_000_000 }),
|
||||
);
|
||||
assert.equal(
|
||||
dom.documentElement.dataset.scopeMask,
|
||||
'off',
|
||||
@@ -254,7 +257,9 @@ test('scope appearance follows direct state changes and install/destroy lifecycl
|
||||
undefined,
|
||||
'destroy clears the document marker',
|
||||
);
|
||||
installScopeMask({ host: { container: dom.container } });
|
||||
installScopeMask(
|
||||
createFakeEngine({ container: dom.container, altitude: 14_000_000 }),
|
||||
);
|
||||
assert.equal(
|
||||
dom.documentElement.dataset.scopeMask,
|
||||
'off',
|
||||
@@ -282,7 +287,9 @@ test('scope appearance follows direct state changes and install/destroy lifecycl
|
||||
test('a DPR change with no resize still repaints the backing store', () => {
|
||||
const dom = stubScopeMaskDom({ width: 1000, height: 800, dpr: 1 });
|
||||
try {
|
||||
installScopeMask({ host: { container: dom.container } });
|
||||
installScopeMask(
|
||||
createFakeEngine({ container: dom.container, altitude: 14_000_000 }),
|
||||
);
|
||||
// 1x monitor: backing store matches CSS pixels.
|
||||
assert.equal(dom.canvas.width, 1000);
|
||||
assert.equal(dom.canvas.height, 800);
|
||||
@@ -314,7 +321,9 @@ test('a DPR change with no resize still repaints the backing store', () => {
|
||||
test('destroy tears the DPR watch down (no redraw after teardown)', () => {
|
||||
const dom = stubScopeMaskDom({ width: 640, height: 480, dpr: 1 });
|
||||
try {
|
||||
installScopeMask({ host: { container: dom.container } });
|
||||
installScopeMask(
|
||||
createFakeEngine({ container: dom.container, altitude: 14_000_000 }),
|
||||
);
|
||||
assert.equal(dom.canvas.width, 640);
|
||||
destroyScopeMask();
|
||||
dom.setDpr(2);
|
||||
@@ -439,7 +448,9 @@ test('quantization snaps to the repaint grid', () => {
|
||||
test('repaint gate: only a QUANTIZED step repaints; hovering costs nothing', () => {
|
||||
const dom = stubScopeMaskDom({ width: 1000, height: 800, dpr: 1 });
|
||||
try {
|
||||
installScopeMask({ host: { container: dom.container } });
|
||||
installScopeMask(
|
||||
createFakeEngine({ container: dom.container, altitude: 14_000_000 }),
|
||||
);
|
||||
const base = getScopeTerminusRepaintCount();
|
||||
|
||||
// Parked at true full-globe altitude: the seeded alpha already matches, so
|
||||
@@ -477,7 +488,9 @@ test('repaint gate: only a QUANTIZED step repaints; hovering costs nothing', ()
|
||||
test('a full zoom-in gesture costs only a handful of repaints', () => {
|
||||
const dom = stubScopeMaskDom({ width: 1000, height: 800, dpr: 1 });
|
||||
try {
|
||||
installScopeMask({ host: { container: dom.container } });
|
||||
installScopeMask(
|
||||
createFakeEngine({ container: dom.container, altitude: 14_000_000 }),
|
||||
);
|
||||
const base = getScopeTerminusRepaintCount();
|
||||
|
||||
// 20 Mm → ground in 10 km steps: ~2000 samples across the whole descent,
|
||||
@@ -510,7 +523,9 @@ test('a full zoom-in gesture costs only a handful of repaints', () => {
|
||||
test('an override pins the terminus and null restores the ramp', () => {
|
||||
const dom = stubScopeMaskDom({ width: 1000, height: 800, dpr: 1 });
|
||||
try {
|
||||
installScopeMask({ host: { container: dom.container } });
|
||||
installScopeMask(
|
||||
createFakeEngine({ container: dom.container, altitude: 14_000_000 }),
|
||||
);
|
||||
setScopeTerminusOverride(0.97);
|
||||
assert.equal(getScopeTerminusOverride(), 0.97);
|
||||
assert.equal(getScopeTerminusAlpha(), 0.97);
|
||||
@@ -536,7 +551,9 @@ test('an override pins the terminus and null restores the ramp', () => {
|
||||
test('the hard-crop (feather 0) path honors the same terminus alpha', () => {
|
||||
const dom = stubScopeMaskDom({ width: 1000, height: 800, dpr: 1 });
|
||||
try {
|
||||
installScopeMask({ host: { container: dom.container } });
|
||||
installScopeMask(
|
||||
createFakeEngine({ container: dom.container, altitude: 14_000_000 }),
|
||||
);
|
||||
updateScopeTerminusForHeight(SCOPE_TERMINUS_FAR_M + 5_000_000); // true full-globe view
|
||||
setScopeMaskFeather(0); // hard crop — the evenodd rect-minus-circle path
|
||||
// The globe-scale seed paint is legitimately in the history, so assert on
|
||||
@@ -583,15 +600,12 @@ function stubScopeEngine(container, heightM) {
|
||||
set.add(listener);
|
||||
return () => set.delete(listener);
|
||||
};
|
||||
const engine = createFakeEngine({ container, altitude: height });
|
||||
engine.frames.preRender.addEventListener = addTo(preRenderListeners);
|
||||
engine.camera.moveEnd.addEventListener = addTo(moveEndListeners);
|
||||
engine.camera.height = () => height;
|
||||
return {
|
||||
engine: {
|
||||
host: { container },
|
||||
frames: { preRender: { addEventListener: addTo(preRenderListeners) } },
|
||||
camera: {
|
||||
height: () => height,
|
||||
moveEnd: { addEventListener: addTo(moveEndListeners) },
|
||||
},
|
||||
},
|
||||
engine,
|
||||
setHeight(next) {
|
||||
height = next;
|
||||
},
|
||||
@@ -748,7 +762,9 @@ test('terminus percents clamp into the supported band; junk is absent, not zero'
|
||||
test('a pinned override is floored to the band at every entry point', () => {
|
||||
const dom = stubScopeMaskDom({ width: 1000, height: 800, dpr: 1 });
|
||||
try {
|
||||
installScopeMask({ host: { container: dom.container } });
|
||||
installScopeMask(
|
||||
createFakeEngine({ container: dom.container, altitude: 14_000_000 }),
|
||||
);
|
||||
setScopeTerminusOverride(0); // an sce=0 link, or any stale caller
|
||||
assert.equal(
|
||||
getScopeTerminusOverride(),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
// The standalone application is created once; an engine switch detaches and
|
||||
// attaches only its engine-bound parts. A candidate that fails releases its
|
||||
// renderer, and the working engine attaches again on the same parts.
|
||||
@@ -48,7 +49,8 @@ function standalone({ failOpen = null, beforeOpen, beforeRelease, lifecycle = fa
|
||||
createStandaloneScene: async ({ engineKind, defer }) => {
|
||||
await beforeOpen?.(engineKind);
|
||||
if (failOpen === engineKind) throw new Error(`${engineKind} refused`);
|
||||
const engine = { kind: engineKind, id: opened.length };
|
||||
const engine = createFakeEngine({ kind: engineKind });
|
||||
engine.id = opened.length;
|
||||
opened.push(engine);
|
||||
defer(async () => {
|
||||
await beforeRelease?.(engine);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
import { createSessionHandoff } from '../app/sessionHandoff.js';
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
@@ -479,10 +480,7 @@ test('pagehide while the outgoing engine is released starts nothing', async () =
|
||||
|
||||
test('a slow first frame never rolls back a started engine', async () => {
|
||||
const timers = [];
|
||||
const engine = {
|
||||
frames: { postRender: { addEventListener: () => () => {} } },
|
||||
render: { request() {} },
|
||||
};
|
||||
const engine = createFakeEngine();
|
||||
const h = harness();
|
||||
let frame;
|
||||
const machine = createEngineSwitch({
|
||||
@@ -525,21 +523,12 @@ test('the first-frame wait resolves on a frame, on a hidden tab and on timeout',
|
||||
};
|
||||
let draw;
|
||||
let requests = 0;
|
||||
const engine = {
|
||||
frames: {
|
||||
postRender: {
|
||||
addEventListener(fn) {
|
||||
draw = fn;
|
||||
return () => {
|
||||
draw = null;
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
render: {
|
||||
request: () => requests++,
|
||||
},
|
||||
const engine = createFakeEngine();
|
||||
engine.frames.postRender.addEventListener = (fn) => {
|
||||
draw = fn;
|
||||
return () => { draw = null; };
|
||||
};
|
||||
engine.render.request = () => requests++;
|
||||
const timers = new Map();
|
||||
const options = {
|
||||
document: doc,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { createRendererCache } from './rendererCache.js';
|
||||
@@ -51,10 +52,7 @@ function fakeBuild(released) {
|
||||
resume() {
|
||||
this.running = true;
|
||||
},
|
||||
engine: {
|
||||
host: { container, canvas },
|
||||
render: { request() {} },
|
||||
},
|
||||
engine: createFakeEngine({ container, canvas }),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createSceneSwitcher } from './sceneSwitcher.js';
|
||||
@@ -193,7 +194,7 @@ test('the restored camera precedes retained follow and replay', async () => {
|
||||
const app = {
|
||||
getState: () => ({ status: 'ready' }),
|
||||
getComponents: () => ({
|
||||
scene: { engine: { kind: current } },
|
||||
scene: { engine: createFakeEngine({ kind: current }) },
|
||||
controls: { styleManager: style },
|
||||
}),
|
||||
async detachScene() {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
// src/ui/displayPanel.test.mjs — the Display panel renders bloom and sharpen
|
||||
// from what VisualSettings publishes, and shows its initial markup before.
|
||||
import '../testSupport/registerDom.mjs';
|
||||
@@ -145,6 +146,7 @@ test('a publish never rewrites a slider the user is dragging', async (t) => {
|
||||
/** VisualSettings with a stand-in detection engine. */
|
||||
function detectionSettings(store) {
|
||||
const { owner } = settings(store);
|
||||
// This is detection state, not a globe engine.
|
||||
const engine = {
|
||||
mode: 'OFF',
|
||||
tuning: { densityPct: 50, allocationStrategy: 'ELASTIC' },
|
||||
@@ -425,12 +427,7 @@ test('the imported files render what the file import publishes and keep focus',
|
||||
assert.equal($('geo-import-hint').textContent, '');
|
||||
|
||||
// A globe engine double the files draw on.
|
||||
const engine = {
|
||||
host: { container: document.createElement('div'), canvas: null },
|
||||
camera: { moveEnd: { addEventListener: () => () => {} } },
|
||||
surface: { tilesLoaded: () => true },
|
||||
render: { request() {} },
|
||||
};
|
||||
const engine = createFakeEngine({ container: document.createElement('div') });
|
||||
const tool = createGeoFileImport({
|
||||
drawFile: async () => ({
|
||||
report: { undrawn: 0 },
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createFrameRateMonitor } from './frameRateMonitor.js';
|
||||
@@ -17,15 +18,10 @@ function fixture(t) {
|
||||
addEventListener: (name, handler) => keys.add(handler),
|
||||
removeEventListener: (name, handler) => keys.delete(handler),
|
||||
};
|
||||
const engine = {
|
||||
frames: {
|
||||
postRender: {
|
||||
addEventListener(handler) {
|
||||
frames.add(handler);
|
||||
return () => frames.delete(handler);
|
||||
},
|
||||
},
|
||||
},
|
||||
const engine = createFakeEngine();
|
||||
engine.frames.postRender.addEventListener = (handler) => {
|
||||
frames.add(handler);
|
||||
return () => frames.delete(handler);
|
||||
};
|
||||
const monitor = createFrameRateMonitor({ engine, documentRef, store });
|
||||
t.after(() => monitor.destroy());
|
||||
@@ -108,7 +104,7 @@ test('a page without the title bar gets no readout and keeps the key', () => {
|
||||
let listened = false;
|
||||
const store = { publishFps: () => assert.fail('nothing to show it in') };
|
||||
const monitor = createFrameRateMonitor({
|
||||
engine: { frames: { postRender: { addEventListener: () => () => {} } } },
|
||||
engine: createFakeEngine(),
|
||||
documentRef: {
|
||||
getElementById: () => null,
|
||||
addEventListener: () => (listened = true),
|
||||
@@ -130,9 +126,7 @@ test("destroying an earlier monitor leaves a newer one's readout", () => {
|
||||
addEventListener() {},
|
||||
removeEventListener() {},
|
||||
};
|
||||
const engine = {
|
||||
frames: { postRender: { addEventListener: () => () => {} } },
|
||||
};
|
||||
const engine = createFakeEngine();
|
||||
const first = createFrameRateMonitor({ engine, documentRef, store });
|
||||
const second = createFrameRateMonitor({ engine, documentRef, store });
|
||||
const shown = store.fps;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
// src/ui/frameRateView.test.mjs — the title bar's FPS readout renders what
|
||||
// createFrameRateMonitor() publishes and toggles with the backquote key.
|
||||
import '../testSupport/registerDom.mjs';
|
||||
@@ -19,7 +20,7 @@ test('the FPS readout sits in the title bar, hidden until the backquote key', as
|
||||
document.querySelector('#title-bar .frame-rate-readout');
|
||||
assert.equal(readout(), null, 'no readout without a monitor');
|
||||
const monitor = createFrameRateMonitor({
|
||||
engine: { frames: { postRender: { addEventListener: () => () => {} } } },
|
||||
engine: createFakeEngine(),
|
||||
store: useStatusStore(pinia),
|
||||
});
|
||||
t.after(() => monitor.destroy());
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
// Behavioral tests for DISPLAY ▸ Import: the controller is driven against
|
||||
// fake DOM and engine doubles with an injected loader and drawing, and its
|
||||
// observable effects are asserted — drawings made and destroyed, the rows
|
||||
@@ -65,22 +66,18 @@ class FakeElement {
|
||||
/** A globe engine double: its host, camera moves and render requests. */
|
||||
function fakeEngine() {
|
||||
const moveEnd = new Set();
|
||||
const engine = {
|
||||
const engine = createFakeEngine({
|
||||
kind: 'fake',
|
||||
host: {
|
||||
container: new FakeElement('div', 'cesiumContainer'),
|
||||
canvas: new FakeElement('canvas'),
|
||||
},
|
||||
camera: {
|
||||
moveEnd: {
|
||||
addEventListener: (fn) => (moveEnd.add(fn), () => moveEnd.delete(fn)),
|
||||
},
|
||||
},
|
||||
surface: { tilesLoaded: () => true },
|
||||
renders: 0,
|
||||
render: { request: () => (engine.renders += 1) },
|
||||
moveEnd,
|
||||
};
|
||||
container: new FakeElement('div', 'cesiumContainer'),
|
||||
canvas: new FakeElement('canvas'),
|
||||
});
|
||||
engine.camera.moveEnd.addEventListener = (fn) => (
|
||||
moveEnd.add(fn),
|
||||
() => moveEnd.delete(fn)
|
||||
);
|
||||
engine.renders = 0;
|
||||
engine.render.request = () => (engine.renders += 1);
|
||||
engine.moveEnd = moveEnd;
|
||||
return engine;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
// A file removed while its drawing on the next engine is still pending
|
||||
// (icon decode) must not leave that drawing on the globe.
|
||||
import test from 'node:test';
|
||||
@@ -10,12 +11,7 @@ const el = () => ({
|
||||
addEventListener() {},
|
||||
removeEventListener() {},
|
||||
});
|
||||
const engine = () => ({
|
||||
host: { container: el(), canvas: el() },
|
||||
camera: { moveEnd: { addEventListener: () => () => {} } },
|
||||
surface: { tilesLoaded: () => true },
|
||||
render: { request() {} },
|
||||
});
|
||||
const engine = () => createFakeEngine({ container: el(), canvas: el() });
|
||||
|
||||
test('removing a file during its redraw destroys the late drawing', async () => {
|
||||
const document = {
|
||||
|
||||
@@ -445,16 +445,13 @@ test('the followed entity is read in the share format, and its offset round-trip
|
||||
gevDisplayPosition: () => target,
|
||||
},
|
||||
};
|
||||
const engine = {
|
||||
camera: {
|
||||
pose: () => ({ position: cameraPosition }),
|
||||
following: () =>
|
||||
viewer.trackedEntity && {
|
||||
native: viewer.trackedEntity,
|
||||
target: () => target,
|
||||
},
|
||||
},
|
||||
};
|
||||
const engine = createFakeEngine();
|
||||
engine.fake.setPose({ position: cameraPosition });
|
||||
engine.camera.following = () =>
|
||||
viewer.trackedEntity && {
|
||||
native: viewer.trackedEntity,
|
||||
target: () => target,
|
||||
};
|
||||
const subject = readShareFollowSubject({ engine, nowMs: NOW_MS });
|
||||
assert.equal(subject.kind, 'military_aircraft');
|
||||
assert.equal(subject.id, 'ae1234');
|
||||
|
||||
@@ -127,12 +127,11 @@ test('camera-entry listeners are removed before any asynchronous layer cleanup',
|
||||
const { owner } = bindings(t);
|
||||
let changes = 0;
|
||||
let trackedCallback;
|
||||
owner.engine = { camera: { followChanged: {
|
||||
addEventListener(callback) {
|
||||
trackedCallback = callback;
|
||||
return () => { changes += 1; };
|
||||
},
|
||||
} } };
|
||||
owner.engine = createFakeEngine();
|
||||
owner.engine.camera.followChanged.addEventListener = (callback) => {
|
||||
trackedCallback = callback;
|
||||
return () => { changes += 1; };
|
||||
};
|
||||
owner._stampNavigation = () => { changes += 100; };
|
||||
owner.observeCamera();
|
||||
owner.stop();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
// The shell lives for the page: an engine switch detaches only its
|
||||
// globe-bound parts and attaches them to the next globe, keeping the shell's
|
||||
// session objects (navigation, location search, HUD, share link, display
|
||||
@@ -37,7 +38,7 @@ function fakeShell() {
|
||||
},
|
||||
};
|
||||
Object.assign(shell, {
|
||||
_scene: { viewer: {}, engine: { kind: 'cesium' }, mapStackController: {} },
|
||||
_scene: { viewer: {}, engine: createFakeEngine({ kind: 'cesium' }), mapStackController: {} },
|
||||
_disposed: false,
|
||||
_layerSession: null,
|
||||
services: {
|
||||
@@ -143,7 +144,7 @@ test('the next globe gets new globe-bound parts and the restore runs on it', asy
|
||||
const { shell, session, log } = fakeShell();
|
||||
await shell.detachScene({ handoff: true });
|
||||
log.length = 0;
|
||||
const engine = { kind: 'navara' };
|
||||
const engine = createFakeEngine({ kind: 'navara' });
|
||||
await shell.attachScene({
|
||||
viewer: null,
|
||||
engine,
|
||||
@@ -176,7 +177,7 @@ test('the next globe gets new globe-bound parts and the restore runs on it', asy
|
||||
assert.equal(shell._navigation, session.navigation);
|
||||
await shell.attachScene({ engine });
|
||||
await assert.rejects(
|
||||
shell.attachScene({ engine: { kind: 'cesium' } }),
|
||||
shell.attachScene({ engine: createFakeEngine({ kind: 'cesium' }) }),
|
||||
/attached to a globe/,
|
||||
);
|
||||
});
|
||||
@@ -186,7 +187,7 @@ test('a panel whose layer is another instance in the new catalog is rebuilt', as
|
||||
await shell.detachScene({ handoff: true });
|
||||
log.length = 0;
|
||||
await shell.attachScene({
|
||||
engine: { kind: 'cesium' },
|
||||
engine: createFakeEngine({ kind: 'cesium' }),
|
||||
services: { cctvLayer: 'other' },
|
||||
});
|
||||
assert.ok(log.includes('_initCctvPanel'));
|
||||
@@ -209,7 +210,7 @@ test('an attachment that throws part-way leaves the shell detached and attachabl
|
||||
throw new Error('overlay host failed');
|
||||
};
|
||||
await assert.rejects(
|
||||
shell.attachScene({ engine: { kind: 'navara' } }),
|
||||
shell.attachScene({ engine: createFakeEngine({ kind: 'navara' }) }),
|
||||
/overlay host failed/,
|
||||
);
|
||||
// The shell releases what it built and is detached when that settles.
|
||||
@@ -217,7 +218,7 @@ test('an attachment that throws part-way leaves the shell detached and attachabl
|
||||
assert.equal(shell.engine, null);
|
||||
log.length = 0;
|
||||
shell._initSceneOverlays = () => log.push('_initSceneOverlays');
|
||||
const engine = { kind: 'cesium' };
|
||||
const engine = createFakeEngine({ kind: 'cesium' });
|
||||
await shell.attachScene({ engine });
|
||||
assert.equal(shell.engine, engine);
|
||||
assert.ok(log.includes('shareRestoration.start'));
|
||||
@@ -244,7 +245,7 @@ test('between engines a style key waits for the next engine instead of the relea
|
||||
shell._layerBindings = { observeCamera() {}, stop() {}, disconnect() {} };
|
||||
shell._visualSettings = { setStyle: (name) => applied.push(name) };
|
||||
};
|
||||
await shell.attachScene({ engine: { kind: 'cesium' } });
|
||||
await shell.attachScene({ engine: createFakeEngine({ kind: 'cesium' }) });
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
assert.deepEqual(applied, ['crt']);
|
||||
@@ -262,13 +263,13 @@ test('shell recovery waits for failed attachment cleanup and serializes repeated
|
||||
shell._initSceneOverlays = () => {
|
||||
throw new Error('overlay failed');
|
||||
};
|
||||
const failed = shell.attachScene({ engine: { kind: 'navara' } });
|
||||
const failed = shell.attachScene({ engine: createFakeEngine({ kind: 'navara' }) });
|
||||
const rejected = assert.rejects(failed, /overlay failed/);
|
||||
await Promise.resolve();
|
||||
const detach = shell.detachScene();
|
||||
const again = shell.detachScene();
|
||||
shell._initSceneOverlays = () => {};
|
||||
const engine = { kind: 'cesium' };
|
||||
const engine = createFakeEngine({ kind: 'cesium' });
|
||||
const recovered = shell.attachScene({ engine });
|
||||
await Promise.resolve();
|
||||
assert.notEqual(shell.engine, engine, 'recovery waits for cleanup');
|
||||
@@ -288,7 +289,7 @@ test('shell attach waits for a deferred outgoing detach', async () => {
|
||||
release = resolve;
|
||||
});
|
||||
const leaving = shell.detachScene({ handoff: true });
|
||||
const engine = { kind: 'navara' };
|
||||
const engine = createFakeEngine({ kind: 'navara' });
|
||||
const entering = shell.attachScene({ engine });
|
||||
await Promise.resolve();
|
||||
assert.notEqual(shell.engine, engine);
|
||||
@@ -302,7 +303,7 @@ test('an engine without Wildlife Sightings gets no card instead of a failed star
|
||||
// The stand-in a catalog gives for a layer the engine leaves out.
|
||||
Object.assign(shell, {
|
||||
services: { wildlifeSightingsLayer: {} },
|
||||
_scene: { viewer: null, engine: { kind: 'navara' } },
|
||||
_scene: { viewer: null, engine: createFakeEngine({ kind: 'navara' }) },
|
||||
});
|
||||
assert.doesNotThrow(() => shell._initWildlifeSightingCard());
|
||||
assert.equal(shell._wildlifeSightingCard, null);
|
||||
@@ -324,7 +325,7 @@ test('a hosted shell uses the enclosing transition while direct calls wait outsi
|
||||
entered();
|
||||
return new Promise((resolve) => { release = resolve; });
|
||||
};
|
||||
const next = { engine: { kind: 'navara' }, catalog };
|
||||
const next = { engine: createFakeEngine({ kind: 'navara' }), catalog };
|
||||
const switching = host.run(async (transaction) => {
|
||||
await host.detach({ handoff: true }, transaction);
|
||||
await host.attach(next, transaction);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { NavigationController } from './navigationController.js';
|
||||
@@ -69,7 +70,7 @@ test('share teardown settles its promise, removes gestures and rejects a retaine
|
||||
let stamps = 0;
|
||||
let applies = 0;
|
||||
const owner = new ShareRestoration({
|
||||
engine: { host: { canvas } },
|
||||
engine: createFakeEngine({ canvas }),
|
||||
navigation: {
|
||||
_beginDeferredNavigation: () => 1,
|
||||
_reassertNavigationHandoff: () => true,
|
||||
@@ -164,12 +165,11 @@ test('on Navara, presets set the Sharpen slider to 80 % and the engine draws it'
|
||||
_sharpenSliderRow: element(),
|
||||
_sharpenSliderValue: element(),
|
||||
};
|
||||
const engine = createFakeEngine({ kind: 'navara' });
|
||||
engine.render.setSharpen = (options) => sharpen.push(options);
|
||||
const owner = new VisualSettings({
|
||||
viewer: null,
|
||||
engine: {
|
||||
kind: 'navara',
|
||||
render: { setSharpen: (options) => sharpen.push(options) },
|
||||
},
|
||||
engine: engine,
|
||||
elements,
|
||||
operations: {
|
||||
_syncShareState() {},
|
||||
|
||||
@@ -57,7 +57,7 @@ function fixture() {
|
||||
createStage: (options) => ({ ...options, enabled: true }),
|
||||
},
|
||||
);
|
||||
const engine = { render: cesium.render, isDestroyed: () => false };
|
||||
const engine = Object.assign(createFakeEngine(), { render: cesium.render });
|
||||
const effects = new VisualEffects({
|
||||
engine,
|
||||
requestRender: (reason) => requests.push(reason),
|
||||
@@ -351,9 +351,8 @@ test('existing baseline and military presets keep one detection default', () =>
|
||||
test('an engine sharpens with the same amount scale, only while on and above 0', () => {
|
||||
const sharpen = [];
|
||||
const requests = [];
|
||||
const engine = {
|
||||
render: { setSharpen: (options) => sharpen.push(options) },
|
||||
};
|
||||
const engine = createFakeEngine();
|
||||
engine.render.setSharpen = (options) => sharpen.push(options);
|
||||
const effects = new VisualEffects({
|
||||
engine,
|
||||
requestRender: (reason) => requests.push(reason),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
// Pure-logic unit tests for the voice-lifecycle size guards (Batch 11, M13).
|
||||
// These helpers are DOM/WebRTC-free so they pin the screenshot down-scaling and
|
||||
// payload-byte estimation that keep an oversized dc.send from stranding a turn.
|
||||
@@ -2859,10 +2860,8 @@ test('isBenignViewportDeleteError is false for non-error payloads and junk', ()
|
||||
test('hidden document yields no fresh frame — capture must not label a stale canvas Current', async () => {
|
||||
const originalDocument = globalThis.document;
|
||||
let requested = 0;
|
||||
const engine = {
|
||||
frames: { postRender: { addEventListener() { return () => {}; } } },
|
||||
render: { request() { requested += 1; } },
|
||||
};
|
||||
const engine = createFakeEngine();
|
||||
engine.render.request = () => { requested += 1; };
|
||||
try {
|
||||
globalThis.document = { hidden: true };
|
||||
const fresh = await renderFreshCesiumFrame(engine);
|
||||
@@ -2878,10 +2877,9 @@ test('visible document with a rendering scene reports a fresh frame', async () =
|
||||
try {
|
||||
globalThis.document = { hidden: false };
|
||||
let fire = null;
|
||||
const engine = {
|
||||
frames: { postRender: { addEventListener(listener) { fire = listener; return () => { fire = null; }; } } },
|
||||
render: { request() { queueMicrotask(() => fire?.()); } },
|
||||
};
|
||||
const engine = createFakeEngine();
|
||||
engine.frames.postRender.addEventListener = (listener) => { fire = listener; return () => { fire = null; }; };
|
||||
engine.render.request = () => { queueMicrotask(() => fire?.()); };
|
||||
const fresh = await renderFreshCesiumFrame(engine);
|
||||
assert.equal(fresh, true);
|
||||
} finally {
|
||||
@@ -2895,10 +2893,9 @@ test('a tab switch during the bounded render wait invalidates freshness', async
|
||||
const doc = { hidden: false };
|
||||
globalThis.document = doc;
|
||||
let fire = null;
|
||||
const engine = {
|
||||
frames: { postRender: { addEventListener(listener) { fire = listener; return () => { fire = null; }; } } },
|
||||
render: { request() { doc.hidden = true; queueMicrotask(() => fire?.()); } },
|
||||
};
|
||||
const engine = createFakeEngine();
|
||||
engine.frames.postRender.addEventListener = (listener) => { fire = listener; return () => { fire = null; }; };
|
||||
engine.render.request = () => { doc.hidden = true; queueMicrotask(() => fire?.()); };
|
||||
const fresh = await renderFreshCesiumFrame(engine);
|
||||
assert.equal(fresh, false, 'freshness rechecked after the await');
|
||||
} finally {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
// Cross-feature voice contracts, driven through the real action runner:
|
||||
// one result contract (engine fields → model payload → voice card), shared
|
||||
// pointing for imagery, result-set ownership between OSM and the analyst,
|
||||
@@ -687,7 +688,7 @@ test('an engine switch keeps the last answer for a follow-up on the next engine'
|
||||
build: () =>
|
||||
harness({ runnerOptions: { analystMemory, layerEnabledAt } }).runner,
|
||||
});
|
||||
run.attach({ engine: { kind: 'cesium' } });
|
||||
run.attach({ engine: createFakeEngine({ kind: 'cesium' }) });
|
||||
const first = await run('analyst_query', {
|
||||
layers: ['flights'],
|
||||
scope: { kind: 'anywhere' },
|
||||
@@ -695,7 +696,7 @@ test('an engine switch keeps the last answer for a follow-up on the next engine'
|
||||
assert.equal(first.ok, true);
|
||||
// The real runner of the outgoing engine is disposed by the switch.
|
||||
run.detach();
|
||||
run.attach({ engine: { kind: 'navara' } });
|
||||
run.attach({ engine: createFakeEngine({ kind: 'navara' }) });
|
||||
const followUp = await run('analyst_query', {
|
||||
layers: ['flights'],
|
||||
followUp: true,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
@@ -96,23 +97,16 @@ test('crop: a slow render gets one bounded fresh-frame recovery request', async
|
||||
let listener = null;
|
||||
try {
|
||||
globalThis.document = { hidden: false };
|
||||
const engine = {
|
||||
frames: {
|
||||
postRender: {
|
||||
addEventListener(next) {
|
||||
listener = next;
|
||||
return () => {
|
||||
if (listener === next) listener = null;
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
render: {
|
||||
request() {
|
||||
requests += 1;
|
||||
if (requests === 2) queueMicrotask(() => listener?.());
|
||||
},
|
||||
},
|
||||
const engine = createFakeEngine();
|
||||
engine.frames.postRender.addEventListener = (next) => {
|
||||
listener = next;
|
||||
return () => {
|
||||
if (listener === next) listener = null;
|
||||
};
|
||||
};
|
||||
engine.render.request = () => {
|
||||
requests += 1;
|
||||
if (requests === 2) queueMicrotask(() => listener?.());
|
||||
};
|
||||
assert.equal(await renderFreshPointerFrame(engine), true);
|
||||
assert.equal(requests, 2, 'only the pointer path retries once');
|
||||
@@ -127,13 +121,9 @@ test('crop: a hidden document never starts the recovery request', async () => {
|
||||
let requests = 0;
|
||||
try {
|
||||
globalThis.document = { hidden: true };
|
||||
const engine = {
|
||||
frames: { postRender: { addEventListener: () => () => {} } },
|
||||
render: {
|
||||
request() {
|
||||
requests += 1;
|
||||
},
|
||||
},
|
||||
const engine = createFakeEngine();
|
||||
engine.render.request = () => {
|
||||
requests += 1;
|
||||
};
|
||||
assert.equal(await renderFreshPointerFrame(engine), false);
|
||||
assert.equal(requests, 0);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createSceneRunner, engineSwitchingRefusal } from './sceneRunner.js';
|
||||
|
||||
const engine = (kind) => ({ kind });
|
||||
const engine = (kind) => createFakeEngine({ kind });
|
||||
|
||||
function runnerFor(log) {
|
||||
return (scene) => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createFakeEngine } from '../globe/testing/fakeEngine.js';
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createSceneVoice } from './gevRealtime.js';
|
||||
@@ -6,11 +7,7 @@ import { createTestVoiceUi } from '../testSupport/voiceUi.mjs';
|
||||
function fakeEngine(kind) {
|
||||
const canvas = new EventTarget();
|
||||
canvas.getBoundingClientRect = () => ({ left: 0, top: 0 });
|
||||
return {
|
||||
kind,
|
||||
host: { canvas, size: () => ({ width: 0, height: 0 }) },
|
||||
camera: { pose: () => ({}) },
|
||||
};
|
||||
return createFakeEngine({ kind, canvas, width: 0, height: 0 });
|
||||
}
|
||||
|
||||
function fixture() {
|
||||
|
||||
Reference in new issue
Block a user