Files
gods-eye-view/src/data/cctvExplicitPickEndsOpening.test.mjs
T
German Khokhlov b65a7aaed8 Keep an explicit CCTV pick of the camera a move re-opened
Until someone picks a camera, a settled move re-opens CCTV on the camera
nearest the view and keeps the opening pick provisional. setActiveCamera
ends that opening pick, but for a camera that is already active and
activated it returns UNCHANGED before clearing the flag. So pressing
NEAREST on the camera a move had just re-opened (or selecting it from the
list, or through setParams) left the pick provisional, and the landing of
NEAREST's own flight, or the next pan, switched to another camera.

Clear the provisional flag on the UNCHANGED path too. reopenNearestCamera
only activates a different camera, so it is unaffected, and the same-camera
re-select still skips the activation work.
2026-11-13 18:47:30 +04:00

130 lines
4.0 KiB
JavaScript

// An explicit pick of the camera that a settled move just re-opened must end
// the opening pick. That pick goes through setActiveCamera's UNCHANGED path
// (same camera, already activated); if that path keeps the provisional flag,
// the next settled move (a pan, or the landing of NEAREST's own flight)
// switches the camera away from the one the user chose.
import assert from 'node:assert/strict';
import test from 'node:test';
import * as Cesium from 'cesium';
import cctvLayer, {
_setCctvCoverageStateForTest,
_setCctvOverlayHostForTest,
reopenNearestCamera,
} from './cctv.js';
const BASE = {
headingDeg: 41,
pitchDeg: -5,
fovDeg: 56,
rangeM: 210,
mountHeightM: 100,
groundElevationM: 0,
city: 'London',
};
function record(id, lat, lon) {
return {
camera: { ...BASE, id, name: `Camera ${id}`, lat, lon },
coverageEntities: [],
projection: null,
activationDone: true,
groundResolved: {},
groundSamples: {},
groundPrior: 0,
frustumGeometry: null,
frustumPositions: null,
probeClampRangeM: null,
position: Cesium.Cartesian3.fromDegrees(lon, lat, 100),
};
}
function viewerAt(lat, lon) {
return {
entities: new Cesium.EntityCollection(),
isDestroyed: () => false,
trackedEntity: null,
camera: {
positionWC: Cesium.Cartesian3.fromDegrees(lon, lat, 300),
positionCartographic: Cesium.Cartographic.fromDegrees(lon, lat, 300),
heading: 0.4,
pitch: -0.7,
roll: 0,
transform: Cesium.Matrix4.clone(Cesium.Matrix4.IDENTITY),
flyToBoundingSphere() {},
},
scene: {
canvas: { clientWidth: 1200, clientHeight: 800 },
cartesianToCanvasCoordinates: () => undefined,
primitives: { add: (p) => p, remove: () => true },
requestRender() {},
},
};
}
function moveViewer(viewer, lat, lon) {
viewer.camera.positionCartographic = Cesium.Cartographic.fromDegrees(
lon,
lat,
300,
);
}
function withReopenedLayer(fn) {
_setCctvOverlayHostForTest({
setEntries() {},
setVisible() {},
clearSource() {},
});
// Activation builds the projection canvas and frame image.
const originalDocument = globalThis.document;
const originalImage = globalThis.Image;
globalThis.document = { createElement: () => ({ getContext: () => null }) };
globalThis.Image = class {};
try {
const trafalgar = record('a-trafalgar', 51.508, -0.128);
const cambridge = record('b-cambridge', 51.513, -0.129);
// enable() assigned the opening camera without activating it.
trafalgar.activationDone = false;
const viewer = viewerAt(51.5081, -0.1281);
_setCctvCoverageStateForTest({
viewer,
records: [trafalgar, cambridge],
activeCameraId: 'a-trafalgar',
enabled: true,
openingCameraProvisional: true,
});
// A settled move next to Cambridge Circus re-opens there, still as the
// opening pick.
moveViewer(viewer, 51.5131, -0.1291);
assert.equal(reopenNearestCamera(), true);
assert.equal(cctvLayer.getUIState().activeCameraId, 'b-cambridge');
fn(viewer);
} finally {
globalThis.document = originalDocument;
globalThis.Image = originalImage;
_setCctvOverlayHostForTest();
_setCctvCoverageStateForTest({ enabled: false });
}
}
for (const [name, pick] of [
['NEAREST (focusNearest)', () => cctvLayer.focusNearest({ focus: false })],
['selectCamera', () => cctvLayer.selectCamera('b-cambridge')],
[
'setParams({ selectedCameraId })',
() => cctvLayer.setParams({ selectedCameraId: 'b-cambridge' }),
],
]) {
test(`an explicit ${name} pick of the re-opened camera ends the opening pick`, () => {
withReopenedLayer((viewer) => {
pick();
assert.equal(cctvLayer.getUIState().activeCameraId, 'b-cambridge');
// A later settled move (a pan, or the landing of the pick's own flight)
// leaves the explicit choice alone.
moveViewer(viewer, 51.5081, -0.1281);
assert.equal(reopenNearestCamera(), false);
assert.equal(cctvLayer.getUIState().activeCameraId, 'b-cambridge');
});
});
}