mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-11 13:52:19 +00:00
Merge branch 'german/refactor-e12b-fake-engine-tests' into german/navara-refactor
This commit is contained in:
commit
8caff876cb
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