From 9e3786716de80777d73855376548c05f964e33b7 Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Tue, 3 Nov 2026 09:11:51 -0800 Subject: [PATCH] test(camera): pin that application controls install the zoom-out guard The guard's own tests pass even if createApplicationControls stops installing it. This checks the ceiling and frame listeners are in place after construction and gone after teardown. --- scripts/format-scope.json | 1 + src/app/controls.test.mjs | 68 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 src/app/controls.test.mjs diff --git a/scripts/format-scope.json b/scripts/format-scope.json index 9e1ceea86..afe2e153c 100644 --- a/scripts/format-scope.json +++ b/scripts/format-scope.json @@ -58,6 +58,7 @@ "src/app/startupCamera.test.mjs", "src/app/viewer.js", "src/app/zoomOutGuard.test.mjs", + "src/app/controls.test.mjs", "scripts/qa-zoom-out-guard.mjs", "src/app/atmosphereCompat.test.mjs", "src/standalone/application.js", diff --git a/src/app/controls.test.mjs b/src/app/controls.test.mjs new file mode 100644 index 000000000..3db3b7d8b --- /dev/null +++ b/src/app/controls.test.mjs @@ -0,0 +1,68 @@ +import test, { after, before } from 'node:test'; +import assert from 'node:assert/strict'; +import * as Cesium from 'cesium'; +import '../testSupport/stubMgrs.mjs'; +import { installVueDom } from '../testSupport/vueDom.mjs'; +import { MAXIMUM_ZOOM_DISTANCE_M } from './zoomOutGuard.js'; + +const { createApplicationControls } = await import('./controls.js'); + +let uninstallDom; +before(() => { + uninstallDom = installVueDom(); +}); +after(() => uninstallDom()); + +test('application controls install the zoom-out guard and remove it on teardown', () => { + const canvas = document.createElement('canvas'); + const controller = { maximumZoomDistance: Infinity }; + const scene = { + mode: Cesium.SceneMode.SCENE3D, + screenSpaceCameraController: controller, + preRender: new Cesium.Event(), + postUpdate: new Cesium.Event(), + }; + const camera = { + position: Cesium.Cartesian3.fromDegrees(0, 0, 1_000_000), + positionCartographic: { height: 1_000_000 }, + }; + const viewer = { scene, camera, canvas }; + const handoffs = new Set(); + class Controls { + hasShareState = true; + orbitController = { stop() {} }; + hud = { destroy() {} }; + dispose() {} + subscribeCameraHandoff(listener) { + handoffs.add(listener); + return () => handoffs.delete(listener); + } + } + const cleanups = []; + createApplicationControls({ + scene: { + viewer, + mapStackController: {}, + operations: { + surface: { controlServices: {} }, + searchAndFlyTo() {}, + requests: { regional: {}, weather: {} }, + }, + }, + loaderStatus: {}, + Controls, + catalog: { get: (id) => ({ id }) }, + defer: (cleanup) => cleanups.push(cleanup), + }); + + assert.equal(controller.maximumZoomDistance, MAXIMUM_ZOOM_DISTANCE_M); + assert.equal(scene.preRender.numberOfListeners, 1); + assert.equal(scene.postUpdate.numberOfListeners, 1); + assert.equal(handoffs.size, 1); + + for (const cleanup of cleanups.reverse()) cleanup(); + assert.equal(controller.maximumZoomDistance, Infinity); + assert.equal(scene.preRender.numberOfListeners, 0); + assert.equal(scene.postUpdate.numberOfListeners, 0); + assert.equal(handoffs.size, 0); +});