From 68855b2089a2a3ab7ae27067cf3cea6dc0b880a5 Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Fri, 6 Nov 2026 09:55:05 -0800 Subject: [PATCH] Undo the reformatting of four camera-slice tests The camera-port commits ran prettier over four test files that were never prettier-formatted. Restore their earlier text and keep only the camera-port edits. --- src/cameraHandoff.test.mjs | 466 ++----- src/data/militaryInstallations.test.mjs | 821 +++--------- src/data/radio.test.mjs | 1630 +++++++---------------- src/worldFocus.test.mjs | 153 +-- 4 files changed, 804 insertions(+), 2266 deletions(-) diff --git a/src/cameraHandoff.test.mjs b/src/cameraHandoff.test.mjs index b8171dd72..6f33f9121 100644 --- a/src/cameraHandoff.test.mjs +++ b/src/cameraHandoff.test.mjs @@ -1,13 +1,7 @@ import { LayerBindings } from './ui/layerBindings.js'; -import { - readShellSource, - shellMethod, -} from './testSupport/readShellSource.mjs'; +import { readShellSource, shellMethod } from './testSupport/readShellSource.mjs'; import { readLayerSource } from './testSupport/readLayerSource.mjs'; -import { - enter as cockpitEnter, - navigateContext, -} from './ui/cockpitTrackingController.js'; +import { enter as cockpitEnter, navigateContext } from './ui/cockpitTrackingController.js'; import { CockpitViewController } from './ui/cockpitController.js'; import assert from 'node:assert/strict'; import fs from 'node:fs'; @@ -17,24 +11,11 @@ import { fileURLToPath } from 'node:url'; const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const ui = readShellSource(); -const firms = readLayerSource( - path.join(ROOT, 'src', 'data', 'firmsHeatmap.js'), -); -const vessels = readLayerSource( - path.join(ROOT, 'src', 'data', 'aisLiveVessels.js'), -); -const voice = fs.readFileSync( - path.join(ROOT, 'src', 'voice', 'gevActions.js'), - 'utf8', -); -const cameraVerbs = fs.readFileSync( - path.join(ROOT, 'src', 'cameraVerbs.js'), - 'utf8', -); -const cockpitTracking = fs.readFileSync( - path.join(ROOT, 'src', 'cockpitTracking.js'), - 'utf8', -); +const firms = readLayerSource(path.join(ROOT, 'src', 'data', 'firmsHeatmap.js')); +const vessels = readLayerSource(path.join(ROOT, 'src', 'data', 'aisLiveVessels.js')); +const voice = fs.readFileSync(path.join(ROOT, 'src', 'voice', 'gevActions.js'), 'utf8'); +const cameraVerbs = fs.readFileSync(path.join(ROOT, 'src', 'cameraVerbs.js'), 'utf8'); +const cockpitTracking = fs.readFileSync(path.join(ROOT, 'src', 'cockpitTracking.js'), 'utf8'); function body(source, pattern, label) { const name = pattern.source.match(/^(\w+)/)?.[1]; @@ -50,14 +31,8 @@ function body(source, pattern, label) { function ordered(source, needles, label) { let previous = -1; for (const needle of needles) { - const match = - needle instanceof RegExp ? needle.exec(source.slice(previous + 1)) : null; - const index = - needle instanceof RegExp - ? match - ? previous + 1 + match.index - : -1 - : source.indexOf(needle, previous + 1); + const match = needle instanceof RegExp ? needle.exec(source.slice(previous + 1)) : null; + const index = needle instanceof RegExp ? (match ? previous + 1 + match.index : -1) : source.indexOf(needle, previous + 1); assert.ok(index >= 0, `${label}: missing ${needle}`); assert.ok(index > previous, `${label}: ${needle} is out of order`); previous = index; @@ -66,16 +41,12 @@ function ordered(source, needles, label) { test('Cockpit takeover invalidates deferred work before camera cancellation', () => { const enter = cockpitEnter.toString(); - ordered( - enter, - [ - 'if (!info || !entity?.position) return false;', - 'this.onCameraTakeover?.();', - 'cameraOf(this.viewer).cancelFlight();', - 'this.viewer.trackedEntity = undefined;', - ], - 'Cockpit takeover', - ); + ordered(enter, [ + 'if (!info || !entity?.position) return false;', + 'this.onCameraTakeover?.();', + 'cameraOf(this.viewer).cancelFlight();', + 'this.viewer.trackedEntity = undefined;', + ], 'Cockpit takeover'); assert.match( ui, /onCameraTakeover: \(\) =>\s*this\._stampNavigation\(\{\s*cancelPendingSelection: false,?\s*\}\),/, @@ -109,23 +80,11 @@ test('navigation clears dormant tracker IDs without aborting unrelated layer res ); assert.doesNotMatch(stamp, /cancelPendingRestores\(\)/); assert.match(stamp, /flightsLayer\.cancelPendingTrackingRestore\?\.\(\)/); - assert.match( - stamp, - /militaryFlightsLayer\.cancelPendingTrackingRestore\?\.\(\)/, - ); + assert.match(stamp, /militaryFlightsLayer\.cancelPendingTrackingRestore\?\.\(\)/); assert.match(stamp, /satellitesLayer\.cancelPendingTrackingRestore\?\.\(\)/); - assert.match( - stamp, - /if \(\s*!passivelyClearedShareSelection\s*&&\s*!flightsLayer\.getTrackedInfo\?\.\(\)\s*\)[\s\S]*?selectedFlightsTrackingId: null/, - ); - assert.match( - stamp, - /if \(\s*!passivelyClearedShareSelection\s*&&\s*!militaryFlightsLayer\.getTrackedInfo\?\.\(\)\s*\)[\s\S]*?selectedMilitaryTrackingId: null/, - ); - assert.match( - stamp, - /if \(\s*!passivelyClearedShareSelection\s*&&\s*!satellitesLayer\.getTrackedInfo\?\.\(\)\s*\)[\s\S]*?selectedSatTrackingId: null/, - ); + assert.match(stamp, /if \(\s*!passivelyClearedShareSelection\s*&&\s*!flightsLayer\.getTrackedInfo\?\.\(\)\s*\)[\s\S]*?selectedFlightsTrackingId: null/); + assert.match(stamp, /if \(\s*!passivelyClearedShareSelection\s*&&\s*!militaryFlightsLayer\.getTrackedInfo\?\.\(\)\s*\)[\s\S]*?selectedMilitaryTrackingId: null/); + assert.match(stamp, /if \(\s*!passivelyClearedShareSelection\s*&&\s*!satellitesLayer\.getTrackedInfo\?\.\(\)\s*\)[\s\S]*?selectedSatTrackingId: null/); }); test('voice Cockpit entry reaches the camera only through stamping seams', () => { @@ -145,14 +104,10 @@ test('voice Cockpit entry reaches the camera only through stamping seams', () => /export function enterCockpitWithTracking\(\{[\s\S]*?\n\}\) \{([\s\S]*?)\n\}\n/, 'Cockpit entry transaction', ); - ordered( - transaction, - [ - /if \(\s*!selectedLayer\.trackById\?\.\(selectedTarget\.id, \{\s*origin: selectionOrigin,?\s*\}\)\s*\) \{/, - 'if (!entryError) entered = Boolean(cockpitView.enter());', - ], - 'Cockpit entry transaction', - ); + ordered(transaction, [ + /if \(\s*!selectedLayer\.trackById\?\.\(selectedTarget\.id, \{\s*origin: selectionOrigin,?\s*\}\)\s*\) \{/, + 'if (!entryError) entered = Boolean(cockpitView.enter());', + ], 'Cockpit entry transaction'); // No direct camera control: every mutation goes through a layer tracker or // the cockpit controller, both of which stamp. assert.doesNotMatch(transaction, /\b(?:viewer\.)?camera\s*(?:\.|\[|=)/); @@ -191,41 +146,27 @@ test('voice Cockpit next/previous shares the manual Context navigation path', () /this\._listen\(this\.contextNext, 'click', \(\) =>\s*this\.navigateContext\(1, \{ origin: 'user' \}\),?\s*\);/, ); const funnel = navigateContext.toString(); - assert.match( - funnel, - /const navigationOptions = wasActive\s*\? \{ \.\.\.options, aircraftOnly: true \}\s*: options;/, - ); - ordered( - funnel, - [ - "const method = direction < 0 ? 'navigatePrevious' : 'navigateNext';", - 'const navigationOptions = wasActive', - 'militaryAwarenessLayer?.[method]?.(navigationOptions)', - 'this._adoptTrackedEntity(performance.now());', - ], - 'Cockpit Context navigation funnel', - ); + assert.match(funnel, /const navigationOptions = wasActive\s*\? \{ \.\.\.options, aircraftOnly: true \}\s*: options;/); + ordered(funnel, [ + "const method = direction < 0 ? 'navigatePrevious' : 'navigateNext';", + 'const navigationOptions = wasActive', + 'militaryAwarenessLayer?.[method]?.(navigationOptions)', + 'this._adoptTrackedEntity(performance.now());', + ], 'Cockpit Context navigation funnel'); const navigate = body( ui, /if \(normalized === 'next' \|\| normalized === 'previous'\) \{([\s\S]*?)\n \}/, 'controlCockpit navigation branch', ); - ordered( - navigate, - ['this.cockpitView.navigateContext(', "normalized === 'next' ? 1 : -1,"], - 'controlCockpit navigation branch', - ); + ordered(navigate, [ + 'this.cockpitView.navigateContext(', + "normalized === 'next' ? 1 : -1,", + ], 'controlCockpit navigation branch'); // No private camera route around the awareness layer. - assert.doesNotMatch( - navigate, - /flyTo|camera|trackById|_runExplicitNavigation/, - ); + assert.doesNotMatch(navigate, /flyTo|camera|trackById|_runExplicitNavigation/); // An exhausted cohort is an honest failure, not a silent success. assert.match(navigate, /ok: changed,/); - assert.match( - navigate, - /error: changed \? null : 'No further context target was available',/, - ); + assert.match(navigate, /error: changed \? null : 'No further context target was available',/); }); test('accepted navigation releases through PR15-aware ownership before flight', () => { @@ -234,69 +175,37 @@ test('accepted navigation releases through PR15-aware ownership before flight', /_runExplicitNavigation\(noun, navigate, releaseOptions = undefined\) \{([\s\S]*?)\n \}/, 'explicit navigation', ); - ordered( - run, - [ - 'cockpitActive: this.isCockpitActive()', - 'stamp: () => this._stampNavigation()', - 'release: () => this._releaseFollowCamera(releaseOptions)', - 'navigate,', - ], - 'explicit navigation', - ); + ordered(run, [ + 'cockpitActive: this.isCockpitActive()', + 'stamp: () => this._stampNavigation()', + 'release: () => this._releaseFollowCamera(releaseOptions)', + 'navigate,', + ], 'explicit navigation'); const release = body( ui, /_releaseFollowCamera\(\{[\s\S]*?\} = \{\}\) \{([\s\S]*?)\n \}/, 'follow release', ); - ordered( - release, - [ - 'origin: trackingOrigin', - 'satellitesLayer.stopTracking?.({ origin: trackingOrigin })', - 'rocketLaunchesLayer.releaseCameraOwnership?.()', - 'this.viewer.trackedEntity = undefined;', - "interruptCameraMotion('explicit-navigation')", - 'this.viewer.camera.cancelFlight();', - 'this.viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);', - ], - 'follow release', - ); - assert.match( - release, - /flightsLayer\.stopTracking\?\.\(\{ origin: trackingOrigin \}\)/, - ); - assert.match( - release, - /militaryFlightsLayer\.stopTracking\?\.\(\{ origin: trackingOrigin \}\)/, - ); + ordered(release, [ + 'origin: trackingOrigin', + 'satellitesLayer.stopTracking?.({ origin: trackingOrigin })', + 'rocketLaunchesLayer.releaseCameraOwnership?.()', + 'this.viewer.trackedEntity = undefined;', + "interruptCameraMotion('explicit-navigation')", + 'this.viewer.camera.cancelFlight();', + 'this.viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);', + ], 'follow release'); + assert.match(release, /flightsLayer\.stopTracking\?\.\(\{ origin: trackingOrigin \}\)/); + assert.match(release, /militaryFlightsLayer\.stopTracking\?\.\(\{ origin: trackingOrigin \}\)/); }); test('validated voice camera destinations share the UI navigation authority facade', () => { - assert.match( - ui, - /runImmediateNavigation\(noun, navigate, releaseOptions = undefined\) \{\s*return this\._runExplicitNavigation\(noun, navigate, releaseOptions\);/, - ); - assert.match( - voice, - /runManagedVoiceNavigation\(\s*styleManager,\s*'camera',\s*'move_camera',\s*navigate,\s*releaseOptions/, - ); - assert.match( - voice, - /runManagedVoiceNavigation\(\s*styleManager,\s*'route',\s*'fly_route',\s*navigate/, - ); - assert.match( - voice, - /runManagedVoiceNavigation\(\s*styleManager,\s*'fire',\s*'track_entity'/, - ); - assert.match( - voice, - /runManagedVoiceNavigation\(\s*styleManager,\s*family\.kind,\s*'track_entity'/, - ); - assert.match( - voice, - /runManagedVoiceNavigation\(\s*styleManager,\s*'frame',\s*'frame_overhead'/, - ); + assert.match(ui, /runImmediateNavigation\(noun, navigate, releaseOptions = undefined\) \{\s*return this\._runExplicitNavigation\(noun, navigate, releaseOptions\);/); + assert.match(voice, /runManagedVoiceNavigation\(\s*styleManager,\s*'camera',\s*'move_camera',\s*navigate,\s*releaseOptions/); + assert.match(voice, /runManagedVoiceNavigation\(\s*styleManager,\s*'route',\s*'fly_route',\s*navigate/); + assert.match(voice, /runManagedVoiceNavigation\(\s*styleManager,\s*'fire',\s*'track_entity'/); + assert.match(voice, /runManagedVoiceNavigation\(\s*styleManager,\s*family\.kind,\s*'track_entity'/); + assert.match(voice, /runManagedVoiceNavigation\(\s*styleManager,\s*'frame',\s*'frame_overhead'/); const trackedVoice = voice.slice( voice.indexOf('async function trackEntity'), voice.indexOf('async function frameOverhead'), @@ -313,80 +222,47 @@ test('validated voice camera destinations share the UI navigation authority faca /export function moveCamera\(args = \{\}, runNavigation = null\) \{([\s\S]*?)\n\}/, 'move camera', ); - ordered( - move, - [ - "if (!['orbit', 'pan', 'tilt', 'rotate', 'rotate_globe'].includes(motion))", - 'const start = () => {', - /const preserveCameraFlight =\s*motion === 'orbit'/, - 'runNavigation(start, { preserveCameraFlight })', - ], - 'move validation before handoff', - ); + ordered(move, [ + "if (!['orbit', 'pan', 'tilt', 'rotate', 'rotate_globe'].includes(motion))", + 'const start = () => {', + /const preserveCameraFlight =\s*motion === 'orbit'/, + 'runNavigation(start, { preserveCameraFlight })', + ], 'move validation before handoff'); const route = body( cameraVerbs, /export function flyRoute\(\s*annoList,\s*args = \{\},\s*floorFn = null,\s*runNavigation = null,\s*warmFn = null,?\s*\) \{([\s\S]*?)\n\}/, 'fly route', ); - ordered( - route, - [ - 'if (!routes.length)', - 'const pts = route.path.map', - 'const start = () => {', - /return typeof runNavigation === 'function'\s*\? runNavigation\(start\)\s*: start\(\);/, - ], - 'route validation before handoff', - ); + ordered(route, [ + 'if (!routes.length)', + 'const pts = route.path.map', + 'const start = () => {', + /return typeof runNavigation === 'function'\s*\? runNavigation\(start\)\s*: start\(\);/, + ], 'route validation before handoff'); // The corridor warm is injected the same way the floor READ is — the dolly // never reaches into the data layer itself, and the voice dispatch is the one // place that binds both. - assert.match( - voice, - /\(lat, lon\) => floorServices\.cachedGroundFloor\(lat, lon\),[\s\S]{0,200}?\(cells\) => floorServices\.warmGroundFloor\(cells\),/, - ); + assert.match(voice, /\(lat, lon\) => floorServices\.cachedGroundFloor\(lat, lon\),[\s\S]{0,200}?\(cells\) => floorServices\.warmGroundFloor\(cells\),/); }); test('deferred search releases only after its final authority check', () => { - const search = fs.readFileSync( - path.join(ROOT, 'src', 'ui', 'locationSearch.js'), - 'utf8', - ); - ordered( - search, - [ - 'const authority = this.begin();', - 'this.onStart?.(authority);', - 'await this.search(query', - 'beforeFly: () => current() && this.beforeFly(authority)', - 'if (!current() || controller.signal.aborted) return;', - 'destination?.cancelled', - 'finally', - 'this.onSettled?.(authority)', - ], - 'deferred search component', - ); - assert.match( - ui, - /begin: \(\) => this\._beginDeferredNavigation\('location'\)/, - ); - assert.match( - ui, - /beforeFly: \(generation\) => this\._reassertNavigationHandoff\(generation\)/, - ); - assert.match( - ui, - /isCurrent: \(generation\) =>\s*!this\._disposed && generation === this\._navigationGeneration/, - ); - assert.match( - ui, - /change\.type === 'settled'\)\s*this\._settleLocationSearchUi\(change\.generation, \{\s*keepQuery: state\.status === 'missing' \|\| state\.status === 'failed',?\s*\}\)/, - ); - assert.doesNotMatch( - search.slice(0, search.indexOf('await this.search')), - /_releaseFollowCamera/, - ); + const search = fs.readFileSync(path.join(ROOT, 'src', 'ui', 'locationSearch.js'), 'utf8'); + ordered(search, [ + 'const authority = this.begin();', + 'this.onStart?.(authority);', + 'await this.search(query', + 'beforeFly: () => current() && this.beforeFly(authority)', + 'if (!current() || controller.signal.aborted) return;', + 'destination?.cancelled', + 'finally', + 'this.onSettled?.(authority)', + ], 'deferred search component'); + assert.match(ui, /begin: \(\) => this\._beginDeferredNavigation\('location'\)/); + assert.match(ui, /beforeFly: \(generation\) => this\._reassertNavigationHandoff\(generation\)/); + assert.match(ui, /isCurrent: \(generation\) =>\s*!this\._disposed && generation === this\._navigationGeneration/); + assert.match(ui, /change\.type === 'settled'\)\s*this\._settleLocationSearchUi\(change\.generation, \{\s*keepQuery: state\.status === 'missing' \|\| state\.status === 'failed',?\s*\}\)/); + assert.doesNotMatch(search.slice(0, search.indexOf('await this.search')), /_releaseFollowCamera/); }); test('a direct globe gesture retires delayed camera and selection restore only', () => { @@ -411,115 +287,77 @@ test('a direct globe gesture retires delayed camera and selection restore only', /_stampNavigation\(\{ cancelPendingSelection = true[^)]*\} = \{\}\) \{([\s\S]*?)\n \}/, 'navigation stamp', ); - assert.match( - stamp, - /if \(cancelPendingSelection\) \{[\s\S]*?cancelPendingTrackingRestore/, - ); + assert.match(stamp, /if \(cancelPendingSelection\) \{[\s\S]*?cancelPendingTrackingRestore/); assert.doesNotMatch(stamp, /cancelPendingRestores\(\)/); }); test('newer navigation, reset, Cockpit, and teardown share one generation', () => { assert.equal((ui.match(/_navigationGeneration \+= 1/g) || []).length, 1); const reset = body(ui, /resetToGlobeView\(\) \{([\s\S]*?)\n \}/, 'reset'); - ordered( - reset, - [ - 'if (this._globeResetPromise) return this._globeResetPromise;', - 'this._stampNavigation();', - "interruptCameraMotion('reset-globe')", - ], - 'reset supersession', - ); + ordered(reset, [ + 'if (this._globeResetPromise) return this._globeResetPromise;', + 'this._stampNavigation();', + "interruptCameraMotion('reset-globe')", + ], 'reset supersession'); const dispose = body(ui, /async dispose\(\) \{([\s\S]*?)\n \}/, 'dispose'); - ordered( - dispose, - [ - 'this._disposed = true;', - 'this._layerBindings.stop();', - 'this._navigation.destroy();', - 'await this._contextControls.restoreForDisposal();', - ], - 'dispose invalidation', - ); + ordered(dispose, [ + 'this._disposed = true;', + 'this._layerBindings.stop();', + 'this._navigation.destroy();', + 'await this._contextControls.restoreForDisposal();', + ], 'dispose invalidation'); assert.match(shellMethod('destroy').toString(), /this\._stampNavigation\(\)/); }); test('teardown synchronously closes immediate camera entry points', () => { const dispose = body(ui, /async dispose\(\) \{([\s\S]*?)\n \}/, 'dispose'); - ordered( - dispose, - [ - 'this._disposed = true;', - 'this._layerBindings.stop();', - 'await this._contextControls.restoreForDisposal();', - ], - 'synchronous teardown barrier', - ); - ordered( - LayerBindings.prototype.stop.toString(), - [ - 'this._disposed = true;', - 'this._removeCctvRequestFocusListener?.();', - 'this._removeWorldRequestFocusListener?.();', - 'this._navigationOwnerChangedRemover?.();', - ], - 'camera bindings close synchronously', - ); + ordered(dispose, [ + 'this._disposed = true;', + 'this._layerBindings.stop();', + 'await this._contextControls.restoreForDisposal();', + ], 'synchronous teardown barrier'); + ordered(LayerBindings.prototype.stop.toString(), [ + 'this._disposed = true;', + 'this._removeCctvRequestFocusListener?.();', + 'this._removeWorldRequestFocusListener?.();', + 'this._navigationOwnerChangedRemover?.();', + ], 'camera bindings close synchronously'); const navigation = body( ui, /_runExplicitNavigation\(noun, navigate, releaseOptions = undefined\) \{([\s\S]*?)\n \}/, 'explicit navigation', ); - ordered( - navigation, - [ - 'disposed: this._disposed', - 'release: () => this._releaseFollowCamera(releaseOptions)', - 'navigate,', - ], - 'disposed navigation guard', - ); + ordered(navigation, [ + 'disposed: this._disposed', + 'release: () => this._releaseFollowCamera(releaseOptions)', + 'navigate,', + ], 'disposed navigation guard'); const cctvFocus = body( ui, /_runExplicitCctvFocus\(activate, focus\) \{([\s\S]*?)\n \}/, 'explicit CCTV focus', ); - ordered( - cctvFocus, - ['if (this._disposed) return false;', 'const cameraId = activate();'], - 'disposed CCTV activation guard', - ); + ordered(cctvFocus, [ + 'if (this._disposed) return false;', + 'const cameraId = activate();', + ], 'disposed CCTV activation guard'); }); test('teardown refuses deferred location work before geocoding begins', () => { - const deferred = body( - ui, - /_beginDeferredNavigation\(noun = 'location', \{ cancelPendingSelection = true \} = \{\}\) \{([\s\S]*?)\n \}/, - 'deferred navigation', - ); + const deferred = body(ui, /_beginDeferredNavigation\(noun = 'location', \{ cancelPendingSelection = true \} = \{\}\) \{([\s\S]*?)\n \}/, 'deferred navigation'); assert.match(deferred, /disposed: this\._disposed/); - const search = fs.readFileSync( - path.join(ROOT, 'src', 'ui', 'locationSearch.js'), - 'utf8', - ); - ordered( - search, - [ - 'if (!query || this.destroyed) return;', - 'const authority = this.begin();', - 'if (authority === false)', - 'this.input?.blur();', - 'this.onStart?.(authority);', - 'await this.search(query', - ], - 'disposed search refusal', - ); - assert.match( - search, - /if \(authority === false\) \{[\s\S]*?return;[\s\S]*?\}\s*this\.controller/, - ); + const search = fs.readFileSync(path.join(ROOT, 'src', 'ui', 'locationSearch.js'), 'utf8'); + ordered(search, [ + 'if (!query || this.destroyed) return;', + 'const authority = this.begin();', + 'if (authority === false)', + 'this.input?.blur();', + 'this.onStart?.(authority);', + 'await this.search(query', + ], 'disposed search refusal'); + assert.match(search, /if \(authority === false\) \{[\s\S]*?return;[\s\S]*?\}\s*this\.controller/); }); test('refused canned destinations commit no location or POI state', () => { @@ -528,15 +366,11 @@ test('refused canned destinations commit no location or POI state', () => { ['poi', /_onPoiClick\(cityId, poiIndex\) \{([\s\S]*?)\n \}/], ]) { const handler = body(ui, pattern, name); - ordered( - handler, - [ - 'this._flyWithTransition(', - 'if (result === false) return;', - 'this._setActiveLocation(cityId);', - ], - name, - ); + ordered(handler, [ + 'this._flyWithTransition(', + 'if (result === false) return;', + 'this._setActiveLocation(cityId);', + ], name); } }); @@ -558,15 +392,11 @@ test('vessel and fire layers announce valid clicks and never fly cameras', () => /inputFor\(viewer\)\.onClick\(\(event\) => \{([\s\S]*?)\n \}\);/, 'vessel click', ); - ordered( - vesselClick, - [ - "isOwnedByOtherLayer('ais-live-vessels', pickedId)", - '_vesselOverlayHost.hitTest?.(', - 'selectAndFocusVessel(record)', - ], - 'vessel sibling ownership', - ); + ordered(vesselClick, [ + "isOwnedByOtherLayer('ais-live-vessels', pickedId)", + '_vesselOverlayHost.hitTest?.(', + 'selectAndFocusVessel(record)', + ], 'vessel sibling ownership'); const vesselFocus = body( vessels, /function selectAndFocusVessel\(record\) \{([\s\S]*?)\n \}/, @@ -578,13 +408,9 @@ test('vessel and fire layers announce valid clicks and never fly cameras', () => /input\.onClick\(\(event\) => \{([\s\S]*?)\n \}\);/, 'fire click', ); - ordered( - fireClick, - [ - 'isOwnedByOtherLayer(id, pickedId)', - 'overlayHost.hitTest?.(', - 'selectAndFocusFire(carded)', - ], - 'fire sibling ownership', - ); + ordered(fireClick, [ + 'isOwnedByOtherLayer(id, pickedId)', + 'overlayHost.hitTest?.(', + 'selectAndFocusFire(carded)', + ], 'fire sibling ownership'); }); diff --git a/src/data/militaryInstallations.test.mjs b/src/data/militaryInstallations.test.mjs index bd914a03c..9a37b2eed 100644 --- a/src/data/militaryInstallations.test.mjs +++ b/src/data/militaryInstallations.test.mjs @@ -25,26 +25,12 @@ import { installRenderGovernor, } from '../renderGovernor.js'; import * as Cesium from 'cesium'; -import { - registerEntityContext, - selectEntityContext, - getSelectedEntityContext, - clearSelectedEntityContextForLayer, -} from './contextStore.js'; +import { registerEntityContext, selectEntityContext, getSelectedEntityContext, clearSelectedEntityContextForLayer } from './contextStore.js'; import { readEntityRecord } from './entityRecords.js'; test('clicking a selected installation again or empty map clears it through refresh', async () => { - const run = await runInstallationLoad({ - elements: [ - { - type: 'node', - id: 42, - lat: 30.2, - lon: -97.7, - tags: { military: 'base', name: 'Site' }, - }, - ], - }); + const run = await runInstallationLoad({ elements: [{ type: 'node', id: 42, + lat: 30.2, lon: -97.7, tags: { military: 'base', name: 'Site' } }] }); try { for (const nextPick of ['osm:node:42', null]) { run.click('osm:node:42'); @@ -52,136 +38,66 @@ test('clicking a selected installation again or empty map clears it through refr run.click(nextPick); assert.equal(getSelectedEntityContext(), null); await militaryInstallationsLayer.update(); - assert.equal( - getSelectedEntityContext(), - null, - 'refresh must not resurrect selection', - ); + assert.equal(getSelectedEntityContext(), null, 'refresh must not resurrect selection'); assert.equal(run.entities()[0].point.pixelSize.getValue(), 9); } - } finally { - run.restore(); - } + } finally { run.restore(); } }); test('background installation paints neither reopen Contacts nor resurrect a cleared selection', async () => { - const run = await runInstallationLoad({ - elements: [ - { - type: 'node', - id: 42, - lat: 30.2, - lon: -97.7, - tags: { military: 'base', name: 'Site' }, - }, - ], - }); + const run = await runInstallationLoad({ elements: [{ type:'node',id:42,lat:30.2,lon:-97.7,tags:{military:'base',name:'Site'} }] }); try { - const selected = () => run.contextLabels().length; + const selected=()=>run.contextLabels().length; run.click('osm:node:42'); - assert.equal(selected(), 1); + assert.equal(selected(),1); await militaryInstallationsLayer.update(); - assert.equal(getSelectedEntityContext()?.id, 'osm:node:42'); - assert.equal( - selected(), - 1, - 'data and floor refreshes must not publish a fresh selection', - ); + assert.equal(getSelectedEntityContext()?.id,'osm:node:42'); + assert.equal(selected(),1,'data and floor refreshes must not publish a fresh selection'); clearSelectedEntityContextForLayer('military-installations'); await militaryInstallationsLayer.update(); - assert.equal(getSelectedEntityContext(), null); - assert.equal( - selected(), - 1, - 'an external clear must survive the next paint', - ); - assert.equal(run.entities()[0].point.pixelSize.getValue(), 9); - } finally { - run.restore(); - } + assert.equal(getSelectedEntityContext(),null); + assert.equal(selected(),1,'an external clear must survive the next paint'); + assert.equal(run.entities()[0].point.pixelSize.getValue(),9); + } finally { run.restore(); } }); test('clearing a stale installation highlight does not clear or reclaim another layer selection', async () => { - const run = await runInstallationLoad({ - elements: [ - { - type: 'node', - id: 42, - lat: 30.2, - lon: -97.7, - tags: { military: 'base', name: 'Site' }, - }, - ], - }); + const run = await runInstallationLoad({ elements: [{ type: 'node', id: 42, + lat: 30.2, lon: -97.7, tags: { military: 'base', name: 'Site' } }] }); try { run.click('osm:node:42'); const aircraft = { id: 'aircraft:test' }; - registerEntityContext(aircraft, { - id: aircraft.id, - layerId: 'military', - label: 'Aircraft', - }); + registerEntityContext(aircraft, { id: aircraft.id, layerId: 'military', label: 'Aircraft' }); selectEntityContext(aircraft); await militaryInstallationsLayer.update(); - assert.equal( - getSelectedEntityContext()?.id, - aircraft.id, - 'non-canvas selection survives a repaint', - ); + assert.equal(getSelectedEntityContext()?.id, aircraft.id, 'non-canvas selection survives a repaint'); run.click('osm:node:42'); selectEntityContext(aircraft); run.click(aircraft); assert.equal(getSelectedEntityContext()?.id, aircraft.id); await militaryInstallationsLayer.update(); assert.equal(getSelectedEntityContext()?.id, aircraft.id); - } finally { - run.restore(); - } + } finally { run.restore(); } }); test('switching sites keeps the new selection through refresh and disable clears it', async () => { - const run = await runInstallationLoad({ - elements: [42, 43].map((id) => ({ - type: 'node', - id, - lat: 30.2, - lon: -97.7, - tags: { military: 'base', name: `Site ${id}` }, - })), - }); + const run = await runInstallationLoad({ elements: [42, 43].map(id => ({ type: 'node', id, + lat: 30.2, lon: -97.7, tags: { military: 'base', name: `Site ${id}` } })) }); try { run.click('osm:node:42'); run.click('osm:node:43'); assert.equal(getSelectedEntityContext()?.id, 'osm:node:43'); await militaryInstallationsLayer.update(); assert.equal(getSelectedEntityContext()?.id, 'osm:node:43'); - assert.equal( - run - .entities() - .find((e) => e.id === 'osm:node:42') - .point.pixelSize.getValue(), - 9, - ); - assert.equal( - run - .entities() - .find((e) => e.id === 'osm:node:43') - .point.pixelSize.getValue(), - 13, - ); + assert.equal(run.entities().find(e => e.id === 'osm:node:42').point.pixelSize.getValue(), 9); + assert.equal(run.entities().find(e => e.id === 'osm:node:43').point.pixelSize.getValue(), 13); militaryInstallationsLayer.disable(); run.click('osm:node:42'); - assert.equal( - getSelectedEntityContext(), - null, - 'disabled layer ignores clicks', - ); + assert.equal(getSelectedEntityContext(), null, 'disabled layer ignores clicks'); militaryInstallationsLayer.enable(); await militaryInstallationsLayer.update(); assert.equal(getSelectedEntityContext(), null); - } finally { - run.restore(); - } + } finally { run.restore(); } }); test('cheap installation distance prefilter is local and antimeridian-safe', () => { @@ -197,50 +113,22 @@ test('cheap installation distance prefilter is local and antimeridian-safe', () }); test('keeps generic Places hits distinct from explicitly typed military facilities', () => { - assert.equal( - classifyGoogleMilitaryPlace({ primaryType: 'military_base' }), - 'military_land', - ); - assert.equal( - classifyGoogleMilitaryPlace({ - types: ['point_of_interest', 'military_base'], - }), - 'military_land', - ); - assert.equal( - classifyGoogleMilitaryPlace({ - name: 'Army Recruiting Office', - types: ['government_office'], - }), - 'places_candidate', - ); - assert.equal( - classifyGoogleMilitaryPlace({ name: 'Military Museum', types: ['museum'] }), - 'places_candidate', - ); + assert.equal(classifyGoogleMilitaryPlace({ primaryType: 'military_base' }), 'military_land'); + assert.equal(classifyGoogleMilitaryPlace({ types: ['point_of_interest', 'military_base'] }), 'military_land'); + assert.equal(classifyGoogleMilitaryPlace({ name: 'Army Recruiting Office', types: ['government_office'] }), 'places_candidate'); + assert.equal(classifyGoogleMilitaryPlace({ name: 'Military Museum', types: ['museum'] }), 'places_candidate'); }); test('reports the record source instead of attributing Places records to OpenStreetMap', () => { - assert.equal( - installationSourceLabel({ sources: [{ name: 'Google Maps Places' }] }), - 'Google Maps Places', - ); - assert.equal( - installationSourceLabel({ - sources: [{ name: 'OpenStreetMap' }, { name: 'OpenStreetMap' }], - }), - 'OpenStreetMap', - ); + assert.equal(installationSourceLabel({ sources: [{ name: 'Google Maps Places' }] }), 'Google Maps Places'); + assert.equal(installationSourceLabel({ sources: [{ name: 'OpenStreetMap' }, { name: 'OpenStreetMap' }] }), 'OpenStreetMap'); }); test('places installation anchors on the shared cached rendered floor', () => { setMeshFloorPreferred(true); _clearMeshFloorCellsForTest(); reportMeshFloorCell(30.2, -97.7, 182.25); - assert.equal( - installationSurfaceHeightM({ latitude: 30.2, longitude: -97.7 }), - 183.75, - ); + assert.equal(installationSurfaceHeightM({ latitude: 30.2, longitude: -97.7 }), 183.75); _clearMeshFloorCellsForTest(); }); @@ -259,11 +147,7 @@ test('real enabled installation entities carry no native label graphics', async globalThis.window = { dispatchEvent() {} }; globalThis.fetch = async (url) => { if (String(url).includes('/api/terrain/heights')) { - return { - ok: true, - status: 200, - json: async () => ({ results: [{ ellipsoid: 100 }] }), - }; + return { ok: true, status: 200, json: async () => ({ results: [{ ellipsoid: 100 }] }) }; } return { ok: true, @@ -271,15 +155,13 @@ test('real enabled installation entities carry no native label graphics', async json: async () => ({ status: 'fresh', retrievedAt: '2026-08-02T00:00:00.000Z', - elements: [ - { - type: 'node', - id: 42, - lat: 30.2, - lon: -97.7, - tags: { military: 'base', name: 'Runtime Installation' }, - }, - ], + elements: [{ + type: 'node', + id: 42, + lat: 30.2, + lon: -97.7, + tags: { military: 'base', name: 'Runtime Installation' }, + }], }), }; }; @@ -305,15 +187,10 @@ test('real enabled installation entities carry no native label graphics', async scene: { canvas, globe: { ellipsoid: Cesium.Ellipsoid.WGS84 }, - pick() { - return null; - }, + pick() { return null; }, }, dataSources: { - add(dataSource) { - dataSources.push(dataSource); - return dataSource; - }, + add(dataSource) { dataSources.push(dataSource); return dataSource; }, remove(dataSource) { const index = dataSources.indexOf(dataSource); if (index >= 0) dataSources.splice(index, 1); @@ -328,10 +205,7 @@ test('real enabled installation entities carry no native label graphics', async militaryInstallationsLayer.enable(); await militaryInstallationsLayer.update(); const entities = dataSources[0].entities.values; - assert.ok( - entities.length > 0, - 'runtime guard requires rendered installation records', - ); + assert.ok(entities.length > 0, 'runtime guard requires rendered installation records'); assert.ok(entities.every((entity) => entity.label === undefined)); } finally { militaryInstallationsLayer.destroy(viewer); @@ -376,9 +250,7 @@ test('Context focus past the render cap selects for real instead of flying blind assert.equal(focused, true, 'focus succeeds'); // The proof: a real entity now backs the selection, so the Context subject // actually changes rather than the camera moving over a stale subject. - const nowRendered = run - .entities() - .find((entity) => entity.id === beyondCap.id); + const nowRendered = run.entities().find((entity) => entity.id === beyondCap.id); assert.ok(nowRendered, 'the focused record was rendered on demand'); assert.equal( run.contextLabels().at(-1), @@ -414,12 +286,7 @@ async function runInstallationLoad({ dispatchEvent(event) { if (event?.detail?.label) contextEvents.push(event.detail.label); }, - CustomEvent: class { - constructor(type, init) { - this.type = type; - Object.assign(this, init); - } - }, + CustomEvent: class { constructor(type, init) { this.type = type; Object.assign(this, init); } }, }; globalThis.fetch = async (url) => { const href = String(url); @@ -428,11 +295,7 @@ async function runInstallationLoad({ } requests.push(href); if (failWith) { - return { - ok: false, - status: 503, - json: async () => ({ error: failWith }), - }; + return { ok: false, status: 503, json: async () => ({ error: failWith }) }; } const exact = href.includes('exact=1'); const payload = { @@ -451,14 +314,8 @@ async function runInstallationLoad({ let clickAction; const viewer = { camera: { - moveEnd: { - addEventListener() { - return () => {}; - }, - }, - flyToBoundingSphere(sphere, options) { - cameraFlights.push({ sphere, options }); - }, + moveEnd: { addEventListener() { return () => {}; } }, + flyToBoundingSphere(sphere, options) { cameraFlights.push({ sphere, options }); }, computeViewRectangle() { return { south: Cesium.Math.toRadians(VIEWPORT.south), @@ -471,9 +328,7 @@ async function runInstallationLoad({ scene: { canvas: { addEventListener() {}, removeEventListener() {} }, globe: { ellipsoid: Cesium.Ellipsoid.WGS84 }, - pick() { - return picked; - }, + pick() { return picked; }, // Enough surface for the real render governor to drive this viewer, so // one-shot render requests are observable. requestRenderMode: false, @@ -481,10 +336,7 @@ async function runInstallationLoad({ requestRender() {}, }, dataSources: { - add(dataSource) { - dataSources.push(dataSource); - return dataSource; - }, + add(dataSource) { dataSources.push(dataSource); return dataSource; }, remove(dataSource) { const index = dataSources.indexOf(dataSource); if (index >= 0) dataSources.splice(index, 1); @@ -494,27 +346,14 @@ async function runInstallationLoad({ }; bindFakeCesiumCamera(viewer); - const originalSetInputAction = - Cesium.ScreenSpaceEventHandler.prototype.setInputAction; - Cesium.ScreenSpaceEventHandler.prototype.setInputAction = function ( - action, - type, - modifier, - ) { + const originalSetInputAction = Cesium.ScreenSpaceEventHandler.prototype.setInputAction; + Cesium.ScreenSpaceEventHandler.prototype.setInputAction = function (action, type, modifier) { // The unmodified click: the input port binds one per modifier. - if ( - type === Cesium.ScreenSpaceEventType.LEFT_CLICK && - modifier === undefined - ) - clickAction = action; + if (type === Cesium.ScreenSpaceEventType.LEFT_CLICK && modifier === undefined) clickAction = action; return originalSetInputAction.call(this, action, type, modifier); }; - try { - militaryInstallationsLayer.init(viewer); - } finally { - Cesium.ScreenSpaceEventHandler.prototype.setInputAction = - originalSetInputAction; - } + try { militaryInstallationsLayer.init(viewer); } + finally { Cesium.ScreenSpaceEventHandler.prototype.setInputAction = originalSetInputAction; } installRenderGovernor(framePortFor(viewer.scene)); militaryInstallationsLayer.enable(); await militaryInstallationsLayer.update(); @@ -526,15 +365,11 @@ async function runInstallationLoad({ contextLabels: () => contextEvents, stats: () => militaryInstallationsLayer.getStats(), click(target) { - const entity = - typeof target === 'string' - ? dataSources[0].entities.getById(target) - : target; + const entity = typeof target === 'string' ? dataSources[0].entities.getById(target) : target; picked = entity ? { id: entity } : undefined; clickAction({ position: { x: 0, y: 0 } }); }, - renderRequests: () => - getRenderGovernorDiagnostics().recentRequests.map((item) => item.reason), + renderRequests: () => getRenderGovernorDiagnostics().recentRequests.map((item) => item.reason), restore() { militaryInstallationsLayer.destroy(viewer); _resetRenderGovernorForTest(); @@ -548,18 +383,8 @@ async function runInstallationLoad({ } test('viewport membership keeps intersecting footprints and drops the snap ring', () => { - const inside = { - osmType: 'node', - latitude: 30.5, - longitude: -97.5, - footprint: null, - }; - const outside = { - osmType: 'node', - latitude: 30.5, - longitude: -96.5, - footprint: null, - }; + const inside = { osmType: 'node', latitude: 30.5, longitude: -97.5, footprint: null }; + const outside = { osmType: 'node', latitude: 30.5, longitude: -96.5, footprint: null }; assert.equal(installationWithinViewport(inside, VIEWPORT), true); assert.equal(installationWithinViewport(outside, VIEWPORT), false); @@ -569,12 +394,7 @@ test('viewport membership keeps intersecting footprints and drops the snap ring' osmType: 'way', latitude: 30.5, longitude: -96.95, - footprint: [ - [-97.05, 30.4], - [-96.9, 30.4], - [-96.9, 30.6], - [-97.05, 30.6], - ], + footprint: [[-97.05, 30.4], [-96.9, 30.4], [-96.9, 30.6], [-97.05, 30.6]], }; assert.equal(installationWithinViewport(straddling, VIEWPORT), true); @@ -582,12 +402,7 @@ test('viewport membership keeps intersecting footprints and drops the snap ring' osmType: 'way', latitude: 30.5, longitude: -96.5, - footprint: [ - [-96.6, 30.4], - [-96.4, 30.4], - [-96.4, 30.6], - [-96.6, 30.6], - ], + footprint: [[-96.6, 30.4], [-96.4, 30.4], [-96.4, 30.6], [-96.6, 30.6]], }; assert.equal(installationWithinViewport(farWithFootprint, VIEWPORT), false); assert.equal(installationWithinViewport(null, VIEWPORT), false); @@ -599,36 +414,17 @@ test('extended features with unknown extent are kept, not centre-tested', () => // are normalized without one, so their true extent is unknown here. Overpass // already proved they intersect the queried bbox; centre-testing them would // erase exactly the biggest installations. - const relation = { - osmType: 'relation', - latitude: 30.5, - longitude: -96.99, - footprint: null, - }; - const hugeWay = { - osmType: 'way', - latitude: 29.5, - longitude: -97.5, - footprint: null, - }; + const relation = { osmType: 'relation', latitude: 30.5, longitude: -96.99, footprint: null }; + const hugeWay = { osmType: 'way', latitude: 29.5, longitude: -97.5, footprint: null }; assert.equal(installationWithinViewport(relation, VIEWPORT), true); assert.equal(installationWithinViewport(hugeWay, VIEWPORT), true); // A node IS its geometry, so excluding it on centre loses nothing. assert.equal( - installationWithinViewport( - { osmType: 'node', latitude: 30.5, longitude: -96.99, footprint: null }, - VIEWPORT, - ), + installationWithinViewport({ osmType: 'node', latitude: 30.5, longitude: -96.99, footprint: null }, VIEWPORT), false, ); // Unknown provenance is treated inclusively, same as an extended feature. - assert.equal( - installationWithinViewport( - { latitude: 30.5, longitude: -96.99, footprint: null }, - VIEWPORT, - ), - true, - ); + assert.equal(installationWithinViewport({ latitude: 30.5, longitude: -96.99, footprint: null }, VIEWPORT), true); }); test('a footprint-less relation just outside the viewport still renders', async () => { @@ -637,27 +433,14 @@ test('a footprint-less relation just outside the viewport still renders', async // A relation whose CENTER sits outside the viewport and whose geometry // lives on members Overpass did not inline. It was returned because it // intersects the queried bbox, so it must survive the viewport filter. - { - type: 'relation', - id: 91, - center: { lat: 30.5, lon: -96.995 }, - tags: { military: 'range', name: 'Straddling Range' }, - }, + { type: 'relation', id: 91, center: { lat: 30.5, lon: -96.995 }, tags: { military: 'range', name: 'Straddling Range' } }, // A NODE at the same off-view spot has no extent and must still be cut. - { - type: 'node', - id: 92, - lat: 30.5, - lon: -96.995, - tags: { military: 'range', name: 'Off View Node' }, - }, + { type: 'node', id: 92, lat: 30.5, lon: -96.995, tags: { military: 'range', name: 'Off View Node' } }, ], }); try { assert.deepEqual( - harness - .entities() - .map((entity) => readEntityRecord(entity)?.labelModel?.title), + harness.entities().map((entity) => readEntityRecord(entity)?.labelModel?.title), ['Straddling Range'], ); } finally { @@ -666,75 +449,38 @@ test('a footprint-less relation just outside the viewport still renders', async }); test('a legacy cached response with no saturation flag still triggers the exact retry', () => { - const atCap = { - elements: new Array(700).fill({ type: 'node' }), - elementCap: 700, - }; + const atCap = { elements: new Array(700).fill({ type: 'node' }), elementCap: 700 }; + assert.equal(installationResponseSaturated(atCap), true, 'derived from the reported cap'); assert.equal( - installationResponseSaturated(atCap), - true, - 'derived from the reported cap', - ); - assert.equal( - installationResponseSaturated({ - elements: new Array(699).fill({ type: 'node' }), - elementCap: 700, - }), + installationResponseSaturated({ elements: new Array(699).fill({ type: 'node' }), elementCap: 700 }), false, ); // An explicit flag always wins over the derivation. - assert.equal( - installationResponseSaturated({ ...atCap, saturated: false }), - false, - ); - assert.equal( - installationResponseSaturated({ elements: [], saturated: true }), - true, - ); + assert.equal(installationResponseSaturated({ ...atCap, saturated: false }), false); + assert.equal(installationResponseSaturated({ elements: [], saturated: true }), true); // Nothing to derive from: do not invent saturation. - assert.equal( - installationResponseSaturated({ elements: new Array(700).fill({}) }), - false, - ); + assert.equal(installationResponseSaturated({ elements: new Array(700).fill({}) }), false); assert.equal(installationResponseSaturated(null), false); }); test('a legacy-shaped payload at the cap fires the exact-viewport retry end to end', async () => { const elements = []; for (let index = 0; index < 700; index += 1) { - elements.push({ - type: 'node', - id: 3000 + index, - lat: 30.5, - lon: -96.2, - tags: { military: 'range' }, - }); + elements.push({ type: 'node', id: 3000 + index, lat: 30.5, lon: -96.2, tags: { military: 'range' } }); } const harness = await runInstallationLoad({ elements, // Pre-fix cache shape: no `saturated` field at all, but at the cap. legacyPayload: true, exactElements: [ - { - type: 'node', - id: 8, - lat: 30.5, - lon: -97.5, - tags: { military: 'range', name: 'Rescued From Legacy' }, - }, + { type: 'node', id: 8, lat: 30.5, lon: -97.5, tags: { military: 'range', name: 'Rescued From Legacy' } }, ], }); try { - assert.equal( - harness.requests.length, - 2, - 'a legacy entry must not skip the retry', - ); + assert.equal(harness.requests.length, 2, 'a legacy entry must not skip the retry'); assert.equal(harness.requests[1].includes('exact=1'), true); assert.deepEqual( - harness - .entities() - .map((entity) => readEntityRecord(entity)?.labelModel?.title), + harness.entities().map((entity) => readEntityRecord(entity)?.labelModel?.title), ['Rescued From Legacy'], ); } finally { @@ -743,15 +489,11 @@ test('a legacy-shaped payload at the cap fires the exact-viewport retry end to e }); test('a failed load buys the frame its status change needs', async () => { - const harness = await runInstallationLoad({ - failWith: 'Installation feed HTTP 503', - }); + const harness = await runInstallationLoad({ failWith: 'Installation feed HTTP 503' }); try { assert.equal(harness.stats().status, 'unavailable'); assert.ok( - harness - .renderRequests() - .some((reason) => reason === 'installations-status'), + harness.renderRequests().some((reason) => reason === 'installations-status'), 'an idle governor would otherwise leave the last healthy readout on screen', ); } finally { @@ -764,32 +506,14 @@ test('off-viewport records from the snapped superset never render or enter conte // The snapped bbox reaches ~5.5 km beyond the viewport; this node sits a // full degree outside it. elements: [ - { - type: 'node', - id: 1, - lat: 30.5, - lon: -97.5, - tags: { military: 'range', name: 'In View' }, - }, - { - type: 'node', - id: 2, - lat: 30.5, - lon: -96.2, - tags: { military: 'range', name: 'Off View' }, - }, + { type: 'node', id: 1, lat: 30.5, lon: -97.5, tags: { military: 'range', name: 'In View' } }, + { type: 'node', id: 2, lat: 30.5, lon: -96.2, tags: { military: 'range', name: 'Off View' } }, ], }); try { - const titles = harness - .entities() - .map((entity) => readEntityRecord(entity)?.labelModel?.title); + const titles = harness.entities().map((entity) => readEntityRecord(entity)?.labelModel?.title); assert.deepEqual(titles, ['In View'], 'only the in-viewport site renders'); - assert.equal( - harness.contextLabels().includes('Off View'), - false, - 'and none enters context', - ); + assert.equal(harness.contextLabels().includes('Off View'), false, 'and none enters context'); assert.equal(harness.stats().count, 1); } finally { harness.restore(); @@ -801,47 +525,21 @@ test('a saturated snapped tile refetches the exact viewport before rendering', a for (let index = 0; index < 700; index += 1) { // A saturated snapped response full of OFF-viewport sites: the in-view ones // were crowded out upstream. - elements.push({ - type: 'node', - id: 1000 + index, - lat: 30.5, - lon: -96.2, - tags: { military: 'range' }, - }); + elements.push({ type: 'node', id: 1000 + index, lat: 30.5, lon: -96.2, tags: { military: 'range' } }); } const harness = await runInstallationLoad({ elements, saturated: true, exactElements: [ - { - type: 'node', - id: 7, - lat: 30.5, - lon: -97.5, - tags: { military: 'range', name: 'Rescued' }, - }, + { type: 'node', id: 7, lat: 30.5, lon: -97.5, tags: { military: 'range', name: 'Rescued' } }, ], }); try { - assert.equal( - harness.requests.length, - 2, - 'saturation triggers exactly one retry', - ); - assert.equal( - harness.requests[0].includes('exact=1'), - false, - 'first ask uses the shared snapped tile', - ); - assert.equal( - harness.requests[1].includes('exact=1'), - true, - 'retry opts out of the snap', - ); + assert.equal(harness.requests.length, 2, 'saturation triggers exactly one retry'); + assert.equal(harness.requests[0].includes('exact=1'), false, 'first ask uses the shared snapped tile'); + assert.equal(harness.requests[1].includes('exact=1'), true, 'retry opts out of the snap'); assert.deepEqual( - harness - .entities() - .map((entity) => readEntityRecord(entity)?.labelModel?.title), + harness.entities().map((entity) => readEntityRecord(entity)?.labelModel?.title), ['Rescued'], 'the in-viewport site is no longer starved by off-view ones', ); @@ -852,15 +550,7 @@ test('a saturated snapped tile refetches the exact viewport before rendering', a test('an unsaturated response never pays for a second upstream ask', async () => { const harness = await runInstallationLoad({ - elements: [ - { - type: 'node', - id: 3, - lat: 30.5, - lon: -97.5, - tags: { military: 'range' }, - }, - ], + elements: [{ type: 'node', id: 3, lat: 30.5, lon: -97.5, tags: { military: 'range' } }], }); try { assert.equal(harness.requests.length, 1); @@ -873,19 +563,9 @@ test('an unsaturated response never pays for a second upstream ask', async () => test('a still-saturated exact viewport is reported honestly instead of implied complete', async () => { const elements = []; for (let index = 0; index < 700; index += 1) { - elements.push({ - type: 'node', - id: 2000 + index, - lat: 30.5, - lon: -97.5, - tags: { military: 'range' }, - }); + elements.push({ type: 'node', id: 2000 + index, lat: 30.5, lon: -97.5, tags: { military: 'range' } }); } - const harness = await runInstallationLoad({ - elements, - saturated: true, - exactSaturated: true, - }); + const harness = await runInstallationLoad({ elements, saturated: true, exactSaturated: true }); try { assert.equal(harness.stats().saturated, true); assert.match(harness.stats().error, /Too many mapped sites/); @@ -914,15 +594,9 @@ test('a floor that lands after the render deadline lifts the dots off the ellips // The production failure: the bounded pre-render resolve gives up before // Re:Earth answers, so the first paint has no floor to stand on. if (terrainCalls === 1) { - await new Promise((resolve) => - setTimeout(resolve, FLOOR_RESOLVE_DEADLINE_MS + 200), - ); + await new Promise((resolve) => setTimeout(resolve, FLOOR_RESOLVE_DEADLINE_MS + 200)); } - return { - ok: true, - status: 200, - json: async () => ({ results: [{ ellipsoid: 2400 }] }), - }; + return { ok: true, status: 200, json: async () => ({ results: [{ ellipsoid: 2400 }] }) }; } return { ok: true, @@ -930,20 +604,14 @@ test('a floor that lands after the render deadline lifts the dots off the ellips json: async () => ({ status: 'fresh', retrievedAt: '2026-08-18T00:00:00.000Z', - elements: [ - { type: 'node', id: 77, lat, lon, tags: { military: 'range' } }, - ], + elements: [{ type: 'node', id: 77, lat, lon, tags: { military: 'range' } }], }), }; }; const dataSources = []; const viewer = { camera: { - moveEnd: { - addEventListener() { - return () => {}; - }, - }, + moveEnd: { addEventListener() { return () => {}; } }, computeViewRectangle() { return { south: Cesium.Math.toRadians(44), @@ -956,15 +624,10 @@ test('a floor that lands after the render deadline lifts the dots off the ellips scene: { canvas: { addEventListener() {}, removeEventListener() {} }, globe: { ellipsoid: Cesium.Ellipsoid.WGS84 }, - pick() { - return null; - }, + pick() { return null; }, }, dataSources: { - add(dataSource) { - dataSources.push(dataSource); - return dataSource; - }, + add(dataSource) { dataSources.push(dataSource); return dataSource; }, remove(dataSource) { const index = dataSources.indexOf(dataSource); if (index >= 0) dataSources.splice(index, 1); @@ -974,10 +637,9 @@ test('a floor that lands after the render deadline lifts the dots off the ellips }; bindFakeCesiumCamera(viewer); - const heightOf = (entity) => - Cesium.Cartographic.fromCartesian( - entity.position.getValue(Cesium.JulianDate.now()), - ).height; + const heightOf = (entity) => Cesium.Cartographic.fromCartesian( + entity.position.getValue(Cesium.JulianDate.now()), + ).height; try { militaryInstallationsLayer.init(viewer); @@ -986,24 +648,13 @@ test('a floor that lands after the render deadline lifts the dots off the ellips const buried = dataSources[0].entities.values[0]; assert.ok(buried, 'the cold-floor pass still renders the record'); - assert.ok( - Math.abs(heightOf(buried)) < 1, - 'a cold floor anchors at the ellipsoid, as before', - ); + assert.ok(Math.abs(heightOf(buried)) < 1, 'a cold floor anchors at the ellipsoid, as before'); // The warm chain resolves out of band; wait for the floor to land. - for ( - let attempt = 0; - attempt < 50 && cachedGroundFloor(lat, lon) == null; - attempt += 1 - ) { + for (let attempt = 0; attempt < 50 && cachedGroundFloor(lat, lon) == null; attempt += 1) { await new Promise((resolve) => setTimeout(resolve, 10)); } - assert.equal( - cachedGroundFloor(lat, lon), - 2400, - 'the late floor landed in the shared cache', - ); + assert.equal(cachedGroundFloor(lat, lon), 2400, 'the late floor landed in the shared cache'); const lifted = dataSources[0].entities.values[0]; assert.ok( @@ -1027,9 +678,7 @@ test('reports bounded installation requests as loading and clears on settlement' const originalWindow = globalThis.window; const originalFetch = globalThis.fetch; let resolveInstallations; - const installationsResponse = new Promise((resolve) => { - resolveInstallations = resolve; - }); + const installationsResponse = new Promise((resolve) => { resolveInstallations = resolve; }); globalThis.document = { addEventListener() {}, removeEventListener() {} }; globalThis.window = { dispatchEvent() {} }; globalThis.fetch = async (url) => { @@ -1040,11 +689,7 @@ test('reports bounded installation requests as loading and clears on settlement' }; const viewer = { camera: { - moveEnd: { - addEventListener() { - return () => {}; - }, - }, + moveEnd: { addEventListener() { return () => {}; } }, computeViewRectangle() { return { south: Cesium.Math.toRadians(30), @@ -1057,18 +702,9 @@ test('reports bounded installation requests as loading and clears on settlement' scene: { canvas: { addEventListener() {}, removeEventListener() {} }, globe: { ellipsoid: Cesium.Ellipsoid.WGS84 }, - pick() { - return null; - }, - }, - dataSources: { - add(value) { - return value; - }, - remove() { - return true; - }, + pick() { return null; }, }, + dataSources: { add(value) { return value; }, remove() { return true; } }, }; bindFakeCesiumCamera(viewer); @@ -1109,50 +745,31 @@ test('zoom-out aborts an active installation request and returns non-loading gui globalThis.fetch = async (_url, options = {}) => { observedSignal = options.signal; return new Promise((_resolve, reject) => { - options.signal?.addEventListener( - 'abort', - () => { - const error = new Error('aborted'); - error.name = 'AbortError'; - reject(error); - }, - { once: true }, - ); + options.signal?.addEventListener('abort', () => { + const error = new Error('aborted'); + error.name = 'AbortError'; + reject(error); + }, { once: true }); }); }; const viewer = { camera: { - moveEnd: { - addEventListener() { - return () => {}; - }, - }, + moveEnd: { addEventListener() { return () => {}; } }, computeViewRectangle() { - return globalView - ? null - : { - south: Cesium.Math.toRadians(30), - west: Cesium.Math.toRadians(-98), - north: Cesium.Math.toRadians(31), - east: Cesium.Math.toRadians(-97), - }; + return globalView ? null : { + south: Cesium.Math.toRadians(30), + west: Cesium.Math.toRadians(-98), + north: Cesium.Math.toRadians(31), + east: Cesium.Math.toRadians(-97), + }; }, }, scene: { canvas: { addEventListener() {}, removeEventListener() {} }, globe: { ellipsoid: Cesium.Ellipsoid.WGS84 }, - pick() { - return null; - }, - }, - dataSources: { - add(value) { - return value; - }, - remove() { - return true; - }, + pick() { return null; }, }, + dataSources: { add(value) { return value; }, remove() { return true; } }, }; bindFakeCesiumCamera(viewer); @@ -1167,15 +784,8 @@ test('zoom-out aborts an active installation request and returns non-loading gui assert.equal(observedSignal.aborted, true); assert.equal(militaryInstallationsLayer.getStats().loading, false); assert.equal(militaryInstallationsLayer.getStats().status, 'zoom-in'); - assert.equal( - militaryInstallationsLayer.getStats().error, - null, - 'guidance is not a fault', - ); - assert.match( - militaryInstallationsLayer.getStats().statusMessage, - /zoom in/i, - ); + assert.equal(militaryInstallationsLayer.getStats().error, null, 'guidance is not a fault'); + assert.match(militaryInstallationsLayer.getStats().statusMessage, /zoom in/i); } finally { militaryInstallationsLayer.destroy(viewer); globalThis.fetch = originalFetch; @@ -1199,194 +809,91 @@ import { installationRetryDelayMs } from './militaryInstallations.js'; import { framePortFor } from '../testSupport/fakeFramePort.mjs'; const installationsSource = readLayerSource( - new URL('./militaryInstallations.js', import.meta.url), - 'utf8', -); + new URL('./militaryInstallations.js', import.meta.url), 'utf8'); test('the unavailable retry backs off 30s to a 240s ceiling and restarts clean', () => { - assert.equal( - installationRetryDelayMs(0), - 30000, - 'first failure retries in 30s', - ); - assert.equal( - installationRetryDelayMs(undefined), - 30000, - 'no prior delay means the minimum', - ); + assert.equal(installationRetryDelayMs(0), 30000, 'first failure retries in 30s'); + assert.equal(installationRetryDelayMs(undefined), 30000, 'no prior delay means the minimum'); assert.equal(installationRetryDelayMs(30000), 60000, 'each failure doubles'); assert.equal(installationRetryDelayMs(60000), 120000); - assert.equal( - installationRetryDelayMs(120000), - 240000, - 'the ceiling is four minutes', - ); + assert.equal(installationRetryDelayMs(120000), 240000, 'the ceiling is four minutes'); assert.equal(installationRetryDelayMs(240000), 240000, 'and it stays there'); - assert.equal( - installationRetryDelayMs(-5), - 30000, - 'garbage restarts at the minimum', - ); + assert.equal(installationRetryDelayMs(-5), 30000, 'garbage restarts at the minimum'); }); test('the retry is wired to every lifecycle edge, not just declared', () => { - assert.match( - installationsSource, + assert.match(installationsSource, /setInstallationStatus\(\s*'unavailable',[^]*?\);\n\s*if \(!isUnavailableCapability\(error\)\)\s*parts\.viewport\.scheduleUnavailableRetry\(error\?\.retryAfterMs\);/, - 'a failed load schedules the retry immediately after reporting unavailable', - ); - assert.match( - installationsSource, + 'a failed load schedules the retry immediately after reporting unavailable'); + assert.match(installationsSource, /clearUnavailableRetry\(\);\n\s*setInstallationStatus\(\n?\s*layerState\.records\.length/, - 'a successful load clears the pending retry and resets the backoff', - ); - assert.match( - installationsSource, + 'a successful load clears the pending retry and resets the backoff'); + assert.match(installationsSource, /clearUnavailableRetry\(\);\n\s*setInstallationStatus\('zoom-in'/, - 'zooming out of range cancels the retry — moveEnd owns re-entry there', - ); - assert.match( - installationsSource, - /disable\(\) \{[^]*?clearUnavailableRetry\(\);/, - 'disabling the layer cancels the retry', - ); - assert.match( - installationsSource, + 'zooming out of range cancels the retry — moveEnd owns re-entry there'); + assert.match(installationsSource, /disable\(\) \{[^]*?clearUnavailableRetry\(\);/, + 'disabling the layer cancels the retry'); + assert.match(installationsSource, /function scheduleLoad\(\) \{[^]*?clearUnavailableRetry\(\{ resetBackoff: false \}\)/, - 'a user-driven load supersedes the retry without resetting the backoff step', - ); - assert.match( - installationsSource, + 'a user-driven load supersedes the retry without resetting the backoff step'); + assert.match(installationsSource, /layerState\.enabled && !layerState\.loading\)\s*parts\.ingestion\.loadInstallations\(\)/, - 'the fired retry re-checks enablement and never races an in-flight load', - ); + 'the fired retry re-checks enablement and never races an in-flight load'); }); + test('a merged installation stays visible when only its second fragment intersects', () => { - const far = [ - [-96.6, 30.4], - [-96.4, 30.4], - [-96.4, 30.6], - [-96.6, 30.6], - ]; - const near = [ - [-97.5, 30.4], - [-97.4, 30.4], - [-97.4, 30.6], - [-97.5, 30.6], - ]; - assert.equal( - installationWithinViewport( - { - latitude: 30.5, - longitude: -96.5, - footprint: far, - footprints: [[far], [near]], - }, - VIEWPORT, - ), - true, - ); + const far = [[-96.6,30.4],[-96.4,30.4],[-96.4,30.6],[-96.6,30.6]]; + const near = [[-97.5,30.4],[-97.4,30.4],[-97.4,30.6],[-97.5,30.6]]; + assert.equal(installationWithinViewport({latitude:30.5,longitude:-96.5,footprint:far,footprints:[[far],[near]]},VIEWPORT), true); }); test('a Contacts anchor replaces the viewport and ignores small subject moves', async () => { // Field report: a follow/Cockpit camera never fires moveEnd and often looks at // the horizon, so the Context row read "?" for the whole track. - const run = await runInstallationLoad({ - elements: [ - { - type: 'node', - id: 7, - lat: 30.3125, - lon: -97.765, - tags: { military: 'base', name: 'Camp Mabry' }, - }, - ], - }); + const run = await runInstallationLoad({ elements: [{ type: 'node', id: 7, + lat: 30.3125, lon: -97.765, tags: { military: 'base', name: 'Camp Mabry' } }] }); try { const box = (href) => { const url = new URL(href, 'https://example.test'); - return Object.fromEntries( - ['south', 'west', 'north', 'east'].map((key) => [ - key, - Number(url.searchParams.get(key)), - ]), - ); + return Object.fromEntries(['south', 'west', 'north', 'east'] + .map((key) => [key, Number(url.searchParams.get(key))])); }; const centre = (href) => (box(href).south + box(href).north) / 2; assert.equal(run.stats().coverageLabel, 'CURRENT VIEWPORT ONLY'); - assert.equal( - militaryInstallationsLayer.setContextAnchor({ - latitude: 30.3125, - longitude: -97.765, - }), - true, - ); + assert.equal(militaryInstallationsLayer.setContextAnchor({ latitude: 30.3125, longitude: -97.765 }), true); await new Promise((resolve) => setTimeout(resolve, 650)); const anchored = box(run.requests.at(-1)); assert.ok(Math.abs(centre(run.requests.at(-1)) - 30.3125) < 0.01); - assert.ok( - anchored.north - anchored.south > 1.5, - 'window reaches ~100 km each way', - ); + assert.ok(anchored.north - anchored.south > 1.5, 'window reaches ~100 km each way'); assert.equal(run.stats().coverageLabel, 'WITHIN 100 KM'); assert.equal(run.stats().coverage.kind, 'subject'); const count = run.requests.length; - assert.equal( - militaryInstallationsLayer.setContextAnchor({ - latitude: 30.35, - longitude: -97.765, - }), - false, - 'a 4 km move keeps the window', - ); - assert.equal( - militaryInstallationsLayer.setContextAnchor({ - latitude: 30.6, - longitude: -97.765, - }), - true, - 'a 32 km move shifts the window', - ); + assert.equal(militaryInstallationsLayer.setContextAnchor({ latitude: 30.35, longitude: -97.765 }), false, + 'a 4 km move keeps the window'); + assert.equal(militaryInstallationsLayer.setContextAnchor({ latitude: 30.6, longitude: -97.765 }), true, + 'a 32 km move shifts the window'); await new Promise((resolve) => setTimeout(resolve, 650)); assert.equal(run.requests.length, count + 1); assert.ok(Math.abs(centre(run.requests.at(-1)) - 30.6) < 0.01); assert.equal(militaryInstallationsLayer.setContextAnchor(null), true); await new Promise((resolve) => setTimeout(resolve, 650)); - assert.deepEqual( - box(run.requests.at(-1)), - { south: 30, west: -98, north: 31, east: -97 }, - 'clearing the subject returns to the viewport', - ); + assert.deepEqual(box(run.requests.at(-1)), { south: 30, west: -98, north: 31, east: -97 }, + 'clearing the subject returns to the viewport'); assert.equal(run.stats().coverageLabel, 'CURRENT VIEWPORT ONLY'); - } finally { - run.restore(); - } + } finally { run.restore(); } }); test('unchanged installations keep their entities across reloads', async () => { - const run = await runInstallationLoad({ - elements: [ - { - type: 'node', - id: 42, - lat: 30.2, - lon: -97.7, - tags: { military: 'base', name: 'Site' }, - }, - ], - }); + const run = await runInstallationLoad({ elements: [{ type: 'node', id: 42, + lat: 30.2, lon: -97.7, tags: { military: 'base', name: 'Site' } }] }); try { const before = run.entities().slice(); assert.ok(before.length > 0); await militaryInstallationsLayer.update(); const after = run.entities(); assert.equal(after.length, before.length); - assert.ok( - after.every((entity, index) => entity === before[index]), - 'a reload with the same geometry must not recreate entities', - ); - } finally { - run.restore(); - } + assert.ok(after.every((entity, index) => entity === before[index]), + 'a reload with the same geometry must not recreate entities'); + } finally { run.restore(); } }); diff --git a/src/data/radio.test.mjs b/src/data/radio.test.mjs index cdf1d9f12..9bf345e0b 100644 --- a/src/data/radio.test.mjs +++ b/src/data/radio.test.mjs @@ -52,10 +52,7 @@ import { radioViewIsGlobal, stationMatchesRadioCategory, } from './radio.js'; -import { - radioStationRow, - withRadioLayer, -} from '../testSupport/radioLayerHarness.mjs'; +import { radioStationRow, withRadioLayer } from '../testSupport/radioLayerHarness.mjs'; import { withEntityRecord } from './entityRecords.js'; const stations = [ @@ -68,27 +65,12 @@ const stations = [ test('station-level tags produce canonical and detected-genre categories', () => { const categories = buildRadioCategories(stations); assert.equal(categories.find((category) => category.id === 'all').count, 4); - assert.equal( - categories.find((category) => category.id === 'weather').count, - 2, - ); - assert.equal( - categories.find((category) => category.id === 'public-safety').count, - 1, - ); - assert.equal( - categories.find((category) => category.id === 'genre:jazz').count, - 1, - ); + assert.equal(categories.find((category) => category.id === 'weather').count, 2); + assert.equal(categories.find((category) => category.id === 'public-safety').count, 1); + assert.equal(categories.find((category) => category.id === 'genre:jazz').count, 1); assert.equal(categories.find((category) => category.id === 'other').count, 1); - assert.equal( - categories.find((category) => category.id === 'music').color, - '#54d17a', - ); - assert.equal( - categories.find((category) => category.id === 'genre:jazz').color, - '#54d17a', - ); + assert.equal(categories.find((category) => category.id === 'music').color, '#54d17a'); + assert.equal(categories.find((category) => category.id === 'genre:jazz').color, '#54d17a'); }); test('every generated Radio category is accepted by durable preferences', () => { @@ -121,46 +103,26 @@ test('cluster badges lead with a readable count and concise dominant category', test('Radio globe labels are compact and frequency-first without misreading names', () => { assert.equal( - radioGlobeLabel({ - name: 'La Zeta (Hermosillo) - 93.9 FM - XHHY - Uniradio', - }), + radioGlobeLabel({ name: 'La Zeta (Hermosillo) - 93.9 FM - XHHY - Uniradio' }), '93.9 FM — La Zeta', ); assert.equal( - radioGlobeLabel({ - name: '100.3 The River - WQRV - Meridianville/Huntsville, AL', - }), + radioGlobeLabel({ name: '100.3 The River - WQRV - Meridianville/Huntsville, AL' }), '100.3 FM — The River', ); - assert.equal( - radioGlobeLabel({ name: "80's New Wave Radio" }), - "80's New Wave Radio", - ); - assert.equal( - radioGlobeLabel({ name: "100 GREATEST OF THE 80'S" }), - "100 GREATEST OF THE 80'S", - ); - assert.doesNotMatch( - radioGlobeLabel({ name: 'Movie Soundtracks Hits Radio @ 1.fm' }), - /^1 FM/, - ); + assert.equal(radioGlobeLabel({ name: "80's New Wave Radio" }), "80's New Wave Radio"); + assert.equal(radioGlobeLabel({ name: "100 GREATEST OF THE 80'S" }), "100 GREATEST OF THE 80'S"); + assert.doesNotMatch(radioGlobeLabel({ name: 'Movie Soundtracks Hits Radio @ 1.fm' }), /^1 FM/); assert.equal( radioGlobeLabel({ name: ' La Zeta - 93.9 fm - XHHY ' }), '93.9 FM — La Zeta', ); assert.equal(radioGlobeLabel({ name: '93.9 FM ---' }), '93.9 FM'); - assert.equal( - radioGlobeLabel({ name: 'Radio One 93.9 FM / 94.1 FM' }), - '93.9 FM — Radio One', - ); - const truncated = radioGlobeLabel({ - name: 'Christian Power Praise Dot Net Worldwide Service', - }); + assert.equal(radioGlobeLabel({ name: 'Radio One 93.9 FM / 94.1 FM' }), '93.9 FM — Radio One'); + const truncated = radioGlobeLabel({ name: 'Christian Power Praise Dot Net Worldwide Service' }); assert.equal(truncated.length, 30); assert.ok(truncated.endsWith('…')); - const unicodeTruncated = radioGlobeLabel({ - name: `${'a'.repeat(28)}📻 more`, - }); + const unicodeTruncated = radioGlobeLabel({ name: `${'a'.repeat(28)}📻 more` }); assert.equal(unicodeTruncated, `${'a'.repeat(28)}📻…`); assert.doesNotMatch(unicodeTruncated, /�/); }); @@ -176,45 +138,27 @@ test('Radio ranks and caps cluster candidates before shared-host entry construct for (let index = 1; index < selected.length; index += 1) { assert.ok(selected[index - 1].stationCount >= selected[index].stationCount); } - assert.deepEqual( - selectRadioClusterCandidates( - [ - { id: 'z', stationCount: 5 }, - { id: 'a', stationCount: 5 }, - ], - 1, - ).map(({ id }) => id), - ['a'], - ); + assert.deepEqual(selectRadioClusterCandidates([ + { id: 'z', stationCount: 5 }, + { id: 'a', stationCount: 5 }, + ], 1).map(({ id }) => id), ['a']); }); test('Radio preserves one-to-one cluster identity across substantial membership churn', () => { const previous = [ - { - id: 'stable:north', - identityId: 'stable:north', - stationIds: ['a', 'b', 'c', 'd'], - }, - { - id: 'stable:south', - identityId: 'stable:south', - stationIds: ['w', 'x', 'y', 'z'], - }, + { id: 'stable:north', identityId: 'stable:north', stationIds: ['a', 'b', 'c', 'd'] }, + { id: 'stable:south', identityId: 'stable:south', stationIds: ['w', 'x', 'y', 'z'] }, ]; const generated = []; - const reconciled = reconcileRadioClusterCandidates( - [ - { id: 'b:e:4', stationIds: ['a', 'b', 'c', 'e'], text: '4 NEWS' }, - { id: 'new:weak:3', stationIds: ['d', 'm', 'n'], text: '3 OTHER' }, - { id: 'x:q:4', stationIds: ['w', 'x', 'y', 'q'], text: '4 MUSIC' }, - ], - previous, - (candidate) => { - const id = `new:${candidate.id}`; - generated.push(id); - return id; - }, - ); + const reconciled = reconcileRadioClusterCandidates([ + { id: 'b:e:4', stationIds: ['a', 'b', 'c', 'e'], text: '4 NEWS' }, + { id: 'new:weak:3', stationIds: ['d', 'm', 'n'], text: '3 OTHER' }, + { id: 'x:q:4', stationIds: ['w', 'x', 'y', 'q'], text: '4 MUSIC' }, + ], previous, (candidate) => { + const id = `new:${candidate.id}`; + generated.push(id); + return id; + }); assert.equal(reconciled[0].id, 'stable:north'); assert.equal(reconciled[0].text, '4 NEWS'); assert.equal(reconciled[2].id, 'stable:south'); @@ -230,48 +174,31 @@ test('directory refresh retains only identities whose represented stations still { id: 'stable:invalid', stationIds: ['x', 'y', 'z'] }, ]; const unchangedRefresh = retainRadioClusterIdentitiesForStations(previous, [ - { id: 'c' }, - { id: 'a' }, - { id: 'b' }, - { id: 'x' }, - { id: 'y' }, - { id: 'z' }, + { id: 'c' }, { id: 'a' }, { id: 'b' }, { id: 'x' }, { id: 'y' }, { id: 'z' }, ]); assert.deepEqual(unchangedRefresh, previous); const changedRefresh = retainRadioClusterIdentitiesForStations(previous, [ - { id: 'a' }, - { id: 'b' }, - { id: 'c' }, - { id: 'x' }, - { id: 'z' }, + { id: 'a' }, { id: 'b' }, { id: 'c' }, { id: 'x' }, { id: 'z' }, ]); - assert.deepEqual( - changedRefresh.map(({ id }) => id), - ['stable:unchanged'], - ); + assert.deepEqual(changedRefresh.map(({ id }) => id), ['stable:unchanged']); }); test('Radio cluster identity inheritance is deterministic for splits and disjoint replacements', () => { - const previous = [ - { - id: 'stable:whole', - identityId: 'stable:whole', - stationIds: ['a', 'b', 'c', 'd', 'e', 'f'], - }, - ]; - const reconciled = reconcileRadioClusterCandidates( - [ - { id: 'a:c:3', stationIds: ['a', 'b', 'c'] }, - { id: 'd:f:3', stationIds: ['d', 'e', 'f'] }, - { id: 'x:z:3', stationIds: ['x', 'y', 'z'] }, - ], - previous, - (candidate) => `new:${candidate.id}`, - ); - assert.deepEqual( - reconciled.map(({ id }) => id), - ['stable:whole', 'new:d:f:3', 'new:x:z:3'], - ); + const previous = [{ + id: 'stable:whole', + identityId: 'stable:whole', + stationIds: ['a', 'b', 'c', 'd', 'e', 'f'], + }]; + const reconciled = reconcileRadioClusterCandidates([ + { id: 'a:c:3', stationIds: ['a', 'b', 'c'] }, + { id: 'd:f:3', stationIds: ['d', 'e', 'f'] }, + { id: 'x:z:3', stationIds: ['x', 'y', 'z'] }, + ], previous, (candidate) => `new:${candidate.id}`); + assert.deepEqual(reconciled.map(({ id }) => id), [ + 'stable:whole', + 'new:d:f:3', + 'new:x:z:3', + ]); assert.equal(new Set(reconciled.map(({ id }) => id)).size, reconciled.length); }); @@ -282,51 +209,38 @@ test('Radio allocates fresh disjoint cluster identities in canonical membership ]; const reconcile = (input) => { let sequence = 0; - return reconcileRadioClusterCandidates( - input, - [], - () => `stable:${++sequence}`, - ); + return reconcileRadioClusterCandidates(input, [], () => `stable:${++sequence}`); }; - const identities = (result) => - Object.fromEntries( - result.map(({ membershipId, identityId }) => [membershipId, identityId]), - ); + const identities = (result) => Object.fromEntries( + result.map(({ membershipId, identityId }) => [membershipId, identityId]), + ); assert.deepEqual(identities(reconcile(current)), { 'z-membership': 'stable:2', 'a-membership': 'stable:1', }); - assert.deepEqual( - identities(reconcile([...current].reverse())), - identities(reconcile(current)), - ); + assert.deepEqual(identities(reconcile([...current].reverse())), identities(reconcile(current))); }); test('Radio split competition never falls through from a consumed majority to a historical minority', () => { const previous = [ - { - id: 'stable:majority', - stationIds: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'], - }, + { id: 'stable:majority', stationIds: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'] }, { id: 'stable:minority', stationIds: ['x'] }, ]; const current = [ { id: 'strong-child', stationIds: ['a', 'b', 'c', 'd', 'e'] }, { id: 'later-child', stationIds: ['f', 'g', 'h', 'i', 'x'] }, ]; - const reconcile = (currentInput, previousInput) => - reconcileRadioClusterCandidates( - currentInput, - previousInput, - (candidate) => `new:${candidate.id}`, - ); + const reconcile = (currentInput, previousInput) => reconcileRadioClusterCandidates( + currentInput, + previousInput, + (candidate) => `new:${candidate.id}`, + ); const forward = reconcile(current, previous); const permuted = reconcile([...current].reverse(), [...previous].reverse()); - const identities = (result) => - Object.fromEntries( - result.map(({ membershipId, identityId }) => [membershipId, identityId]), - ); + const identities = (result) => Object.fromEntries( + result.map(({ membershipId, identityId }) => [membershipId, identityId]), + ); assert.deepEqual(identities(forward), { 'strong-child': 'stable:majority', @@ -337,39 +251,21 @@ test('Radio split competition never falls through from a consumed majority to a test('Radio split identities cannot migrate to weaker children to increase inherited count', () => { const previous = [ - { - id: 'stable:alpha', - stationIds: ['a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9'], - }, + { id: 'stable:alpha', stationIds: ['a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9'] }, { id: 'stable:beta', stationIds: ['b1', 'b2', 'b3', 'b4', 'b5'] }, ]; - const reconciled = reconcileRadioClusterCandidates( - [ - { - id: 'strong-tie', - stationIds: [ - 'a1', - 'a2', - 'a3', - 'a4', - 'a5', - 'b1', - 'b2', - 'b3', - 'b4', - 'b5', - ], - }, - { id: 'weaker-alpha-child', stationIds: ['a6', 'a7', 'a8', 'a9'] }, - ], - previous, - (candidate) => `new:${candidate.id}`, - ); + const reconciled = reconcileRadioClusterCandidates([ + { + id: 'strong-tie', + stationIds: ['a1', 'a2', 'a3', 'a4', 'a5', 'b1', 'b2', 'b3', 'b4', 'b5'], + }, + { id: 'weaker-alpha-child', stationIds: ['a6', 'a7', 'a8', 'a9'] }, + ], previous, (candidate) => `new:${candidate.id}`); - assert.deepEqual( - reconciled.map(({ id }) => id), - ['stable:beta', 'new:weaker-alpha-child'], - ); + assert.deepEqual(reconciled.map(({ id }) => id), [ + 'stable:beta', + 'new:weaker-alpha-child', + ]); }); test('Radio duplicate prior records cannot assign one stable identity twice', () => { @@ -381,29 +277,23 @@ test('Radio duplicate prior records cannot assign one stable identity twice', () { id: 'a-child', stationIds: ['a', 'b'] }, { id: 'z-child', stationIds: ['c', 'd'] }, ]; - const reconcile = (currentInput, previousInput) => - reconcileRadioClusterCandidates( - currentInput, - previousInput, - (candidate) => `new:${candidate.id}`, - ); + const reconcile = (currentInput, previousInput) => reconcileRadioClusterCandidates( + currentInput, + previousInput, + (candidate) => `new:${candidate.id}`, + ); const forward = reconcile(current, previous); const permuted = reconcile([...current].reverse(), [...previous].reverse()); - const identities = (result) => - Object.fromEntries( - result.map(({ membershipId, identityId }) => [membershipId, identityId]), - ); + const identities = (result) => Object.fromEntries( + result.map(({ membershipId, identityId }) => [membershipId, identityId]), + ); assert.deepEqual(identities(forward), { 'a-child': 'stable:duplicate', 'z-child': 'new:z-child', }); assert.deepEqual(identities(permuted), identities(forward)); - assert.equal( - forward.filter(({ identityId }) => identityId === 'stable:duplicate') - .length, - 1, - ); + assert.equal(forward.filter(({ identityId }) => identityId === 'stable:duplicate').length, 1); }); test('Radio cluster merges preserve the larger contributor over a fully retained minority', () => { @@ -411,43 +301,32 @@ test('Radio cluster merges preserve the larger contributor over a fully retained { id: 'stable:minority', stationIds: ['a', 'b'] }, { id: 'stable:majority', stationIds: ['c', 'd', 'e', 'f', 'g', 'h'] }, ]; - const [merged] = reconcileRadioClusterCandidates( - [{ id: 'merged', stationIds: ['a', 'b', 'c', 'd', 'e'] }], - previous, - () => 'new:merged', - ); + const [merged] = reconcileRadioClusterCandidates([ + { id: 'merged', stationIds: ['a', 'b', 'c', 'd', 'e'] }, + ], previous, () => 'new:merged'); assert.equal(merged.id, 'stable:majority'); }); test('Radio cluster inheritance never discards the greatest low-ratio contributor', () => { const previous = [ - { - id: 'stable:four', - stationIds: ['a', 'b', 'c', 'd', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w'], - }, + { id: 'stable:four', stationIds: ['a', 'b', 'c', 'd', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w'] }, { id: 'stable:two', stationIds: ['e', 'f'] }, ]; - const [merged] = reconcileRadioClusterCandidates( - [ - { - id: 'merged', - stationIds: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'], - }, - ], - previous, - () => 'new:merged', - ); + const [merged] = reconcileRadioClusterCandidates([{ + id: 'merged', + stationIds: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'], + }], previous, () => 'new:merged'); assert.equal(merged.id, 'stable:four'); }); test('Radio cluster inheritance permits a unique one-member overlap', () => { - const [reconciled] = reconcileRadioClusterCandidates( - [{ id: 'current', stationIds: ['a', 'x', 'y'] }], - [{ id: 'stable:single-overlap', stationIds: ['a', 'b', 'c'] }], - () => 'new:current', - ); + const [reconciled] = reconcileRadioClusterCandidates([ + { id: 'current', stationIds: ['a', 'x', 'y'] }, + ], [ + { id: 'stable:single-overlap', stationIds: ['a', 'b', 'c'] }, + ], () => 'new:current'); assert.equal(reconciled.id, 'stable:single-overlap'); }); @@ -458,16 +337,8 @@ test('Radio cluster majority ties are deterministic across input permutations', { id: 'stable:alpha', stationIds: ['c', 'd', 'y'] }, ]; const current = [{ id: 'merged', stationIds: ['a', 'b', 'c', 'd'] }]; - const forward = reconcileRadioClusterCandidates( - current, - previous, - () => 'new:merged', - ); - const reversed = reconcileRadioClusterCandidates( - current, - [...previous].reverse(), - () => 'new:merged', - ); + const forward = reconcileRadioClusterCandidates(current, previous, () => 'new:merged'); + const reversed = reconcileRadioClusterCandidates(current, [...previous].reverse(), () => 'new:merged'); assert.equal(forward[0].id, 'stable:alpha'); assert.equal(reversed[0].id, 'stable:alpha'); @@ -488,14 +359,8 @@ test('active station tags own cluster labels even when overlapping tags have hig test('filters do not rewrite inputs and overlapping operational tags remain discoverable', () => { const before = structuredClone(stations); - assert.deepEqual( - filterRadioStations(stations, 'public-safety').map((station) => station.id), - ['safety'], - ); - assert.equal( - stationMatchesRadioCategory(stations[0], 'aviation-marine'), - true, - ); + assert.deepEqual(filterRadioStations(stations, 'public-safety').map((station) => station.id), ['safety']); + assert.equal(stationMatchesRadioCategory(stations[0], 'aviation-marine'), true); assert.deepEqual(stations, before); }); @@ -521,35 +386,17 @@ test('destroy and re-init reset Radio audio ownership, volume, filter, and telem audioInstances.push(this); } - addEventListener(type, listener) { - this.listeners.set(type, listener); - } + addEventListener(type, listener) { this.listeners.set(type, listener); } pause() {} - play() { - return Promise.resolve(); - } - removeAttribute(name) { - if (name === 'src') this.srcRemoved = true; - } + play() { return Promise.resolve(); } + removeAttribute(name) { if (name === 'src') this.srcRemoved = true; } load() {} }; const viewer = { camera: { positionWC: { x: 7_000_000, y: 0, z: 0 } }, - scene: { - canvas: { - disableRootEvents: true, - onwheel: null, - addEventListener() {}, - removeEventListener() {}, - }, - }, + scene: { canvas: { disableRootEvents: true, onwheel: null, addEventListener() {}, removeEventListener() {} } }, dataSources: { add() {}, remove() {} }, - entities: { - add(entity) { - return entity; - }, - remove() {}, - }, + entities: { add(entity) { return entity; }, remove() {} }, }; radioLayer.destroy(); @@ -557,9 +404,7 @@ test('destroy and re-init reset Radio audio ownership, volume, filter, and telem radioLayer.init(bindFakeCesiumCamera(viewer)); radioLayer.enable(); radioLayer.setLifecyclePresentation({ - lifecycleState: 'enabled', - enabled: true, - uncertain: false, + lifecycleState: 'enabled', enabled: true, uncertain: false, }); assert.equal(audioInstances.length, 1); assert.equal(setRadioVolume(0.25), true); @@ -582,19 +427,11 @@ test('destroy and re-init reset Radio audio ownership, volume, filter, and telem assert.equal(getRadioUIState().effectiveVolume, 0.8); radioLayer.init(bindFakeCesiumCamera(viewer)); - assert.equal( - audioInstances.length, - 2, - 're-init creates a clean Audio session', - ); + assert.equal(audioInstances.length, 2, 're-init creates a clean Audio session'); const nextState = getRadioUIState(); for (const eventName of ['playing', 'pause', 'waiting', 'error']) { oldAudio.listeners.get(eventName)?.(); - assert.deepEqual( - getRadioUIState(), - nextState, - `discarded ${eventName} callback is inert`, - ); + assert.deepEqual(getRadioUIState(), nextState, `discarded ${eventName} callback is inert`); } } finally { radioLayer.destroy(); @@ -617,18 +454,14 @@ test('late media errors after replacement or Pause cannot mutate the active stat audioInstances.push(this); } - addEventListener(type, listener) { - this.listeners.set(type, listener); - } + addEventListener(type, listener) { this.listeners.set(type, listener); } pause() {} play() { this.playCalls += 1; this.currentSrc = this.src; return new Promise(() => {}); } - removeAttribute(name) { - if (name === 'src') this.src = ''; - } + removeAttribute(name) { if (name === 'src') this.src = ''; } load() {} }; const stationRows = [ @@ -665,37 +498,24 @@ test('late media errors after replacement or Pause cannot mutate the active stat bitrate: 128, }, ]; - globalThis.fetch = async (url) => - String(url).startsWith('/api/radio/click/') - ? { ok: true } - : { - ok: true, - json: async () => ({ - stations: stationRows, - updatedAt: new Date().toISOString(), - stale: false, - degraded: false, - acceptedGeneration: 1, - catalogInstance: 'qa-instance-a', - }), - }; + globalThis.fetch = async (url) => String(url).startsWith('/api/radio/click/') + ? { ok: true } + : { + ok: true, + json: async () => ({ + stations: stationRows, + updatedAt: new Date().toISOString(), + stale: false, + degraded: false, + acceptedGeneration: 1, + catalogInstance: 'qa-instance-a', + }), + }; const viewer = { camera: { positionWC: { x: 7_000_000, y: 0, z: 0 } }, - scene: { - canvas: { - disableRootEvents: true, - onwheel: null, - addEventListener() {}, - removeEventListener() {}, - }, - }, + scene: { canvas: { disableRootEvents: true, onwheel: null, addEventListener() {}, removeEventListener() {} } }, dataSources: { add() {}, remove() {} }, - entities: { - add(entity) { - return entity; - }, - remove() {}, - }, + entities: { add(entity) { return entity; }, remove() {} }, }; radioLayer.destroy(); @@ -709,29 +529,18 @@ test('late media errors after replacement or Pause cannot mutate the active stat uncertain: true, }); assert.equal(radioLayer.getUIState().presentationActive, false); - assert.equal( - radioLayer.selectStation(stationRows[0].id, { - autoplay: true, - origin: 'voice', - }), - false, - 'uncertain lifecycle blocks direct station selection', - ); - assert.equal( - radioLayer.cycleStation(1, { - stationIds: stationRows.map(({ id }) => id), - autoplay: false, - rotate: true, - }), - false, - 'uncertain lifecycle blocks cycling before camera or fallback mutation', - ); + assert.equal(radioLayer.selectStation(stationRows[0].id, { + autoplay: true, + origin: 'voice', + }), false, 'uncertain lifecycle blocks direct station selection'); + assert.equal(radioLayer.cycleStation(1, { + stationIds: stationRows.map(({ id }) => id), + autoplay: false, + rotate: true, + }), false, 'uncertain lifecycle blocks cycling before camera or fallback mutation'); assert.equal(await radioLayer.togglePlayback({ origin: 'user' }), false); assert.equal(radioLayer.getUIState().selected, null); - assert.equal( - audioInstances.reduce((sum, audio) => sum + audio.playCalls, 0), - 0, - ); + assert.equal(audioInstances.reduce((sum, audio) => sum + audio.playCalls, 0), 0); radioLayer.setLifecyclePresentation({ lifecycleState: 'enabled', enabled: true, @@ -753,36 +562,28 @@ test('late media errors after replacement or Pause cannot mutate the active stat const replacementAudio = audioInstances.at(-1); assert.notEqual(replacementAudio, primaryAudio); assert.equal(radioLayer.getUIState().selected?.id, stationRows[1].id); - assert.equal( - audioInstances.reduce((sum, audio) => sum + audio.playCalls, 0), - 2, - ); + assert.equal(audioInstances.reduce((sum, audio) => sum + audio.playCalls, 0), 2); primaryAudio.currentSrc = ''; primaryAudio.listeners.get('error')?.(); assert.equal(radioLayer.getUIState().audioState, 'loading'); assert.equal(radioLayer.getUIState().selected?.id, stationRows[1].id); - assert.equal( - audioInstances.reduce((sum, audio) => sum + audio.playCalls, 0), - 2, - ); + assert.equal(audioInstances.reduce((sum, audio) => sum + audio.playCalls, 0), 2); const controlEvents = []; - const unsubscribeControls = radioLayer.subscribePlaybackControls( - (event) => { - const observed = radioLayer.getUIState(); - const cleanupResult = radioLayer.stopPlayback({ - origin: 'voice-cleanup', - attemptId: event.attemptId, - }); - controlEvents.push({ - action: event.action, - observedAudioState: observed.audioState, - observedStationId: observed.playingStationId, - cleanupResult, - }); - }, - ); + const unsubscribeControls = radioLayer.subscribePlaybackControls((event) => { + const observed = radioLayer.getUIState(); + const cleanupResult = radioLayer.stopPlayback({ + origin: 'voice-cleanup', + attemptId: event.attemptId, + }); + controlEvents.push({ + action: event.action, + observedAudioState: observed.audioState, + observedStationId: observed.playingStationId, + cleanupResult, + }); + }); assert.equal(radioLayer.pause({ origin: 'user' }), true); replacementAudio.listeners.get('error')?.(); await Promise.resolve(); @@ -792,14 +593,12 @@ test('late media errors after replacement or Pause cannot mutate the active stat assert.equal(state.selected?.id, stationRows[1].id); assert.equal(state.playingStationId, stationRows[1].id); assert.equal(replacementAudio.src, stationRows[1].streamUrl); - assert.deepEqual(controlEvents, [ - { - action: 'pause', - observedAudioState: 'paused', - observedStationId: stationRows[1].id, - cleanupResult: false, - }, - ]); + assert.deepEqual(controlEvents, [{ + action: 'pause', + observedAudioState: 'paused', + observedStationId: stationRows[1].id, + cleanupResult: false, + }]); void radioLayer.togglePlayback({ origin: 'user' }); await Promise.resolve(); @@ -815,10 +614,7 @@ test('late media errors after replacement or Pause cannot mutate the active stat assert.equal(radioLayer.getUIState().audioState, 'stopped'); assert.equal(resumedAudio.src, ''); unsubscribeControls(); - assert.equal( - audioInstances.reduce((sum, audio) => sum + audio.playCalls, 0), - 3, - ); + assert.equal(audioInstances.reduce((sum, audio) => sum + audio.playCalls, 0), 3); } finally { radioLayer.destroy(); globalThis.fetch = originalFetch; @@ -848,37 +644,21 @@ test('unusable directory responses preserve warm client state atomically', async }; const viewer = { camera: { positionWC: { x: 7_000_000, y: 0, z: 0 } }, - scene: { - canvas: { - disableRootEvents: true, - onwheel: null, - addEventListener() {}, - removeEventListener() {}, - }, - }, + scene: { canvas: { disableRootEvents: true, onwheel: null, addEventListener() {}, removeEventListener() {} } }, dataSources: { add() {}, remove() {} }, - entities: { - add(entity) { - return entity; - }, - remove() {}, - }, + entities: { add(entity) { return entity; }, remove() {} }, }; radioLayer.destroy(); try { radioLayer.init(bindFakeCesiumCamera(viewer)); - globalThis.fetch = async () => - new Response( - JSON.stringify({ - stations: [station], - stale: false, - degraded: false, - acceptedGeneration: 1, - catalogInstance: 'qa-instance-a', - updatedAt: now, - }), - { status: 200, headers: { 'Content-Type': 'application/json' } }, - ); + globalThis.fetch = async () => new Response(JSON.stringify({ + stations: [station], + stale: false, + degraded: false, + acceptedGeneration: 1, + catalogInstance: 'qa-instance-a', + updatedAt: now, + }), { status: 200, headers: { 'Content-Type': 'application/json' } }); radioLayer.enable(); await radioLayer.update(); assert.equal(getRadioUIState().stationCount, 1); @@ -886,18 +666,13 @@ test('unusable directory responses preserve warm client state atomically', async const acceptedSnapshot = getRadioAcceptedCatalogSnapshot(); assert.equal(acceptedSnapshot.generation, 1); assert.equal(acceptedSnapshot.stations[0].id, station.id); - assert.equal( - acceptedSnapshot.stations[0].metadataTrust, - 'untrusted-community', - ); + assert.equal(acceptedSnapshot.stations[0].metadataTrust, 'untrusted-community'); assert.equal(Object.isFrozen(acceptedSnapshot), true); assert.equal(Object.isFrozen(acceptedSnapshot.stations), true); assert.equal(Object.isFrozen(acceptedSnapshot.stations[0]), true); assert.equal(Object.isFrozen(acceptedSnapshot.stations[0].tags), true); assert.equal(Object.isFrozen(acceptedSnapshot.stations[0].languages), true); - assert.throws(() => { - acceptedSnapshot.stations[0].name = 'mutated'; - }, TypeError); + assert.throws(() => { acceptedSnapshot.stations[0].name = 'mutated'; }, TypeError); radioLayer.selectStation(station.id, { autoplay: false, focus: false }); const replacement = { @@ -906,26 +681,13 @@ test('unusable directory responses preserve warm client state atomically', async name: 'Partial Replacement', }; for (const body of [ - { - stations: [replacement], - stale: false, - degraded: true, - acceptedGeneration: 1, - updatedAt: now, - }, - { - stations: [replacement], - stale: true, - degraded: true, - acceptedGeneration: 1, - updatedAt: now, - }, + { stations: [replacement], stale: false, degraded: true, acceptedGeneration: 1, updatedAt: now }, + { stations: [replacement], stale: true, degraded: true, acceptedGeneration: 1, updatedAt: now }, ]) { - globalThis.fetch = async () => - new Response(JSON.stringify(body), { - status: 200, - headers: { 'Content-Type': 'application/json' }, - }); + globalThis.fetch = async () => new Response(JSON.stringify(body), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }); await radioLayer.update(); const preserved = getRadioUIState(); assert.equal(preserved.stationCount, 1); @@ -939,113 +701,64 @@ test('unusable directory responses preserve warm client state atomically', async for (const body of [ { stations: {} }, - { - stations: [station, { id: 'malformed' }], - stale: false, - degraded: false, - updatedAt: now, - }, - { - stations: [station], - stale: false, - degraded: false, - updatedAt: 'not-a-date', - }, + { stations: [station, { id: 'malformed' }], stale: false, degraded: false, updatedAt: now }, + { stations: [station], stale: false, degraded: false, updatedAt: 'not-a-date' }, { stations: [station], stale: 'false', degraded: false, updatedAt: now }, ]) { - globalThis.fetch = async () => - new Response(JSON.stringify(body), { - status: 200, - headers: { 'Content-Type': 'application/json' }, - }); + globalThis.fetch = async () => new Response(JSON.stringify(body), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }); await radioLayer.update(); assert.equal(getRadioUIState().stationCount, 1); assert.equal(getRadioUIState().updatedAt, now); assert.equal(getRadioUIState().stale, true); assert.equal(getRadioUIState().degraded, true); } - globalThis.fetch = async () => - new Response( - JSON.stringify({ - stations: [replacement, { id: 'malformed' }], - stale: false, - degraded: false, - acceptedGeneration: 2, - catalogInstance: 'qa-instance-a', - updatedAt: now, - }), - ); + globalThis.fetch = async () => new Response(JSON.stringify({ + stations: [replacement, { id: 'malformed' }], stale: false, degraded: false, + acceptedGeneration: 2, catalogInstance: 'qa-instance-a', updatedAt: now, + })); await radioLayer.update(); assert.equal(getRadioUIState().stationCount, 1); assert.equal(getRadioUIState().rejectedStationCount, 1); assert.equal(getRadioUIState().degraded, true); assert.equal(getRadioUIState().stale, false); assert.equal(getRadioAcceptedCatalogSnapshot().generation, 2); - assert.equal( - getRadioAcceptedCatalogSnapshot().stations[0].id, - replacement.id, - ); + assert.equal(getRadioAcceptedCatalogSnapshot().stations[0].id, replacement.id); // Server-side structural rejections are combined with client rejections // after admission; the healthy generation is still accepted. - globalThis.fetch = async () => - new Response( - JSON.stringify({ - stations: [replacement, { id: 'malformed' }], - stale: false, - degraded: false, - acceptedGeneration: 3, - catalogInstance: 'qa-instance-a', - updatedAt: now, - rejectedStationCount: 2, - }), - ); + globalThis.fetch = async () => new Response(JSON.stringify({ + stations: [replacement, { id: 'malformed' }], stale: false, degraded: false, + acceptedGeneration: 3, catalogInstance: 'qa-instance-a', updatedAt: now, + rejectedStationCount: 2, + })); await radioLayer.update(); assert.equal(getRadioUIState().rejectedStationCount, 3); assert.equal(getRadioUIState().degraded, true); assert.equal(getRadioAcceptedCatalogSnapshot().generation, 3); - globalThis.fetch = async () => - new Response( - JSON.stringify({ - stations: [replacement], - stale: false, - degraded: false, - acceptedGeneration: 4, - catalogInstance: 'qa-instance-a', - updatedAt: now, - rejectedStationCount: 10_000, - }), - ); + globalThis.fetch = async () => new Response(JSON.stringify({ + stations: [replacement], stale: false, degraded: false, + acceptedGeneration: 4, catalogInstance: 'qa-instance-a', updatedAt: now, + rejectedStationCount: 10_000, + })); await radioLayer.update(); assert.equal(getRadioUIState().rejectedStationCount, 10_000); assert.equal(getRadioAcceptedCatalogSnapshot().generation, 4); for (const rejectedStationCount of [-1, 1.5, '2', null, 10_001]) { - globalThis.fetch = async () => - new Response( - JSON.stringify({ - stations: [replacement], - stale: false, - degraded: false, - acceptedGeneration: 5, - catalogInstance: 'qa-instance-a', - updatedAt: now, - rejectedStationCount, - }), - ); + globalThis.fetch = async () => new Response(JSON.stringify({ + stations: [replacement], stale: false, degraded: false, + acceptedGeneration: 5, catalogInstance: 'qa-instance-a', updatedAt: now, + rejectedStationCount, + })); await radioLayer.update(); assert.equal(getRadioAcceptedCatalogSnapshot().generation, 4); assert.equal(getRadioUIState().stale, true); } - globalThis.fetch = async () => - new Response( - JSON.stringify({ - stations: [{ id: 'malformed' }], - stale: false, - degraded: false, - acceptedGeneration: 5, - catalogInstance: 'qa-instance-a', - updatedAt: now, - }), - ); + globalThis.fetch = async () => new Response(JSON.stringify({ + stations: [{ id: 'malformed' }], stale: false, degraded: false, + acceptedGeneration: 5, catalogInstance: 'qa-instance-a', updatedAt: now, + })); await radioLayer.update(); assert.equal(getRadioAcceptedCatalogSnapshot().generation, 4); assert.equal(getRadioUIState().stale, true); @@ -1064,21 +777,9 @@ test('tuner drag keeps one immutable catalog resolution through refresh and rele const originalFetch = globalThis.fetch; const viewer = { camera: { positionWC: { x: 7_000_000, y: 0, z: 0 } }, - scene: { - canvas: { - disableRootEvents: true, - onwheel: null, - addEventListener() {}, - removeEventListener() {}, - }, - }, + scene: { canvas: { disableRootEvents: true, onwheel: null, addEventListener() {}, removeEventListener() {} } }, dataSources: { add() {}, remove() {} }, - entities: { - add(entity) { - return entity; - }, - remove() {}, - }, + entities: { add(entity) { return entity; }, remove() {} }, }; const stationA = { id: '00000000-0000-4000-8000-000000000041', @@ -1107,21 +808,15 @@ test('tuner drag keeps one immutable catalog resolution through refresh and rele name: 'Generation C', }; const publish = async (stationOrStations, acceptedGeneration) => { - const stations = Array.isArray(stationOrStations) - ? stationOrStations - : [stationOrStations]; - globalThis.fetch = async () => - new Response( - JSON.stringify({ - stations, - stale: false, - degraded: false, - acceptedGeneration, - catalogInstance: 'qa-instance-a', - updatedAt: new Date().toISOString(), - }), - { status: 200, headers: { 'Content-Type': 'application/json' } }, - ); + const stations = Array.isArray(stationOrStations) ? stationOrStations : [stationOrStations]; + globalThis.fetch = async () => new Response(JSON.stringify({ + stations, + stale: false, + degraded: false, + acceptedGeneration, + catalogInstance: 'qa-instance-a', + updatedAt: new Date().toISOString(), + }), { status: 200, headers: { 'Content-Type': 'application/json' } }); await radioLayer.update(); }; @@ -1131,17 +826,12 @@ test('tuner drag keeps one immutable catalog resolution through refresh and rele radioLayer.enable(); await publish(stationA, 1); assert.equal(radioLayer.beginTuning(), true); - assert.equal( - radioLayer.previewTuningStation(stationA.id, { rotate: false }), - true, - ); + assert.equal(radioLayer.previewTuningStation(stationA.id, { rotate: false }), true); assert.equal(getRadioUIState().tuningCatalogGeneration, 1); await publish(stationB, 2); assert.equal(getRadioUIState().tuningPreviewStationId, stationA.id); - const changed = radioLayer.commitTuningStation(stationA.id, { - origin: 'user', - }); + const changed = radioLayer.commitTuningStation(stationA.id, { origin: 'user' }); assert.deepEqual(changed, { ok: false, reason: 'station-unavailable', @@ -1152,65 +842,39 @@ test('tuner drag keeps one immutable catalog resolution through refresh and rele assert.equal(getRadioUIState().tuningUnavailableStationId, stationA.id); assert.equal(radioLayer.beginTuning(), true); - assert.equal( - radioLayer.previewTuningStation(stationB.id, { rotate: false }), - true, - ); + assert.equal(radioLayer.previewTuningStation(stationB.id, { rotate: false }), true); await publish(stationB, 3); - const unchanged = radioLayer.commitTuningStation(stationB.id, { - origin: 'user', - }); + const unchanged = radioLayer.commitTuningStation(stationB.id, { origin: 'user' }); assert.equal(unchanged.ok, true); assert.equal(unchanged.generation, 2); assert.equal(getRadioUIState().selected?.name, 'Generation B'); assert.equal(radioLayer.beginTuning(), true); - assert.equal( - radioLayer.previewTuningStation(stationB.id, { rotate: false }), - true, - ); + assert.equal(radioLayer.previewTuningStation(stationB.id, { rotate: false }), true); await publish(stationC, 4); - const removed = radioLayer.commitTuningStation(stationB.id, { - origin: 'user', - }); + const removed = radioLayer.commitTuningStation(stationB.id, { origin: 'user' }); assert.equal(removed.ok, false); assert.equal(removed.reason, 'station-unavailable'); assert.equal(removed.stationId, stationB.id); assert.notEqual(getRadioUIState().selected?.id, stationC.id); await publish([stationA, stationC], 5); - assert.equal( - radioLayer.selectStation(stationA.id, { autoplay: false, focus: false }), - true, - ); + assert.equal(radioLayer.selectStation(stationA.id, { autoplay: false, focus: false }), true); assert.equal(radioLayer.beginTuning(), true); - assert.equal( - radioLayer.previewTuningStation(stationC.id, { rotate: false }), - true, - ); + assert.equal(radioLayer.previewTuningStation(stationC.id, { rotate: false }), true); await publish([stationA, { ...stationC, name: 'Changed target' }], 6); - const priorSelectionMismatch = radioLayer.commitTuningStation(stationC.id, { - origin: 'user', - }); + const priorSelectionMismatch = radioLayer.commitTuningStation(stationC.id, { origin: 'user' }); assert.equal(priorSelectionMismatch.ok, false); assert.equal(priorSelectionMismatch.reason, 'station-unavailable'); assert.equal(getRadioUIState().selected, null); assert.equal(getRadioUIState().tuningUnavailableStationId, stationC.id); await publish([stationB, stationC], 7); - assert.equal( - radioLayer.selectStation(stationB.id, { autoplay: false, focus: false }), - true, - ); + assert.equal(radioLayer.selectStation(stationB.id, { autoplay: false, focus: false }), true); assert.equal(radioLayer.beginTuning(), true); - assert.equal( - radioLayer.previewTuningStation(stationB.id, { rotate: false }), - true, - ); + assert.equal(radioLayer.previewTuningStation(stationB.id, { rotate: false }), true); await publish([{ ...stationB, name: 'Same ID replacement' }, stationC], 8); - const sameIdMismatch = radioLayer.commitTuningStation(stationB.id, { - origin: 'user', - }); + const sameIdMismatch = radioLayer.commitTuningStation(stationB.id, { origin: 'user' }); assert.equal(sameIdMismatch.ok, false); assert.equal(sameIdMismatch.reason, 'station-unavailable'); assert.equal(getRadioUIState().selected, null); @@ -1236,47 +900,30 @@ test('failed exact tuner release cannot consume a stale playback fallback', asyn streamUrl: 'https://radio.example.com/fallback.mp3', }), ]; - await withRadioLayer( - { - stations: stationRows, - failingUrls: [stationRows[0].streamUrl], - }, - async ({ playUrls }) => { - // A playing stream, so the release plays. - assert.equal( - radioLayer.selectStation(stationRows[1].id, { autoplay: true }), - true, - ); - await new Promise((resolve) => setTimeout(resolve)); - assert.equal( - radioLayer.cycleStation(1, { - stationIds: stationRows.map(({ id }) => id), - autoplay: false, - fallback: true, - }), - true, - 'a voice-style prepared cycle creates the stale-fallback precondition', - ); - playUrls.length = 0; - assert.equal(radioLayer.beginTuning(), true); - assert.equal( - radioLayer.previewTuningStation(stationRows[0].id, { rotate: false }), - true, - ); - assert.equal( - radioLayer.commitTuningStation(stationRows[0].id, { origin: 'user' }) - .ok, - true, - ); - await new Promise((resolve) => setTimeout(resolve)); - const state = radioLayer.getUIState(); - assert.deepEqual(playUrls, [stationRows[0].streamUrl]); - assert.equal(state.selected?.id, stationRows[0].id); - assert.equal(state.audioState, 'error'); - assert.equal(state.tuningStatic, true); - assert.equal(state.tuningAwaitingStationId, stationRows[0].id); - }, - ); + await withRadioLayer({ + stations: stationRows, + failingUrls: [stationRows[0].streamUrl], + }, async ({ playUrls }) => { + // A playing stream, so the release plays. + assert.equal(radioLayer.selectStation(stationRows[1].id, { autoplay: true }), true); + await new Promise((resolve) => setTimeout(resolve)); + assert.equal(radioLayer.cycleStation(1, { + stationIds: stationRows.map(({ id }) => id), + autoplay: false, + fallback: true, + }), true, 'a voice-style prepared cycle creates the stale-fallback precondition'); + playUrls.length = 0; + assert.equal(radioLayer.beginTuning(), true); + assert.equal(radioLayer.previewTuningStation(stationRows[0].id, { rotate: false }), true); + assert.equal(radioLayer.commitTuningStation(stationRows[0].id, { origin: 'user' }).ok, true); + await new Promise((resolve) => setTimeout(resolve)); + const state = radioLayer.getUIState(); + assert.deepEqual(playUrls, [stationRows[0].streamUrl]); + assert.equal(state.selected?.id, stationRows[0].id); + assert.equal(state.audioState, 'error'); + assert.equal(state.tuningStatic, true); + assert.equal(state.tuningAwaitingStationId, stationRows[0].id); + }); }); test('tuner cancellation restores the frozen start marker after catalog removal', async () => { @@ -1292,38 +939,23 @@ test('tuner cancellation restores the frozen start marker after catalog removal' addEventListener() {} pause() {} - play() { - playCalls += 1; - return Promise.resolve(); - } + play() { playCalls += 1; return Promise.resolve(); } removeAttribute() {} load() {} }; const viewer = { camera: { positionWC: { x: 7_000_000, y: 0, z: 0 }, - flyTo() { - flyToCalls += 1; - }, + flyTo() { flyToCalls += 1; }, }, scene: { - canvas: { - disableRootEvents: true, - onwheel: null, - addEventListener() {}, - removeEventListener() {}, - }, + canvas: { disableRootEvents: true, onwheel: null, addEventListener() {}, removeEventListener() {} }, requestRender() {}, }, dataSources: { add() {}, remove() {} }, entities: { - add(entity) { - selectedEntities.set(entity.id, entity); - return entity; - }, - remove(entity) { - return selectedEntities.delete(entity?.id); - }, + add(entity) { selectedEntities.set(entity.id, entity); return entity; }, + remove(entity) { return selectedEntities.delete(entity?.id); }, }, }; const stationA = { @@ -1351,18 +983,14 @@ test('tuner cancellation restores the frozen start marker after catalog removal' streamUrl: 'https://radio.example.com/preview.mp3', }; const publish = async (rows, acceptedGeneration) => { - globalThis.fetch = async () => - new Response( - JSON.stringify({ - stations: rows, - stale: false, - degraded: false, - acceptedGeneration, - catalogInstance: 'qa-instance-a', - updatedAt: new Date().toISOString(), - }), - { status: 200, headers: { 'Content-Type': 'application/json' } }, - ); + globalThis.fetch = async () => new Response(JSON.stringify({ + stations: rows, + stale: false, + degraded: false, + acceptedGeneration, + catalogInstance: 'qa-instance-a', + updatedAt: new Date().toISOString(), + }), { status: 200, headers: { 'Content-Type': 'application/json' } }); await radioLayer.update(); }; @@ -1371,47 +999,27 @@ test('tuner cancellation restores the frozen start marker after catalog removal' radioLayer.init(bindFakeCesiumCamera(viewer)); radioLayer.enable(); radioLayer.setLifecyclePresentation({ - lifecycleState: 'enabled', - enabled: true, - uncertain: false, + lifecycleState: 'enabled', enabled: true, uncertain: false, }); let nextGeneration = 1; const restoreRemovedStartMarker = async () => { await publish([stationA, stationB], nextGeneration); nextGeneration += 1; - assert.equal( - radioLayer.selectStation(stationA.id, { - autoplay: false, - focus: false, - }), - true, - ); + assert.equal(radioLayer.selectStation(stationA.id, { autoplay: false, focus: false }), true); const startMarker = selectedEntities.get(`radio:selected:${stationA.id}`); assert.ok(startMarker); assert.equal(radioLayer.beginTuning(), true); - assert.equal( - radioLayer.previewTuningStation(stationB.id, { rotate: false }), - true, - ); + assert.equal(radioLayer.previewTuningStation(stationB.id, { rotate: false }), true); await publish([stationB], nextGeneration); nextGeneration += 1; assert.equal(getRadioUIState().selected, null); assert.equal(radioLayer.cancelTuning(), true); - const restoredMarker = selectedEntities.get( - `radio:selected:${stationA.id}`, - ); - assert.ok( - restoredMarker, - 'the frozen start marker remains visible after cancellation', - ); + const restoredMarker = selectedEntities.get(`radio:selected:${stationA.id}`); + assert.ok(restoredMarker, 'the frozen start marker remains visible after cancellation'); assert.deepEqual(restoredMarker.position, startMarker.position); assert.equal(getRadioUIState().tuningRestoredStationId, stationA.id); assert.equal(getRadioUIState().tuningActive, false); - assert.equal( - getRadioUIState().selected, - null, - 'stale marker never becomes playback authority', - ); + assert.equal(getRadioUIState().selected, null, 'stale marker never becomes playback authority'); assert.equal(playCalls, 0); assert.equal(flyToCalls, 0); return radioLayer.getAcceptedCatalogSnapshot(); @@ -1433,10 +1041,7 @@ test('tuner cancellation restores the frozen start marker after catalog removal' assert.equal(getRadioUIState().filter, 'news'); assert.equal(getRadioUIState().tuningRestoredStationId, null); assert.equal(selectedEntities.has(`radio:selected:${stationA.id}`), false); - assert.equal( - radioLayer.getAcceptedCatalogSnapshot(), - changedFilterSnapshot, - ); + assert.equal(radioLayer.getAcceptedCatalogSnapshot(), changedFilterSnapshot); assert.equal(getRadioUIState().selected, null); assert.equal(playCalls, 0); assert.equal(flyToCalls, 0); @@ -1449,17 +1054,11 @@ test('tuner cancellation restores the frozen start marker after catalog removal' 'the next accepted equal-generation refresh clears the presentation-only marker', ); assert.equal(selectedEntities.has(`radio:selected:${stationA.id}`), false); - assert.equal( - radioLayer.getAcceptedCatalogSnapshot(), - equalGenerationSnapshot, - ); + assert.equal(radioLayer.getAcceptedCatalogSnapshot(), equalGenerationSnapshot); assert.equal(playCalls, 0); assert.equal(flyToCalls, 0); - assert.equal( - radioLayer.selectStation(stationB.id, { autoplay: false, focus: false }), - true, - ); + assert.equal(radioLayer.selectStation(stationB.id, { autoplay: false, focus: false }), true); assert.equal(getRadioUIState().tuningRestoredStationId, null); assert.equal(selectedEntities.has(`radio:selected:${stationA.id}`), false); } finally { @@ -1472,53 +1071,35 @@ test('tuner cancellation restores the frozen start marker after catalog removal' test('Radio public tuner, filter, and resume mutations require certain enabled lifecycle authority', async () => { const originalFetch = globalThis.fetch; - const stationRows = [ - { - id: '00000000-0000-4000-8000-000000000061', - name: 'Lifecycle station', - lat: 30, - lon: -97, - streamUrl: 'https://radio.example.com/lifecycle.mp3', - homepage: null, - tags: ['news'], - languages: ['English'], - state: 'Texas', - country: 'United States', - countryCode: 'US', - metadataTrust: 'untrusted-community', - codec: 'MP3', - bitrate: 128, - }, - ]; - globalThis.fetch = async () => - new Response( - JSON.stringify({ - stations: stationRows, - stale: false, - degraded: false, - acceptedGeneration: 1, - catalogInstance: 'qa-instance-a', - updatedAt: new Date().toISOString(), - }), - { status: 200, headers: { 'Content-Type': 'application/json' } }, - ); + const stationRows = [{ + id: '00000000-0000-4000-8000-000000000061', + name: 'Lifecycle station', + lat: 30, + lon: -97, + streamUrl: 'https://radio.example.com/lifecycle.mp3', + homepage: null, + tags: ['news'], + languages: ['English'], + state: 'Texas', + country: 'United States', + countryCode: 'US', + metadataTrust: 'untrusted-community', + codec: 'MP3', + bitrate: 128, + }]; + globalThis.fetch = async () => new Response(JSON.stringify({ + stations: stationRows, + stale: false, + degraded: false, + acceptedGeneration: 1, + catalogInstance: 'qa-instance-a', + updatedAt: new Date().toISOString(), + }), { status: 200, headers: { 'Content-Type': 'application/json' } }); const viewer = { camera: { positionWC: { x: 7_000_000, y: 0, z: 0 } }, - scene: { - canvas: { - disableRootEvents: true, - onwheel: null, - addEventListener() {}, - removeEventListener() {}, - }, - }, + scene: { canvas: { disableRootEvents: true, onwheel: null, addEventListener() {}, removeEventListener() {} } }, dataSources: { add() {}, remove() {} }, - entities: { - add(entity) { - return entity; - }, - remove() {}, - }, + entities: { add(entity) { return entity; }, remove() {} }, }; viewer.scene.requestRender = () => {}; @@ -1528,9 +1109,7 @@ test('Radio public tuner, filter, and resume mutations require certain enabled l radioLayer.enable(); await radioLayer.update(); radioLayer.setLifecyclePresentation({ - lifecycleState: 'enabled', - enabled: true, - uncertain: false, + lifecycleState: 'enabled', enabled: true, uncertain: false, }); assert.equal(radioLayer.beginTuning(), true); assert.equal(radioLayer.setTuningStatic(false), true); @@ -1543,36 +1122,18 @@ test('Radio public tuner, filter, and resume mutations require certain enabled l ]) { radioLayer.setLifecyclePresentation(presentation); const before = getRadioUIState(); - assert.equal( - radioLayer.setTuningStatic(true), - false, - presentation.lifecycleState, - ); - assert.equal( - radioLayer.setFilter('news'), - false, - presentation.lifecycleState, - ); - assert.equal( - await radioLayer.togglePlayback({ origin: 'user' }), - false, - presentation.lifecycleState, - ); + assert.equal(radioLayer.setTuningStatic(true), false, presentation.lifecycleState); + assert.equal(radioLayer.setFilter('news'), false, presentation.lifecycleState); + assert.equal(await radioLayer.togglePlayback({ origin: 'user' }), false, presentation.lifecycleState); const after = getRadioUIState(); - assert.equal( - after.tuningStatic, - before.tuningStatic, - presentation.lifecycleState, - ); + assert.equal(after.tuningStatic, before.tuningStatic, presentation.lifecycleState); assert.equal(after.filter, before.filter, presentation.lifecycleState); assert.equal(after.selected, null, presentation.lifecycleState); assert.equal(after.audioState, 'stopped', presentation.lifecycleState); } radioLayer.setLifecyclePresentation({ - lifecycleState: 'enabled', - enabled: true, - uncertain: false, + lifecycleState: 'enabled', enabled: true, uncertain: false, }); assert.equal(radioLayer.setTuningStatic(true), true); assert.equal(getRadioUIState().tuningStatic, true); @@ -1589,21 +1150,9 @@ test('tuner refuses a degraded fallback that has no accepted catalog generation' const originalFetch = globalThis.fetch; const viewer = { camera: { positionWC: { x: 7_000_000, y: 0, z: 0 } }, - scene: { - canvas: { - disableRootEvents: true, - onwheel: null, - addEventListener() {}, - removeEventListener() {}, - }, - }, + scene: { canvas: { disableRootEvents: true, onwheel: null, addEventListener() {}, removeEventListener() {} } }, dataSources: { add() {}, remove() {} }, - entities: { - add(entity) { - return entity; - }, - remove() {}, - }, + entities: { add(entity) { return entity; }, remove() {} }, }; const station = { id: '00000000-0000-4000-8000-000000000043', @@ -1624,26 +1173,18 @@ test('tuner refuses a degraded fallback that has no accepted catalog generation' radioLayer.destroy(); try { - globalThis.fetch = async () => - new Response( - JSON.stringify({ - stations: [station], - stale: false, - degraded: true, - acceptedGeneration: null, - updatedAt: new Date().toISOString(), - }), - { status: 200, headers: { 'Content-Type': 'application/json' } }, - ); + globalThis.fetch = async () => new Response(JSON.stringify({ + stations: [station], + stale: false, + degraded: true, + acceptedGeneration: null, + updatedAt: new Date().toISOString(), + }), { status: 200, headers: { 'Content-Type': 'application/json' } }); radioLayer.init(bindFakeCesiumCamera(viewer)); radioLayer.enable(); await radioLayer.update(); - assert.equal( - getRadioUIState().stationCount, - 1, - 'fallback remains visible outside the tuner', - ); + assert.equal(getRadioUIState().stationCount, 1, 'fallback remains visible outside the tuner'); assert.equal(getRadioUIState().acceptedCatalogGeneration, null); assert.deepEqual(radioLayer.getTunerStations(), []); assert.equal(radioLayer.beginTuning(), false); @@ -1677,39 +1218,19 @@ test('accepted snapshots allowlist station fields and never regress generation i }; const viewer = { camera: { positionWC: { x: 7_000_000, y: 0, z: 0 } }, - scene: { - canvas: { - disableRootEvents: true, - onwheel: null, - addEventListener() {}, - removeEventListener() {}, - }, - }, + scene: { canvas: { disableRootEvents: true, onwheel: null, addEventListener() {}, removeEventListener() {} } }, dataSources: { add() {}, remove() {} }, - entities: { - add(entity) { - return entity; - }, - remove() {}, - }, + entities: { add(entity) { return entity; }, remove() {} }, }; - const serve = ( - stations, - acceptedGeneration, - catalogInstance = 'qa-instance-a', - ) => { - globalThis.fetch = async () => - new Response( - JSON.stringify({ - stations, - stale: false, - degraded: false, - acceptedGeneration, - catalogInstance, - updatedAt: now, - }), - { status: 200, headers: { 'Content-Type': 'application/json' } }, - ); + const serve = (stations, acceptedGeneration, catalogInstance = 'qa-instance-a') => { + globalThis.fetch = async () => new Response(JSON.stringify({ + stations, + stale: false, + degraded: false, + acceptedGeneration, + catalogInstance, + updatedAt: now, + }), { status: 200, headers: { 'Content-Type': 'application/json' } }); }; radioLayer.destroy(); @@ -1798,21 +1319,9 @@ test('update abort, destroy, and re-init invalidate the prior session and reset let requestSignal = null; const viewer = { camera: { positionWC: { x: 7_000_000, y: 0, z: 0 } }, - scene: { - canvas: { - disableRootEvents: true, - onwheel: null, - addEventListener() {}, - removeEventListener() {}, - }, - }, + scene: { canvas: { disableRootEvents: true, onwheel: null, addEventListener() {}, removeEventListener() {} } }, dataSources: { add() {}, remove() {} }, - entities: { - add(entity) { - return entity; - }, - remove() {}, - }, + entities: { add(entity) { return entity; }, remove() {} }, }; const lateStation = { id: '00000000-0000-4000-8000-000000000099', @@ -1835,9 +1344,7 @@ test('update abort, destroy, and re-init invalidate the prior session and reset try { globalThis.fetch = (_url, options) => { requestSignal = options.signal; - return new Promise((resolve) => { - resolveFetch = resolve; - }); + return new Promise((resolve) => { resolveFetch = resolve; }); }; radioLayer.init(bindFakeCesiumCamera(viewer)); radioLayer.enable(); @@ -1850,57 +1357,45 @@ test('update abort, destroy, and re-init invalidate the prior session and reset radioLayer.destroy(); radioLayer.init(bindFakeCesiumCamera(viewer)); const cleanState = getRadioUIState(); - assert.deepEqual( - { - enabled: cleanState.enabled, - loading: cleanState.loading, - stale: cleanState.stale, - degraded: cleanState.degraded, - error: cleanState.error, - updatedAt: cleanState.updatedAt, - stationCount: cleanState.stationCount, - selected: cleanState.selected, - audioState: cleanState.audioState, - voiceDucked: cleanState.voiceDucked, - voiceRestoring: cleanState.voiceRestoring, - acceptedCatalogGeneration: cleanState.acceptedCatalogGeneration, - }, - { - enabled: false, - loading: false, - stale: false, - degraded: false, - error: null, - updatedAt: null, - stationCount: 0, - selected: null, - audioState: 'stopped', - voiceDucked: false, - voiceRestoring: false, - acceptedCatalogGeneration: null, - }, - ); + assert.deepEqual({ + enabled: cleanState.enabled, + loading: cleanState.loading, + stale: cleanState.stale, + degraded: cleanState.degraded, + error: cleanState.error, + updatedAt: cleanState.updatedAt, + stationCount: cleanState.stationCount, + selected: cleanState.selected, + audioState: cleanState.audioState, + voiceDucked: cleanState.voiceDucked, + voiceRestoring: cleanState.voiceRestoring, + acceptedCatalogGeneration: cleanState.acceptedCatalogGeneration, + }, { + enabled: false, + loading: false, + stale: false, + degraded: false, + error: null, + updatedAt: null, + stationCount: 0, + selected: null, + audioState: 'stopped', + voiceDucked: false, + voiceRestoring: false, + acceptedCatalogGeneration: null, + }); assert.equal(getRadioAcceptedCatalogSnapshot().generation, null); - resolveFetch( - new Response( - JSON.stringify({ - stations: [lateStation], - stale: false, - degraded: false, - acceptedGeneration: 99, - catalogInstance: 'qa-instance-a', - updatedAt: new Date().toISOString(), - }), - { status: 200, headers: { 'Content-Type': 'application/json' } }, - ), - ); + resolveFetch(new Response(JSON.stringify({ + stations: [lateStation], + stale: false, + degraded: false, + acceptedGeneration: 99, + catalogInstance: 'qa-instance-a', + updatedAt: new Date().toISOString(), + }), { status: 200, headers: { 'Content-Type': 'application/json' } })); await pending; - assert.deepEqual( - getRadioUIState(), - cleanState, - 'retired update completion is inert after re-init', - ); + assert.deepEqual(getRadioUIState(), cleanState, 'retired update completion is inert after re-init'); } finally { globalThis.fetch = originalFetch; radioLayer.destroy(); @@ -1913,107 +1408,48 @@ test('All is the initial Radio filter', () => { }); test('local viewport ranking chooses geographic distance without a language override', () => { - const ranked = rankRadioStationsForViewport( - [ - { id: 'english-far', lat: 30.5, lon: -97.7, languages: ['English'] }, - { - id: 'spanish-near', - lat: 30.2673, - lon: -97.743, - languages: ['Spanish'], - }, - { id: 'english-near', lat: 30.28, lon: -97.74, languages: ['en'] }, - ], - { lat: 30.2672, lon: -97.7431 }, - ); - assert.deepEqual( - ranked.map((station) => station.id), - ['spanish-near', 'english-near', 'english-far'], - ); + const ranked = rankRadioStationsForViewport([ + { id: 'english-far', lat: 30.5, lon: -97.7, languages: ['English'] }, + { id: 'spanish-near', lat: 30.2673, lon: -97.7430, languages: ['Spanish'] }, + { id: 'english-near', lat: 30.28, lon: -97.74, languages: ['en'] }, + ], { lat: 30.2672, lon: -97.7431 }); + assert.deepEqual(ranked.map((station) => station.id), ['spanish-near', 'english-near', 'english-far']); }); test('global viewport ranking prefers English stations, then distance', () => { - const ranked = rankRadioStationsForViewport( - [ - { id: 'spanish-nearest', lat: 0, lon: 0, languages: ['Spanish'] }, - { id: 'english-far', lat: 15, lon: 0, languages: ['English'] }, - { id: 'english-near', lat: 5, lon: 0, languages: ['eng'] }, - ], - { lat: 0, lon: 0 }, - { preferEnglish: true }, - ); - assert.deepEqual( - ranked.map((station) => station.id), - ['english-near', 'english-far', 'spanish-nearest'], - ); + const ranked = rankRadioStationsForViewport([ + { id: 'spanish-nearest', lat: 0, lon: 0, languages: ['Spanish'] }, + { id: 'english-far', lat: 15, lon: 0, languages: ['English'] }, + { id: 'english-near', lat: 5, lon: 0, languages: ['eng'] }, + ], { lat: 0, lon: 0 }, { preferEnglish: true }); + assert.deepEqual(ranked.map((station) => station.id), ['english-near', 'english-far', 'spanish-nearest']); assert.equal(isEnglishRadioStation(ranked[0]), true); }); test('explicit Radio requests combine category, country, station name, and distance', () => { const rows = [ - { - id: 'music-austin', - name: 'Austin Music', - tags: ['music'], - countryCode: 'US', - country: 'United States', - lat: 30.26, - lon: -97.74, - }, - { - id: 'news-dallas', - name: 'Texas News', - tags: ['news'], - countryCode: 'US', - country: 'United States', - lat: 32.77, - lon: -96.79, - }, - { - id: 'news-austin', - name: 'Austin Public News', - tags: ['news'], - countryCode: 'US', - country: 'United States', - lat: 30.27, - lon: -97.75, - }, - { - id: 'news-mexico', - name: 'Noticias', - tags: ['news'], - countryCode: 'MX', - country: 'Mexico', - lat: 25.68, - lon: -100.31, - }, + { id: 'music-austin', name: 'Austin Music', tags: ['music'], countryCode: 'US', country: 'United States', lat: 30.26, lon: -97.74 }, + { id: 'news-dallas', name: 'Texas News', tags: ['news'], countryCode: 'US', country: 'United States', lat: 32.77, lon: -96.79 }, + { id: 'news-austin', name: 'Austin Public News', tags: ['news'], countryCode: 'US', country: 'United States', lat: 30.27, lon: -97.75 }, + { id: 'news-mexico', name: 'Noticias', tags: ['news'], countryCode: 'MX', country: 'Mexico', lat: 25.68, lon: -100.31 }, ]; const ranked = rankRadioStationsForRequest(rows, { categoryId: 'news', country: 'US', anchor: { lat: 30.2672, lon: -97.7431 }, }); - assert.deepEqual( - ranked.map((station) => station.id), - ['news-austin', 'news-dallas'], - ); - assert.equal( - rankRadioStationsForRequest(rows, { - categoryId: 'news', - stationQuery: 'public', - })[0].id, - 'news-austin', - ); + assert.deepEqual(ranked.map((station) => station.id), ['news-austin', 'news-dallas']); + assert.equal(rankRadioStationsForRequest(rows, { + categoryId: 'news', + stationQuery: 'public', + })[0].id, 'news-austin'); }); test('viewport ranking treats the antimeridian as adjacent', () => { - const ranked = rankRadioStationsForViewport( - [ - { id: 'far', lat: 0, lon: 150, languages: ['English'] }, - { id: 'across-dateline', lat: 0, lon: -179.9, languages: ['English'] }, - ], - { lat: 0, lon: 179.9 }, - ); + const ranked = rankRadioStationsForViewport([ + { id: 'far', lat: 0, lon: 150, languages: ['English'] }, + { id: 'across-dateline', lat: 0, lon: -179.9, languages: ['English'] }, + ], { lat: 0, lon: 179.9 }); assert.equal(ranked[0].id, 'across-dateline'); }); @@ -2024,26 +1460,10 @@ test('global-view threshold tolerates Cesium altitude round-off without widening test('Radio picks resolve ordinary, selected, primitive, and clustered entity ids', () => { assert.equal(radioStationIdFromPick({ id: 'radio:alpha' }), 'alpha'); - assert.equal( - radioStationIdFromPick({ primitive: { id: 'radio:beta' } }), - 'beta', - ); - assert.equal( - radioStationIdFromPick({ id: { id: 'radio:selected:gamma' } }), - 'gamma', - ); - assert.equal( - radioStationIdFromPick({ id: [{ id: 'other' }, { id: 'radio:delta' }] }), - 'delta', - ); - assert.equal( - radioStationIdFromPick({ - primitive: { - id: [{ id: 'radio:cluster-first' }, { id: 'radio:cluster-second' }], - }, - }), - 'cluster-first', - ); + assert.equal(radioStationIdFromPick({ primitive: { id: 'radio:beta' } }), 'beta'); + assert.equal(radioStationIdFromPick({ id: { id: 'radio:selected:gamma' } }), 'gamma'); + assert.equal(radioStationIdFromPick({ id: [{ id: 'other' }, { id: 'radio:delta' }] }), 'delta'); + assert.equal(radioStationIdFromPick({ primitive: { id: [{ id: 'radio:cluster-first' }, { id: 'radio:cluster-second' }] } }), 'cluster-first'); assert.equal(radioStationIdFromPick({ id: 'flights:abc' }), null); }); @@ -2057,14 +1477,11 @@ test('selected Radio station bracket is a transparent four-corner category-color test('Radio text uses protected selected and bounded ambient WorldOverlay entries', () => { const position = { x: 1, y: 2, z: 3 }; - const selected = createRadioSelectedOverlayEntry( - { - id: 'station-a', - name: 'Austin News 93.9 FM - KQA', - tags: ['news'], - }, - position, - ); + const selected = createRadioSelectedOverlayEntry({ + id: 'station-a', + name: 'Austin News 93.9 FM - KQA', + tags: ['news'], + }, position); assert.equal(selected.variant, 'selected'); assert.equal(selected.selected, true); assert.equal(selected.protected, true); @@ -2085,26 +1502,16 @@ test('Radio text uses protected selected and bounded ambient WorldOverlay entrie assert.equal(cluster.collisionGroup, 'ambient-label'); assert.equal(cluster.horizonCull, true); assert.equal(cluster.maxDistance, RADIO_GLOBE_INTERACTION_MAX_DISTANCE_M); - assert.equal( - cluster.distanceScale.far, - RADIO_GLOBE_INTERACTION_MAX_DISTANCE_M, - ); + assert.equal(cluster.distanceScale.far, RADIO_GLOBE_INTERACTION_MAX_DISTANCE_M); assert.equal(cluster.title, '12 NEWS'); assert.equal(cluster.priority, 12); assert.equal(cluster.stateless, true); assert.equal(cluster.edgeFade, 'none'); - assert.equal( - RADIO_OVERLAY_SOURCE_OPTIONS.cohortLimit, - RADIO_OVERLAY_COHORT_LIMIT, - ); + assert.equal(RADIO_OVERLAY_SOURCE_OPTIONS.cohortLimit, RADIO_OVERLAY_COHORT_LIMIT); assert.ok(RADIO_OVERLAY_COHORT_LIMIT < 750); const singleton = createRadioSingletonOverlayEntry({ - station: { - id: 'station-b', - name: '100.3 The River - WQRV', - tags: ['public safety'], - }, + station: { id: 'station-b', name: '100.3 The River - WQRV', tags: ['public safety'] }, position, priority: 1.5, }); @@ -2115,106 +1522,63 @@ test('Radio text uses protected selected and bounded ambient WorldOverlay entrie assert.equal(singleton.interactive, false); assert.equal(singleton.horizonCull, true); assert.equal(singleton.maxDistance, RADIO_GLOBE_INTERACTION_MAX_DISTANCE_M); - assert.equal( - singleton.distanceScale.far, - RADIO_GLOBE_INTERACTION_MAX_DISTANCE_M, - ); + assert.equal(singleton.distanceScale.far, RADIO_GLOBE_INTERACTION_MAX_DISTANCE_M); assert.equal(singleton.priority, 1.5); assert.equal(singleton.stateless, true); }); test('Radio singleton labels are zoom-aware, nearest-first, stable, and bounded', () => { - assert.equal( - radioSingletonLabelLimit(25_000_000), - RADIO_SINGLETON_GLOBAL_LIMIT, - ); - assert.equal( - radioSingletonLabelLimit(2_000_000), - RADIO_SINGLETON_GLOBAL_LIMIT, - ); + assert.equal(radioSingletonLabelLimit(25_000_000), RADIO_SINGLETON_GLOBAL_LIMIT); + assert.equal(radioSingletonLabelLimit(2_000_000), RADIO_SINGLETON_GLOBAL_LIMIT); assert.equal(radioSingletonLabelLimit(1_999_999), RADIO_SINGLETON_MID_LIMIT); assert.equal(radioSingletonLabelLimit(250_000), RADIO_SINGLETON_MID_LIMIT); assert.equal(radioSingletonLabelLimit(249_999), RADIO_SINGLETON_NEAR_LIMIT); - const ranked = selectRadioSingletonCandidates( - [ - { station: { id: 'far' }, distanceM: 30 }, - { station: { id: 'near-z' }, distanceM: 10 }, - { station: { id: 'near-a' }, distanceM: 10 }, - ], - 2, - ); - assert.deepEqual( - ranked.map(({ station }) => station.id), - ['near-a', 'near-z'], - ); + const ranked = selectRadioSingletonCandidates([ + { station: { id: 'far' }, distanceM: 30 }, + { station: { id: 'near-z' }, distanceM: 10 }, + { station: { id: 'near-a' }, distanceM: 10 }, + ], 2); + assert.deepEqual(ranked.map(({ station }) => station.id), ['near-a', 'near-z']); }); test('directory tuner maps every absolute slot to an available station', () => { assert.deepEqual(radioTunerSlot(0, 4), { - slot: 0, - max: 3, - locked: true, - stationIndex: 0, - leftIndex: 0, - rightIndex: 0, + slot: 0, max: 3, locked: true, stationIndex: 0, leftIndex: 0, rightIndex: 0, }); assert.deepEqual(radioTunerSlot(1, 4), { - slot: 1, - max: 3, - locked: true, - stationIndex: 1, - leftIndex: 1, - rightIndex: 1, + slot: 1, max: 3, locked: true, stationIndex: 1, leftIndex: 1, rightIndex: 1, }); assert.deepEqual(radioTunerSlot(99, 4), { - slot: 3, - max: 3, - locked: true, - stationIndex: 3, - leftIndex: 3, - rightIndex: 3, + slot: 3, max: 3, locked: true, stationIndex: 3, leftIndex: 3, rightIndex: 3, }); assert.equal(radioTunerSlot(0, 0).locked, false); }); test('directory tuner pointer progress maps left, center, right, and midpoint ties exactly', () => { assert.deepEqual(radioTunerPointerPosition(7, 0, 114, 750), { - ratio: 0, - coordinate: 0, - stationIndex: 0, + ratio: 0, coordinate: 0, stationIndex: 0, }); assert.deepEqual(radioTunerPointerPosition(57, 0, 114, 750), { - ratio: 0.5, - coordinate: 374.5, - stationIndex: 375, + ratio: 0.5, coordinate: 374.5, stationIndex: 375, }); assert.deepEqual(radioTunerPointerPosition(107, 0, 114, 750), { - ratio: 1, - coordinate: 749, - stationIndex: 749, + ratio: 1, coordinate: 749, stationIndex: 749, }); assert.deepEqual(radioTunerPointerPosition(50, 10, 80, 1), { - ratio: 0.5, - coordinate: 0, - stationIndex: 0, + ratio: 0.5, coordinate: 0, stationIndex: 0, }); assert.equal(radioTunerPointerPosition(50, 10, 80, 0).stationIndex, -1); assert.deepEqual(radioTunerCommitSlot(2.5, 5), radioTunerSlot(3, 5)); }); test('directory tuner bands preserve stable filtered order without injecting outside selections', () => { - const ranked = Array.from({ length: 800 }, (_, index) => ({ - id: `station-${index}`, - })); + const ranked = Array.from({ length: 800 }, (_, index) => ({ id: `station-${index}` })); const selected = { id: 'selected-outside-band' }; const band = buildRadioTunerBand(ranked, selected, 750); assert.equal(band.length, 750); assert.deepEqual(band, ranked.slice(0, 750)); assert.ok(!band.includes(selected)); - assert.deepEqual( - buildRadioTunerBand(ranked, ranked[5], 24), - ranked.slice(0, 24), - ); + assert.deepEqual(buildRadioTunerBand(ranked, ranked[5], 24), ranked.slice(0, 24)); }); test('directory tuner virtual tape is bounded and moves opposite the needle', () => { @@ -2222,21 +1586,12 @@ test('directory tuner virtual tape is bounded and moves opposite the needle', () const at101 = buildRadioTunerTicks(101, 750, 300); assert.ok(at100.ticks.length <= Math.ceil(286 / at100.pitchPx) + 6); assert.ok(at101.needleX > at100.needleX); - const sharedIndex = at100.ticks.find((tick) => - at101.ticks.some((next) => next.stationIndex === tick.stationIndex), - ); + const sharedIndex = at100.ticks.find((tick) => at101.ticks.some((next) => next.stationIndex === tick.stationIndex)); assert.ok(sharedIndex); - const before = at100.ticks.find( - (tick) => tick.stationIndex === sharedIndex.stationIndex, - ); - const after = at101.ticks.find( - (tick) => tick.stationIndex === sharedIndex.stationIndex, - ); + const before = at100.ticks.find((tick) => tick.stationIndex === sharedIndex.stationIndex); + const after = at101.ticks.find((tick) => tick.stationIndex === sharedIndex.stationIndex); assert.ok(after.xPx < before.xPx); - assert.ok( - Math.abs(after.xPx - before.xPx) >= - 4 * (at101.needleX - at100.needleX) - 1e-9, - ); + assert.ok(Math.abs(after.xPx - before.xPx) >= 4 * (at101.needleX - at100.needleX) - 1e-9); const singleton = buildRadioTunerTicks(0, 1, 300); assert.equal(singleton.ratio, 0.5); assert.equal(singleton.ticks.length, 1); @@ -2244,30 +1599,24 @@ test('directory tuner virtual tape is bounded and moves opposite the needle', () }); test('station camera plans preserve altitude and viewing orientation without zooming', () => { - const nadir = radioStationCameraPlan( - { lat: 30, lon: -97 }, - { - height: 600, - heading: 0, - pitch: -Math.PI / 2, - roll: 0, - }, - ); + const nadir = radioStationCameraPlan({ lat: 30, lon: -97 }, { + height: 600, + heading: 0, + pitch: -Math.PI / 2, + roll: 0, + }); assert.ok(Math.abs(nadir.lat - 30) < 1e-9); assert.ok(Math.abs(nadir.lon + 97) < 1e-9); assert.equal(nadir.height, 600); assert.equal(nadir.heading, 0); assert.equal(nadir.pitch, -Math.PI / 2); assert.equal(nadir.roll, 0); - const oblique = radioStationCameraPlan( - { lat: 30, lon: -97 }, - { - height: 600, - heading: 0, - pitch: -Math.PI / 6, - roll: 0.2, - }, - ); + const oblique = radioStationCameraPlan({ lat: 30, lon: -97 }, { + height: 600, + heading: 0, + pitch: -Math.PI / 6, + roll: 0.2, + }); assert.equal(oblique.height, 600); assert.equal(oblique.heading, 0); assert.equal(oblique.pitch, -Math.PI / 6); @@ -2286,27 +1635,12 @@ test('Radio recenters clipped global and below-center closer Earth discs', () => keyholeRadius: 420, }; assert.equal(radioGlobeNeedsRecentering(centered), false); - assert.equal( - radioGlobeNeedsRecentering({ ...centered, earthCenterY: 560 }), - true, - ); - assert.equal( - radioGlobeNeedsRecentering({ ...centered, earthCenterX: 755 }), - true, - ); - assert.equal( - radioGlobeNeedsRecentering({ ...centered, earthRadius: 405 }), - false, - ); + assert.equal(radioGlobeNeedsRecentering({ ...centered, earthCenterY: 560 }), true); + assert.equal(radioGlobeNeedsRecentering({ ...centered, earthCenterX: 755 }), true); + assert.equal(radioGlobeNeedsRecentering({ ...centered, earthRadius: 405 }), false); const closer = { ...centered, earthRadius: 900 }; - assert.equal( - radioGlobeNeedsRecentering({ ...closer, earthCenterY: 800 }), - false, - ); - assert.equal( - radioGlobeNeedsRecentering({ ...closer, earthCenterY: 1_300 }), - true, - ); + assert.equal(radioGlobeNeedsRecentering({ ...closer, earthCenterY: 800 }), false); + assert.equal(radioGlobeNeedsRecentering({ ...closer, earthCenterY: 1_300 }), true); assert.equal(radioGlobeNeedsRecentering(null), false); }); @@ -2333,48 +1667,21 @@ test('Radio camera navigation yields to every live tracked-entity owner', () => }); test('tuner static spans drag and broadcaster handoff but voice ducking silences it', () => { - assert.equal( - radioTuningStaticShouldPlay({ tuningActive: true, tuningStatic: true }), - true, - ); - assert.equal( - radioTuningStaticShouldPlay({ - tuningStatic: true, - awaitingStationId: 'news', - }), - true, - ); - assert.equal( - radioTuningStaticShouldPlay({ - tuningStatic: true, - awaitingStationId: 'news', - voiceDucked: true, - }), - false, - ); - assert.equal( - radioTuningStaticShouldPlay({ - tuningStatic: false, - awaitingStationId: 'news', - }), - false, - ); + assert.equal(radioTuningStaticShouldPlay({ tuningActive: true, tuningStatic: true }), true); + assert.equal(radioTuningStaticShouldPlay({ tuningStatic: true, awaitingStationId: 'news' }), true); + assert.equal(radioTuningStaticShouldPlay({ + tuningStatic: true, + awaitingStationId: 'news', + voiceDucked: true, + }), false); + assert.equal(radioTuningStaticShouldPlay({ tuningStatic: false, awaitingStationId: 'news' }), false); }); test('station horizon scans ignore stationary camera noise but detect movement', () => { const prior = { x: 1_000, y: -2_000, z: 3_000 }; - assert.equal( - radioCameraPositionChanged(prior, { x: 1_000, y: -2_000, z: 3_000 }), - false, - ); - assert.equal( - radioCameraPositionChanged(prior, { x: 1_000.5, y: -2_000, z: 3_000 }), - false, - ); - assert.equal( - radioCameraPositionChanged(prior, { x: 1_002, y: -2_000, z: 3_000 }), - true, - ); + assert.equal(radioCameraPositionChanged(prior, { x: 1_000, y: -2_000, z: 3_000 }), false); + assert.equal(radioCameraPositionChanged(prior, { x: 1_000.5, y: -2_000, z: 3_000 }), false); + assert.equal(radioCameraPositionChanged(prior, { x: 1_002, y: -2_000, z: 3_000 }), true); assert.equal(radioCameraPositionChanged(null, prior), true); }); @@ -2382,9 +1689,7 @@ test('voice ducking preserves the user-owned Radio volume', async () => { radioLayer.destroy(); radioLayer.enable(); radioLayer.setLifecyclePresentation({ - lifecycleState: 'enabled', - enabled: true, - uncertain: false, + lifecycleState: 'enabled', enabled: true, uncertain: false, }); try { setRadioVoiceDucking(true); @@ -2426,9 +1731,7 @@ test('Radio volume requires certain enabled lifecycle authority', () => { } radioLayer.setLifecyclePresentation({ - lifecycleState: 'enabled', - enabled: true, - uncertain: false, + lifecycleState: 'enabled', enabled: true, uncertain: false, }); assert.equal(setRadioVolume(0.2), true); assert.equal(getRadioUIState().volume, 0.2); @@ -2451,9 +1754,7 @@ test('voice playback confirmation waits for playing and requires a hard duck', a subscribe: (next) => { listener = next; next({ ...state, audioState: 'stopped' }); - return () => { - listener = () => {}; - }; + return () => { listener = () => {}; }; }, getState: () => state, timeoutMs: 50, @@ -2461,18 +1762,15 @@ test('voice playback confirmation waits for playing and requires a hard duck', a assert.equal(await confirmed, true); state = { ...state, voiceDucked: false }; - assert.equal( - await confirmRadioPlayback({ - startPlayback: async () => true, - subscribe: (next) => { - next(state); - return () => {}; - }, - getState: () => state, - timeoutMs: 5, - }), - false, - ); + assert.equal(await confirmRadioPlayback({ + startPlayback: async () => true, + subscribe: (next) => { + next(state); + return () => {}; + }, + getState: () => state, + timeoutMs: 5, + }), false); }); test('voice playback confirmation accepts fallback buffering but times out safely', async () => { diff --git a/src/worldFocus.test.mjs b/src/worldFocus.test.mjs index bb8773334..090c77496 100644 --- a/src/worldFocus.test.mjs +++ b/src/worldFocus.test.mjs @@ -22,28 +22,16 @@ function stubCamera() { return { calls, heading: Cesium.Math.toRadians(45), - cancelFlight() { - calls.cancelFlight += 1; - }, - flyToBoundingSphere(sphere, options) { - calls.flights.push({ sphere, options }); - }, + cancelFlight() { calls.cancelFlight += 1; }, + flyToBoundingSphere(sphere, options) { calls.flights.push({ sphere, options }); }, }; } test('a focus request carries the clicked target to any registered listener', () => { const target = new EventTarget(); const seen = []; - const dispose = registerWorldFocusRequestListener(target, (event) => - seen.push(event.detail), - ); - assert.equal( - requestWorldFocus( - { kind: 'vessel', position: POSITION, id: '123' }, - target, - ), - true, - ); + const dispose = registerWorldFocusRequestListener(target, (event) => seen.push(event.detail)); + assert.equal(requestWorldFocus({ kind: 'vessel', position: POSITION, id: '123' }, target), true); assert.equal(seen.length, 1); assert.equal(seen[0].kind, 'vessel'); assert.equal(seen[0].id, '123'); @@ -57,9 +45,7 @@ test('a focus request carries the clicked target to any registered listener', () test('requests without a kind or a position are dropped, never dispatched', () => { const target = new EventTarget(); let count = 0; - target.addEventListener(WORLD_FOCUS_REQUEST_EVENT, () => { - count += 1; - }); + target.addEventListener(WORLD_FOCUS_REQUEST_EVENT, () => { count += 1; }); assert.equal(requestWorldFocus({ position: POSITION }, target), false); assert.equal(requestWorldFocus({ kind: 'vessel' }, target), false); assert.equal(requestWorldFocus(null, target), false); @@ -89,95 +75,51 @@ test('a refusing policy (cockpit owns the camera) never reaches the flight', () const result = routeWorldFocusRequest( { detail: { kind: 'vessel', id: '123', position: POSITION } }, () => false, - () => { - flights += 1; - }, + () => { flights += 1; }, ); assert.equal(result, false); assert.equal(flights, 0); }); test('malformed requests are inert', () => { - assert.equal( - routeWorldFocusRequest( - null, - () => 'x', - () => 'y', - ), - false, - ); - assert.equal( - routeWorldFocusRequest( - { detail: { kind: 'vessel' } }, - () => 'x', - () => 'y', - ), - false, - ); + assert.equal(routeWorldFocusRequest(null, () => 'x', () => 'y'), false); + assert.equal(routeWorldFocusRequest({ detail: { kind: 'vessel' } }, () => 'x', () => 'y'), false); }); // A request that cannot fly must never cost the user their tracking: the UI // policy releases the follow camera, so validation happens BEFORE it runs. test('an unflyable request never reaches the release policy', () => { const unflyable = [ - { kind: 'plane', position: POSITION }, // no framing for this kind - { kind: 'vessel', position: { x: NaN, y: 0, z: 0 } }, // malformed position - { kind: 'vessel', position: { x: 0, y: 0 } }, // truthy but incomplete + { kind: 'plane', position: POSITION }, // no framing for this kind + { kind: 'vessel', position: { x: NaN, y: 0, z: 0 } }, // malformed position + { kind: 'vessel', position: { x: 0, y: 0 } }, // truthy but incomplete { kind: 'vessel', id: '123', position: { x: 0, y: 0, z: 0 } }, // ECEF origin { kind: 'fire', id: 'fire-1', position: { x: 1, y: 1, z: 1 } }, // finite but inside Earth { kind: 'vessel', position: 'somewhere' }, - { kind: 'vessel', position: POSITION }, // missing owner id + { kind: 'vessel', position: POSITION }, // missing owner id { position: POSITION }, ]; for (const detail of unflyable) { - assert.equal( - isValidWorldFocusTarget(detail), - false, - JSON.stringify(detail), - ); + assert.equal(isValidWorldFocusTarget(detail), false, JSON.stringify(detail)); let policyRuns = 0; const result = routeWorldFocusRequest( { detail }, - () => { - policyRuns += 1; - return 'released'; - }, + () => { policyRuns += 1; return 'released'; }, () => 'flew', ); assert.equal(result, false, JSON.stringify(detail)); - assert.equal( - policyRuns, - 0, - `the policy must not run for ${JSON.stringify(detail)}`, - ); + assert.equal(policyRuns, 0, `the policy must not run for ${JSON.stringify(detail)}`); // Nor may such a request be dispatched in the first place. assert.equal(requestWorldFocus(detail, new EventTarget()), false); } - assert.equal( - isValidWorldFocusTarget({ kind: 'vessel', id: '123', position: POSITION }), - true, - ); - assert.equal( - isValidWorldFocusTarget({ kind: 'fire', id: 'fire-1', position: POSITION }), - true, - ); + assert.equal(isValidWorldFocusTarget({ kind: 'vessel', id: '123', position: POSITION }), true); + assert.equal(isValidWorldFocusTarget({ kind: 'fire', id: 'fire-1', position: POSITION }), true); }); test('the transfer flight supersedes any flight in progress and keeps the operator heading', () => { const camera = stubCamera(); - assert.equal( - flyToWorldTarget(bindFakeCesiumCamera({ camera }), { - kind: 'vessel', - id: '123', - position: POSITION, - }), - true, - ); - assert.equal( - camera.calls.cancelFlight, - 1, - 'a prior flight must be cancelled, not queued', - ); + assert.equal(flyToWorldTarget(bindFakeCesiumCamera({ camera }), { kind: 'vessel', id: '123', position: POSITION }), true); + assert.equal(camera.calls.cancelFlight, 1, 'a prior flight must be cancelled, not queued'); assert.equal(camera.calls.flights.length, 1); const { sphere, options } = camera.calls.flights[0]; assert.equal(sphere.radius, WORLD_FOCUS_FRAMING.vessel.radiusM); @@ -190,20 +132,12 @@ test('the transfer flight supersedes any flight in progress and keeps the operat test('fires frame wider than vessels — a fire is read by its surroundings', () => { const camera = stubCamera(); - flyToWorldTarget(bindFakeCesiumCamera({ camera }), { - kind: 'fire', - id: 'fire-1', - position: POSITION, - }); + flyToWorldTarget(bindFakeCesiumCamera({ camera }), { kind: 'fire', id: 'fire-1', position: POSITION }); const { sphere, options } = camera.calls.flights[0]; assert.equal(sphere.radius, WORLD_FOCUS_FRAMING.fire.radiusM); assert.equal(options.offset.range, WORLD_FOCUS_FRAMING.fire.rangeM); - assert.ok( - WORLD_FOCUS_FRAMING.fire.rangeM > WORLD_FOCUS_FRAMING.vessel.rangeM, - ); - assert.ok( - WORLD_FOCUS_FRAMING.fire.radiusM > WORLD_FOCUS_FRAMING.vessel.radiusM, - ); + assert.ok(WORLD_FOCUS_FRAMING.fire.rangeM > WORLD_FOCUS_FRAMING.vessel.rangeM); + assert.ok(WORLD_FOCUS_FRAMING.fire.radiusM > WORLD_FOCUS_FRAMING.vessel.radiusM); }); test('every framing is a real oblique standoff, not a nadir or an inside-out sphere', () => { @@ -212,52 +146,25 @@ test('every framing is a real oblique standoff, not a nadir or an inside-out sph for (const kind of kinds) { const framing = WORLD_FOCUS_FRAMING[kind]; // Looking DOWN at the target, but obliquely — a nadir drop reads as a map. - assert.ok( - framing.pitchDeg < 0, - `${kind}: pitch must look down, got ${framing.pitchDeg}`, - ); - assert.ok( - framing.pitchDeg > -80, - `${kind}: pitch must stay oblique, got ${framing.pitchDeg}`, - ); + assert.ok(framing.pitchDeg < 0, `${kind}: pitch must look down, got ${framing.pitchDeg}`); + assert.ok(framing.pitchDeg > -80, `${kind}: pitch must stay oblique, got ${framing.pitchDeg}`); // Standing off outside the framed sphere, at a human-readable distance. assert.ok(framing.radiusM > 0, `${kind}: radius must be positive`); assert.ok( framing.rangeM > framing.radiusM, `${kind}: range ${framing.rangeM} must stand off outside radius ${framing.radiusM}`, ); - assert.ok( - framing.rangeM <= 10000, - `${kind}: range must stay close-in, got ${framing.rangeM}`, - ); + assert.ok(framing.rangeM <= 10000, `${kind}: range must stay close-in, got ${framing.rangeM}`); } // Exact shipped values — a silent retune must show up as a failing test. - assert.deepEqual( - { ...WORLD_FOCUS_FRAMING.vessel }, - { radiusM: 150, rangeM: 1200, pitchDeg: -30 }, - ); - assert.deepEqual( - { ...WORLD_FOCUS_FRAMING.fire }, - { radiusM: 400, rangeM: 3000, pitchDeg: -35 }, - ); + assert.deepEqual({ ...WORLD_FOCUS_FRAMING.vessel }, { radiusM: 150, rangeM: 1200, pitchDeg: -30 }); + assert.deepEqual({ ...WORLD_FOCUS_FRAMING.fire }, { radiusM: 400, rangeM: 3000, pitchDeg: -35 }); }); test('unknown kinds and missing viewers issue no flight', () => { const camera = stubCamera(); - assert.equal( - flyToWorldTarget(bindFakeCesiumCamera({ camera }), { - kind: 'plane', - position: POSITION, - }), - false, - ); - assert.equal( - flyToWorldTarget(bindFakeCesiumCamera({ camera }), { kind: 'vessel' }), - false, - ); - assert.equal( - flyToWorldTarget(null, { kind: 'vessel', position: POSITION }), - false, - ); + assert.equal(flyToWorldTarget(bindFakeCesiumCamera({ camera }), { kind: 'plane', position: POSITION }), false); + assert.equal(flyToWorldTarget(bindFakeCesiumCamera({ camera }), { kind: 'vessel' }), false); + assert.equal(flyToWorldTarget(null, { kind: 'vessel', position: POSITION }), false); assert.equal(camera.calls.flights.length, 0); });