mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
The panel connected before the shell had its data manager, so it never heard switch-on requests and a user switch-on left it collapsed. Co-Authored-By: Claude Opus 5.5 <[email protected]>
164 lines
5.8 KiB
JavaScript
164 lines
5.8 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { LocationNavigation } from './locationNavigation.js';
|
|
import { LayerBindings } from './layerBindings.js';
|
|
|
|
function replaceWindow(t, value) {
|
|
const previous = Object.getOwnPropertyDescriptor(globalThis, 'window');
|
|
Object.defineProperty(globalThis, 'window', { configurable: true, writable: true, value });
|
|
t.after(() => {
|
|
if (previous) Object.defineProperty(globalThis, 'window', previous);
|
|
else delete globalThis.window;
|
|
});
|
|
}
|
|
|
|
function location(t) {
|
|
t.mock.timers.enable({ apis: ['setTimeout'] });
|
|
replaceWindow(t, { setTimeout });
|
|
const calls = [];
|
|
let hooks;
|
|
const owner = new LocationNavigation({
|
|
viewer: { camera: {
|
|
cancelFlight() {}, lookAtTransform() {},
|
|
positionCartographic: { latitude: 0, longitude: 0, height: 20000000 },
|
|
} },
|
|
services: {
|
|
OrbitController: class { stop() { calls.push('orbit-stop'); } },
|
|
GLOBE_VIEW: { heightM: 20000000 },
|
|
flyToGlobeView(_viewer, callbacks) { hooks = callbacks; return {}; },
|
|
interruptCameraMotion() {},
|
|
militaryAwarenessLayer: {}, satellitesLayer: {}, rocketLaunchesLayer: {},
|
|
trafficLayer: {
|
|
beginWorldJump() { calls.push('begin'); },
|
|
endWorldJump() { calls.push('end'); },
|
|
},
|
|
suspendDetection() { calls.push('suspend'); },
|
|
resumeDetection() { calls.push('resume'); },
|
|
},
|
|
elements: {}, navigation: {}, readCockpit: () => null,
|
|
operations: {
|
|
_stampNavigation() {}, _updateTrafficSyncChip() {},
|
|
_runExplicitNavigation(_noun, run) { return run(); },
|
|
},
|
|
});
|
|
return { owner, calls, readHooks: () => hooks };
|
|
}
|
|
|
|
test('shell disposal settles a pending globe reset once and revokes its timeout', async (t) => {
|
|
const { owner, calls, readHooks } = location(t);
|
|
const pending = owner.resetToGlobeView();
|
|
assert.equal(owner.resetToGlobeView(), pending);
|
|
owner.destroy();
|
|
assert.equal((await pending).cancelled, true);
|
|
const after = [...calls];
|
|
readHooks().onComplete();
|
|
t.mock.timers.tick(10000);
|
|
owner.destroy();
|
|
assert.deepEqual(calls, after);
|
|
assert.equal(calls.filter(c => c === 'resume').length, 1);
|
|
assert.equal((await owner.resetToGlobeView()).cancelled, true);
|
|
});
|
|
|
|
test('world jump completion cannot revive navigation after shell disposal', (t) => {
|
|
const { owner, calls } = location(t);
|
|
let complete;
|
|
owner._flyWithTransition(true, ({ onStart, onComplete }) => {
|
|
onStart(); complete = onComplete;
|
|
});
|
|
owner.destroy();
|
|
const after = [...calls];
|
|
complete();
|
|
t.mock.timers.tick(10000);
|
|
assert.deepEqual(calls, after);
|
|
assert.equal(calls.filter(c => c === 'resume').length, 1);
|
|
});
|
|
|
|
function bindings(t) {
|
|
replaceWindow(t, new EventTarget());
|
|
const events = [];
|
|
const controls = {
|
|
hud: { attachDataManager() {} },
|
|
_contextControls: { connect() {} },
|
|
_cctvControls: { connect() {} }, _radioControls: { connect() {} },
|
|
_streetLevelControls: { connects: 0, connect() { this.connects += 1; } },
|
|
};
|
|
const owner = new LayerBindings({
|
|
viewer: {}, services: {}, readControls: () => controls,
|
|
feedback: {}, shareRestoration: { connect() {} },
|
|
operations: {
|
|
_updateTrafficSyncChip() {}, _updateGlobalLoadingFeedback() {},
|
|
_syncContextModeButtons() {},
|
|
},
|
|
});
|
|
function manager(id, directions = true) {
|
|
return {
|
|
layers: new Map(directions ? [['directions', { module: {
|
|
attachShellServices(value) { events.push([id, value ? 'attach' : 'detach']); },
|
|
} }]] : []),
|
|
subscribe() { events.push([id, 'subscribe']); return () => events.push([id, 'unsubscribe']); },
|
|
};
|
|
}
|
|
return { owner, manager, events, controls };
|
|
}
|
|
|
|
test('manager replacement releases Directions even when the new manager has no route layer', (t) => {
|
|
const { owner, manager, events } = bindings(t);
|
|
owner.attachDataManager(manager('old'));
|
|
owner.attachDataManager(manager('new', false));
|
|
assert.deepEqual(events, [
|
|
['old', 'subscribe'], ['old', 'attach'], ['old', 'unsubscribe'],
|
|
['new', 'subscribe'], ['old', 'detach'],
|
|
]);
|
|
owner.stop(); owner.disconnect();
|
|
assert.deepEqual(events.at(-1), ['new', 'unsubscribe']);
|
|
const count = events.length;
|
|
owner.attachDataManager(manager('late'));
|
|
owner.stop(); owner.disconnect();
|
|
assert.equal(events.length, count);
|
|
});
|
|
|
|
test('attaching the manager reconnects Street Level, so its switch-on requests reach the panel', (t) => {
|
|
const { owner, manager, controls } = bindings(t);
|
|
owner.attachDataManager(manager('first'));
|
|
assert.equal(controls._streetLevelControls.connects, 1);
|
|
owner.stop(); owner.disconnect();
|
|
});
|
|
|
|
test('camera-entry listeners are removed before any asynchronous layer cleanup', (t) => {
|
|
const { owner } = bindings(t);
|
|
let changes = 0;
|
|
let trackedCallback;
|
|
owner.viewer = { trackedEntityChanged: {
|
|
addEventListener(callback) {
|
|
trackedCallback = callback;
|
|
return () => { changes += 1; };
|
|
},
|
|
} };
|
|
owner._stampNavigation = () => { changes += 100; };
|
|
owner.observeCamera();
|
|
owner.stop();
|
|
trackedCallback({});
|
|
owner.stop();
|
|
assert.equal(changes, 1);
|
|
});
|
|
|
|
|
|
test('weather shell supplies live imagery hosts to wind and observations and releases them', (t) => {
|
|
const { owner } = bindings(t);
|
|
let host = { collection: {}, kind: 'tileset' };
|
|
owner.services.imageryHost = () => host;
|
|
const attached = new Map();
|
|
const ids = ['wind', 'weather-radar', 'weather-satellite', 'weather-lightning'];
|
|
owner.attachDataManager({
|
|
layers: new Map(ids.map(id => [id, { module: {
|
|
attachShellServices(services) { attached.set(id, services); },
|
|
} }])),
|
|
subscribe: () => () => {},
|
|
});
|
|
for (const id of ids) assert.equal(attached.get(id).imageryHost(), host);
|
|
host = { collection: null, kind: 'none' };
|
|
for (const id of ids) assert.equal(attached.get(id).imageryHost(), host);
|
|
owner.disconnect();
|
|
for (const id of ids) assert.equal(attached.get(id), null);
|
|
});
|