gods-eye-view/scripts/qa-radio.mjs

4626 lines
225 KiB
JavaScript

#!/usr/bin/env node
/**
* Deterministic browser proof for the Radio companion layer.
*
* Intercepts only `/api/radio/*` with a 750-station fixture and stubs the
* browser media `play()` primitive. It proves marker scale/culling, dynamic
* station-tag filtering, first-click selection, direct-action-only playback, panel/Context
* independence, restoration without autoplay, responsive UI, and a clean
* console. Screenshots are written under the gitignored `qa-shots/radio/`.
*
* Run: node scripts/qa-radio.mjs --url http://localhost:4173
*/
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import puppeteer from 'puppeteer';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = path.resolve(__dirname, '..');
const SHOTS_DIR = path.join(REPO_ROOT, 'qa-shots', 'radio');
const args = process.argv.slice(2);
const option = (name, fallback) => {
const index = args.indexOf(name);
return index >= 0 && args[index + 1] ? args[index + 1] : fallback;
};
const APP_URL = option('--url', process.env.QA_BASE_URL || 'http://localhost:4173');
const APP_ORIGIN = new URL(APP_URL).origin;
const HEADFUL = args.includes('--headful');
const chromeCandidates = [
process.env.PUPPETEER_EXECUTABLE_PATH,
(() => { try { return puppeteer.executablePath(); } catch { return null; } })(),
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
'/Applications/Chromium.app/Contents/MacOS/Chromium',
].filter(Boolean);
const chrome = chromeCandidates.find((candidate) => {
try { return fs.existsSync(candidate); } catch { return false; }
});
const tags = [
'news', 'talk', 'weather,emergency', 'public safety,scanner',
'aviation,atc', 'marine,maritime', 'traffic,transit',
'music,jazz', 'music,rock', 'community',
];
const stations = Array.from({ length: 750 }, (_, index) => {
const row = Math.floor(index / 30);
const column = index % 30;
return {
id: `00000000-0000-4000-8000-${index.toString(16).padStart(12, '0')}`,
name: index === 360
? '100.3 The River - WQRV - Meridianville/Huntsville, AL'
: index === 390
? 'La Zeta (Hermosillo) - 93.9 FM - XHHY - Uniradio - Hermosillo, Sonora'
: `QA Radio ${String(index + 1).padStart(3, '0')}`,
lat: -72 + row * 6,
lon: -174 + column * 12,
streamUrl: `https://audio.example.test/${index}.mp3`,
homepage: `https://station.example.test/${index}`,
tags: tags[index % tags.length].split(','),
languages: index === 360 ? ['Spanish'] : ['English'],
state: `Region ${row + 1}`,
country: 'United States',
countryCode: 'US',
metadataTrust: 'untrusted-community',
codec: index % 2 ? 'AAC' : 'MP3',
bitrate: 128,
};
});
const results = [];
function check(name, ok, detail = '') {
results.push({ name, ok });
const label = ok ? '\x1b[32mPASS\x1b[0m' : '\x1b[31mFAIL\x1b[0m';
console.log(` [${label}] ${name}${detail ? `${detail}` : ''}`);
}
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function main() {
const response = await fetch(APP_URL).catch(() => null);
if (!response?.ok) {
console.error(`Dev server not reachable at ${APP_URL}`);
process.exit(2);
}
fs.mkdirSync(SHOTS_DIR, { recursive: true });
const browser = await puppeteer.launch({
headless: HEADFUL ? false : 'new',
...(chrome ? { executablePath: chrome } : {}),
args: [
'--no-sandbox', '--disable-setuid-sandbox', '--use-gl=angle',
'--disable-dev-shm-usage', '--disable-background-timer-throttling',
'--disable-renderer-backgrounding', '--window-size=1440,900',
],
});
try {
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
await page.evaluateOnNewDocument(() => {
window.__qaRadioPlayCalls = [];
window.__qaRadioFailedNearest = false;
window.__qaRadioDelayNextPlay = false;
window.__qaRadioDelayNextFailure = false;
window.__qaRadioFailNextPlay = false;
HTMLMediaElement.prototype.play = function play() {
window.__qaRadioPlayCalls.push(this.src);
if (window.__qaRadioDelayNextPlay) {
window.__qaRadioDelayNextPlay = false;
return new Promise((resolve) => {
window.__qaReleaseRadioPlay = () => {
this.dispatchEvent(new Event('playing'));
resolve();
delete window.__qaReleaseRadioPlay;
};
});
}
if (window.__qaRadioDelayNextFailure) {
window.__qaRadioDelayNextFailure = false;
return new Promise((resolve, reject) => {
window.__qaRejectRadioPlay = () => {
reject(new DOMException('QA delayed broadcaster failure', 'NotSupportedError'));
delete window.__qaRejectRadioPlay;
};
});
}
if (window.__qaRadioFailNextPlay) {
window.__qaRadioFailNextPlay = false;
return Promise.reject(new DOMException('QA silent broadcaster', 'NotSupportedError'));
}
if (this.src.endsWith('/330.mp3') && !window.__qaRadioFailedNearest) {
window.__qaRadioFailedNearest = true;
return Promise.reject(new DOMException('QA broadcaster unavailable', 'NotSupportedError'));
}
queueMicrotask(() => this.dispatchEvent(new Event('playing')));
return Promise.resolve();
};
});
await page.setRequestInterception(true);
let catalogDegraded = false;
let catalogStations = stations;
let catalogGeneration = 1;
let catalogResponseDelayMs = 0;
page.on('request', (request) => {
const url = new URL(request.url());
if (url.origin === APP_ORIGIN && url.pathname === '/api/radio/stations') {
const response = {
status: 200,
contentType: 'application/json',
body: JSON.stringify({
stations: catalogStations,
updatedAt: new Date().toISOString(),
stale: false,
degraded: catalogDegraded,
acceptedGeneration: catalogGeneration,
catalogInstance: 'qa-harness-instance',
}),
};
const delayMs = catalogResponseDelayMs;
catalogResponseDelayMs = 0;
if (delayMs > 0) setTimeout(() => void request.respond(response), delayMs);
else void request.respond(response);
return;
}
if (url.origin === APP_ORIGIN && /^\/api\/radio\/click\/[^/]+$/.test(url.pathname)) {
request.respond({ status: 204 });
return;
}
if (url.origin === APP_ORIGIN && url.pathname === '/api/openai/hud-summary') {
request.respond({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ summary: 'QA globe ready' }),
});
return;
}
if (url.origin === APP_ORIGIN && url.pathname === '/api/ais-live') {
request.respond({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
rows: [],
source: 'AISStream QA',
status: 'open',
error: null,
refreshing: false,
newestPositionAt: null,
lastMessageAt: null,
}),
});
return;
}
request.continue();
});
const consoleErrors = [];
const failedResponses = [];
const externalCesiumEndpointFailures = [];
page.on('console', (message) => {
if (message.type() === 'error') {
const sourceUrl = message.location()?.url || '';
consoleErrors.push(sourceUrl ? `${message.text()} [${sourceUrl}]` : message.text());
}
});
page.on('pageerror', (error) => consoleErrors.push(error.message));
page.on('requestfailed', (request) => {
const url = request.url();
if (/^https:\/\/api\.cesium\.com\/v1\/assets\/1\/endpoint(?:\?|$)/.test(url)) {
externalCesiumEndpointFailures.push(`${request.failure()?.errorText || 'request failed'} ${url}`);
}
});
page.on('response', (response) => {
if (response.status() >= 500) {
failedResponses.push(`HTTP ${response.status()} ${response.url()}`);
}
});
await page.goto(APP_URL, { waitUntil: 'domcontentloaded', timeout: 60_000 });
await page.waitForFunction(() => window.__godsEyeView?.dataManager, { timeout: 60_000 });
await page.waitForFunction(() => window.__godsEyeView?.styleManager?._dataManager?.layers?.has('radio'), { timeout: 60_000 });
await page.waitForFunction(
() => document.getElementById('loading-screen')?.classList.contains('hidden'),
{ timeout: 60_000 },
);
await page.waitForFunction(() => (
typeof window.__gevQaRegisterLayer === 'function'
&& typeof window.__gevQaUnregisterLayer === 'function'
));
await page.evaluate(() => {
window.__godsEyeView.viewer.camera.cancelFlight();
window.__godsEyeView.styleManager.setPanelCollapsed('pp-toggles', true);
window.__godsEyeView.styleManager.setPanelCollapsed('cctv-panel', true);
window.__godsEyeView.styleManager.setPanelCollapsed('radio-panel', true);
window.__godsEyeView.styleManager.setPanelCollapsed('global-context-panel', true);
});
const enableStartedAt = Date.now();
const initialDisclosure = await page.evaluate(() => {
const button = document.getElementById('context-radio-toggle-btn');
const mini = document.getElementById('context-radio-mini');
const enabledBefore = window.__godsEyeView.dataManager.isEnabled('radio');
button.click();
return {
enabledBefore,
enabledAfterDisclosure: window.__godsEyeView.dataManager.isEnabled('radio'),
expanded: button.getAttribute('aria-expanded'),
miniHidden: mini.hidden,
};
});
check(
'Radio launcher discloses compact controls without changing power',
!initialDisclosure.enabledBefore && !initialDisclosure.enabledAfterDisclosure
&& initialDisclosure.expanded === 'true' && !initialDisclosure.miniHidden,
JSON.stringify(initialDisclosure),
);
await page.$eval('#context-radio-mini-enable-btn', (button) => button.click());
await page.waitForFunction(() => {
const state = window.__godsEyeView.dataManager.layers.get('radio').module.getUIState();
return state.enabled && !state.loading && state.stationCount === 750;
});
const enableElapsedMs = Date.now() - enableStartedAt;
const initial = await page.evaluate(() => {
const module = window.__godsEyeView.dataManager.layers.get('radio').module;
const state = module.getUIState();
const context = document.getElementById('global-context-panel');
const radio = document.getElementById('radio-panel');
return {
state,
plays: window.__qaRadioPlayCalls.length,
panelExpanded: !radio.classList.contains('collapsed'),
contextExpanded: !context.classList.contains('collapsed'),
nestedInContext: context.contains(radio),
filterOptions: document.getElementById('radio-filter').options.length,
filterPalette: Array.from(document.getElementById('radio-filter').options).map((entry) => ({
id: entry.value,
label: entry.textContent,
color: entry.dataset.radioColor,
})),
enablePressed: document.getElementById('radio-enable-btn').getAttribute('aria-pressed'),
launcherExpanded: document.getElementById('context-radio-toggle-btn').getAttribute('aria-expanded'),
launcherActive: document.getElementById('context-radio-toggle-btn').classList.contains('active'),
compactEnablePressed: document.getElementById('context-radio-mini-enable-btn').getAttribute('aria-pressed'),
fullPlayEnabled: !document.getElementById('radio-play-btn').disabled,
fullPlayLabel: document.getElementById('radio-play-btn').getAttribute('aria-label'),
tunerVisible: !document.getElementById('radio-tuner').hidden,
tunerCount: window.__godsEyeView.styleManager._radioTunerStations.length,
tunerLabel: document.getElementById('radio-tuner-band-label').textContent,
};
});
check('750-station catalog renders within the browser budget', initial.state.stationCount === 750 && enableElapsedMs < 5000, `${enableElapsedMs}ms`);
check('enable does not autoplay', initial.plays === 0 && initial.state.audioState === 'stopped');
check(
'compact and detailed Radio power controls reflect the layer manager independently of disclosure',
initial.enablePressed === 'true' && initial.compactEnablePressed === 'true'
&& initial.launcherExpanded === 'true' && initial.launcherActive,
);
check('dynamic canonical and genre categories reach the UI', initial.filterOptions >= 10, `${initial.filterOptions} options`);
const musicOption = initial.filterPalette.find((entry) => entry.id === 'music');
const newsOption = initial.filterPalette.find((entry) => entry.id === 'news');
check(
'station-tag options show category-colored circles before their labels',
initial.filterPalette.every((entry) => entry.label.startsWith('● '))
&& musicOption?.color === '#54d17a'
&& newsOption?.color === '#44adff',
JSON.stringify({ musicOption, newsOption }),
);
check('All is the initial Radio filter', initial.state.filter === 'all' && initial.state.filteredCount === 750, `${initial.state.filteredCount} stations`);
check(
'the default All filter exposes a draggable directory tuner band',
initial.tunerVisible && initial.tunerCount === 750 && initial.tunerLabel === 'DIRECTORY BAND',
JSON.stringify({ visible: initial.tunerVisible, count: initial.tunerCount, label: initial.tunerLabel }),
);
const uncertainPresentation = await page.evaluate(async () => {
const gev = window.__godsEyeView;
const style = gev.styleManager;
const entry = gev.dataManager.layers.get('radio');
const module = entry.module;
const stationId = module.getTunerStations(1)[0]?.id || null;
const selectedBefore = module.getUIState().selected?.id || null;
const playsBefore = window.__qaRadioPlayCalls.length;
const camera = gev.viewer.camera;
const originalFlyTo = camera.flyTo;
const flyToCalls = [];
camera.flyTo = (options) => flyToCalls.push(options);
const beganTuning = module.beginTuning();
module.setTuningStatic(false);
entry.lifecycleUncertain = true;
module.setLifecyclePresentation({
lifecycleState: 'enabled',
enabled: true,
uncertain: true,
});
gev.dataManager._refreshTogglePanel();
const staticResult = module.setTuningStatic(true);
const filterBefore = module.getUIState().filter;
const filterControl = document.getElementById('radio-filter');
const originalPinned = style._radioTunerBandPinnedForNavigation;
const originalPool = style._radioTunerPool;
style._radioTunerBandPinnedForNavigation = true;
filterControl.value = filterBefore === 'news' ? 'all' : 'news';
filterControl.dispatchEvent(new Event('change', { bubbles: true }));
const rejectedUIFilter = {
moduleFilter: module.getUIState().filter,
controlFilter: filterControl.value,
pinned: style._radioTunerBandPinnedForNavigation,
poolIdentityPreserved: style._radioTunerPool === originalPool,
};
style._radioTunerBandPinnedForNavigation = originalPinned;
style._radioTunerPool = originalPool;
const filterResult = module.setFilter('news');
const toggleResult = await module.togglePlayback({ origin: 'user' });
const directSelect = module.selectStation(stationId, {
autoplay: true,
origin: 'voice',
});
const directPlay = await module.play({ origin: 'voice' });
const directCycle = module.cycleStation(1, {
autoplay: false,
rotate: true,
stationIds: module.getTunerStations(3).map(({ id }) => id),
});
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
const result = {
presentationActive: module.getUIState().presentationActive,
markerSourceVisible: gev.viewer.dataSources.getByName('Radio stations')[0]?.show,
tunerHidden: document.getElementById('radio-tuner').hidden,
filterDisabled: document.getElementById('radio-filter').disabled,
prevDisabled: document.getElementById('radio-prev-btn').disabled,
playDisabled: document.getElementById('radio-play-btn').disabled,
volumeDisabled: document.getElementById('radio-volume').disabled,
disclosureLabel: document.getElementById('context-radio-toggle-btn').getAttribute('aria-label'),
compactPowerLabel: document.getElementById('context-radio-mini-enable-btn').getAttribute('aria-label'),
compactPowerText: document.getElementById('context-radio-mini-enable-btn').textContent,
fullLabel: document.getElementById('radio-enable-btn').getAttribute('aria-label'),
fullText: document.getElementById('radio-enable-btn').textContent,
layerState: document.getElementById('radio-layer-state').textContent,
playbackMessage: document.getElementById('radio-playback-state').textContent,
miniStation: document.getElementById('context-radio-mini-station').textContent,
dataRailText: document.querySelector('[data-layer-id="radio"] .data-toggle-btn')?.textContent,
dataRailState: document.querySelector('[data-layer-id="radio"] .data-toggle-btn')?.dataset.feedState,
dataRailLabel: document.querySelector('[data-layer-id="radio"] .data-toggle-btn')?.getAttribute('aria-label'),
dataRailMeta: document.querySelector('[data-layer-id="radio"] .data-toggle-meta')?.textContent,
directSelect,
directPlay,
directCycle,
beganTuning,
staticResult,
tuningActive: module.getUIState().tuningActive,
tuningStatic: module.getUIState().tuningStatic,
filterBefore,
rejectedUIFilter,
filterResult,
filterAfter: module.getUIState().filter,
toggleResult,
flyToCalls: flyToCalls.length,
selectedBefore,
selectedAfter: module.getUIState().selected?.id || null,
playDelta: window.__qaRadioPlayCalls.length - playsBefore,
};
entry.lifecycleUncertain = false;
module.setLifecyclePresentation({
lifecycleState: 'enabled',
enabled: true,
uncertain: false,
});
gev.dataManager._refreshTogglePanel();
module.cancelTuning();
camera.flyTo = originalFlyTo;
return result;
});
check(
'uncertain settled-ON Radio remains hidden and inert until manager reconciliation',
uncertainPresentation.presentationActive === false
&& uncertainPresentation.markerSourceVisible === false
&& uncertainPresentation.tunerHidden
&& uncertainPresentation.filterDisabled
&& uncertainPresentation.prevDisabled
&& uncertainPresentation.playDisabled
&& uncertainPresentation.volumeDisabled
&& /compact Radio controls/i.test(uncertainPresentation.disclosureLabel)
&& /uncertain/i.test(uncertainPresentation.compactPowerLabel)
&& uncertainPresentation.compactPowerText === 'RECONCILE'
&& /uncertain/i.test(uncertainPresentation.fullLabel)
&& uncertainPresentation.fullText === 'RECONCILE'
&& uncertainPresentation.layerState === 'UNCERTAIN'
&& /uncertain/i.test(uncertainPresentation.playbackMessage)
&& uncertainPresentation.miniStation === 'RADIO STATE UNCERTAIN'
&& uncertainPresentation.dataRailText === 'UNCERTAIN'
&& uncertainPresentation.dataRailState === 'uncertain'
&& /uncertain/i.test(uncertainPresentation.dataRailLabel)
&& /uncertain/i.test(uncertainPresentation.dataRailMeta)
&& uncertainPresentation.directSelect === false
&& uncertainPresentation.directPlay === false
&& uncertainPresentation.directCycle === false
&& uncertainPresentation.beganTuning
&& uncertainPresentation.staticResult === false
&& uncertainPresentation.tuningActive
&& uncertainPresentation.tuningStatic === false
&& uncertainPresentation.filterResult === false
&& uncertainPresentation.filterAfter === uncertainPresentation.filterBefore
&& uncertainPresentation.rejectedUIFilter.moduleFilter === uncertainPresentation.filterBefore
&& uncertainPresentation.rejectedUIFilter.controlFilter === uncertainPresentation.filterBefore
&& uncertainPresentation.rejectedUIFilter.pinned
&& uncertainPresentation.rejectedUIFilter.poolIdentityPreserved
&& uncertainPresentation.toggleResult === false
&& uncertainPresentation.flyToCalls === 0
&& uncertainPresentation.selectedAfter === uncertainPresentation.selectedBefore
&& uncertainPresentation.playDelta === 0,
JSON.stringify(uncertainPresentation),
);
await page.evaluate(() => {
const gev = window.__godsEyeView;
const module = gev.dataManager.layers.get('radio').module;
const originalDisable = module.disable.bind(module);
module.disable = async (...args) => {
window.__qaRadioDisableStarted = true;
await new Promise((resolve) => { window.__qaReleaseRadioDisable = resolve; });
module.disable = originalDisable;
return originalDisable(...args);
};
window.__qaRadioDisablePromise = gev.dataManager.setEnabled('radio', false, { origin: 'qa-setup' });
});
await page.waitForFunction(() => window.__qaRadioDisableStarted === true);
const disablingPresentation = await page.evaluate(() => ({
manager: window.__godsEyeView.dataManager.getLayerLifecycleState('radio'),
presentationActive: window.__godsEyeView.dataManager.layers.get('radio').module.getUIState().presentationActive,
markerSourceVisible: window.__godsEyeView.viewer.dataSources.getByName('Radio stations')[0]?.show,
full: document.getElementById('radio-enable-btn').textContent,
disclosure: document.getElementById('context-radio-toggle-btn').getAttribute('aria-label'),
disclosureControls: document.getElementById('context-radio-toggle-btn').getAttribute('aria-controls'),
disclosureExpanded: document.getElementById('context-radio-toggle-btn').getAttribute('aria-expanded'),
compactPower: document.getElementById('context-radio-mini-enable-btn').textContent,
layerState: document.getElementById('radio-layer-state').textContent,
message: document.getElementById('radio-playback-state').textContent,
stopDisabled: document.getElementById('radio-stop-btn').disabled,
volumeDisabled: document.getElementById('radio-volume').disabled,
}));
await page.screenshot({ path: path.join(SHOTS_DIR, 'lifecycle-disabling.png') });
check(
'Radio teardown exposes DISABLING while settled visibility remains ON',
disablingPresentation.manager.enabled === true
&& disablingPresentation.manager.lifecycleState === 'disabling'
&& disablingPresentation.presentationActive === false
&& disablingPresentation.markerSourceVisible === false
&& disablingPresentation.full === 'DISABLING'
&& /compact Radio controls/i.test(disablingPresentation.disclosure)
&& disablingPresentation.compactPower === 'DISABLING'
&& disablingPresentation.layerState === 'DISABLING'
&& /disabling/i.test(disablingPresentation.message)
&& disablingPresentation.stopDisabled
&& disablingPresentation.volumeDisabled,
JSON.stringify(disablingPresentation),
);
await page.evaluate(async () => {
window.__qaReleaseRadioDisable();
await window.__qaRadioDisablePromise;
delete window.__qaReleaseRadioDisable;
delete window.__qaRadioDisablePromise;
delete window.__qaRadioDisableStarted;
});
const explicitRevealBefore = await page.evaluate(async () => {
const gev = window.__godsEyeView;
gev.styleManager.setPanelCollapsed('global-context-panel', false);
gev.styleManager.setPanelCollapsed('radio-panel', false);
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
const scroller = document.querySelector('#global-context-panel .global-context-panel-inner');
const radio = document.getElementById('radio-panel');
scroller.scrollTop = Math.max(0, radio.offsetTop - scroller.clientHeight + 70);
const camera = gev.viewer.camera.positionWC;
return {
scrollTop: scroller.scrollTop,
pageY: window.scrollY,
camera: { x: camera.x, y: camera.y, z: camera.z },
plays: window.__qaRadioPlayCalls.length,
};
});
await page.screenshot({ path: path.join(SHOTS_DIR, 'explicit-enable-before.png') });
// A warm accepted catalog makes re-enable legitimately synchronous: there
// may be no station request for the interception delay below to hold. Gate
// the real module enable boundary instead so the manager's ENABLING state
// is observed deterministically without slowing production behavior.
await page.evaluate(() => {
const radio = window.__godsEyeView.dataManager.layers.get('radio').module;
const originalEnable = radio.enable;
let release;
const gate = new Promise((resolve) => { release = resolve; });
radio.enable = async function qaGatedRadioEnable(...args) {
await gate;
return originalEnable.apply(this, args);
};
window.__qaReleaseRadioEnable = () => release();
window.__qaRestoreRadioEnable = () => { radio.enable = originalEnable; };
});
await page.focus('#radio-enable-btn');
await page.click('#radio-enable-btn');
await page.waitForFunction(() => (
window.__godsEyeView.dataManager.getLayerLifecycleState('radio')?.lifecycleState === 'enabling'
));
const enablingPresentation = await page.evaluate(() => ({
manager: window.__godsEyeView.dataManager.getLayerLifecycleState('radio'),
presentationActive: window.__godsEyeView.dataManager.layers.get('radio').module.getUIState().presentationActive,
markerSourceVisible: window.__godsEyeView.viewer.dataSources.getByName('Radio stations')[0]?.show,
full: document.getElementById('radio-enable-btn').textContent,
disclosure: document.getElementById('context-radio-toggle-btn').getAttribute('aria-label'),
disclosureControls: document.getElementById('context-radio-toggle-btn').getAttribute('aria-controls'),
disclosureExpanded: document.getElementById('context-radio-toggle-btn').getAttribute('aria-expanded'),
compactPower: document.getElementById('context-radio-mini-enable-btn').textContent,
layerState: document.getElementById('radio-layer-state').textContent,
message: document.getElementById('radio-playback-state').textContent,
tunerHidden: document.getElementById('radio-tuner').hidden,
stopDisabled: document.getElementById('radio-stop-btn').disabled,
volumeDisabled: document.getElementById('radio-volume').disabled,
}));
await page.screenshot({ path: path.join(SHOTS_DIR, 'lifecycle-enabling.png') });
check(
'Radio activation exposes ENABLING while settled visibility remains OFF',
enablingPresentation.manager.enabled === false
&& enablingPresentation.manager.lifecycleState === 'enabling'
&& enablingPresentation.presentationActive === false
&& enablingPresentation.markerSourceVisible === false
&& enablingPresentation.full === 'ENABLING'
&& enablingPresentation.disclosure === 'Go to expanded Radio section'
&& enablingPresentation.disclosureControls === 'radio-panel'
&& enablingPresentation.disclosureExpanded === 'true'
&& enablingPresentation.compactPower === 'ENABLING'
&& enablingPresentation.layerState === 'ENABLING'
&& /enabling/i.test(enablingPresentation.message)
&& enablingPresentation.tunerHidden
&& enablingPresentation.stopDisabled
&& enablingPresentation.volumeDisabled,
JSON.stringify(enablingPresentation),
);
await page.evaluate(() => window.__qaReleaseRadioEnable?.());
await page.waitForFunction(() => window.__godsEyeView.dataManager.isEnabled('radio'));
await page.evaluate(() => window.__qaRestoreRadioEnable?.());
await sleep(650);
const explicitRevealAfter = await page.evaluate(() => {
const gev = window.__godsEyeView;
const scroller = document.querySelector('#global-context-panel .global-context-panel-inner');
const viewport = scroller.getBoundingClientRect();
const directory = document.querySelector('#radio-panel .radio-directory-row').getBoundingClientRect();
const play = document.getElementById('radio-play-btn').getBoundingClientRect();
const camera = gev.viewer.camera.positionWC;
return {
scrollTop: scroller.scrollTop,
pageY: window.scrollY,
camera: { x: camera.x, y: camera.y, z: camera.z },
directoryVisible: directory.top >= viewport.top && directory.bottom <= viewport.bottom,
playVisible: play.top >= viewport.top && play.bottom <= viewport.bottom,
focusId: document.activeElement?.id,
plays: window.__qaRadioPlayCalls.length,
audioState: gev.dataManager.layers.get('radio').module.getUIState().audioState,
};
});
await page.screenshot({ path: path.join(SHOTS_DIR, 'explicit-enable-after.png') });
const revealCameraDelta = Math.hypot(
explicitRevealAfter.camera.x - explicitRevealBefore.camera.x,
explicitRevealAfter.camera.y - explicitRevealBefore.camera.y,
explicitRevealAfter.camera.z - explicitRevealBefore.camera.z,
);
check(
'expanded Radio Enable reveals the directory and Play inside Context without autoplay or focus/camera theft',
explicitRevealAfter.scrollTop > explicitRevealBefore.scrollTop
&& explicitRevealAfter.directoryVisible && explicitRevealAfter.playVisible
&& explicitRevealAfter.focusId === 'radio-enable-btn'
&& explicitRevealAfter.pageY === explicitRevealBefore.pageY
&& revealCameraDelta < 0.01
&& explicitRevealAfter.plays === explicitRevealBefore.plays
&& explicitRevealAfter.audioState === 'stopped',
JSON.stringify({ before: explicitRevealBefore, after: explicitRevealAfter, cameraDelta: revealCameraDelta }),
);
await page.evaluate(() => {
window.__godsEyeView.styleManager.setPanelCollapsed('radio-panel', true);
window.__godsEyeView.styleManager.setPanelCollapsed('global-context-panel', true);
});
const singletonViews = [];
const singletonViewSpecs = [
{ category: 'news', lon: -174 },
{ category: 'public-safety', lon: -138 },
{ category: 'weather', lon: -150 },
{ category: 'all', lon: -174 },
];
for (const spec of singletonViewSpecs) {
await page.select('#radio-filter', spec.category);
for (const view of [
{ name: 'global', height: 24_200_000, limit: 16 },
{ name: 'near', height: 500_000, limit: 32 },
]) {
await page.evaluate(({ lon, height }) => {
const viewer = window.__godsEyeView.viewer;
viewer.camera.cancelFlight();
viewer.camera.setView({
destination: viewer.scene.globe.ellipsoid.cartographicToCartesian({
longitude: lon * Math.PI / 180,
latitude: 6 * Math.PI / 180,
height,
}),
orientation: { heading: 0, pitch: -Math.PI / 2, roll: 0 },
});
viewer.scene.requestRender();
}, { lon: spec.lon, height: view.height });
await sleep(700);
const sample = await page.evaluate(async () => {
const viewer = window.__godsEyeView.viewer;
const radio = window.__godsEyeView.dataManager.layers.get('radio').module;
const dataSource = Array.from({ length: viewer.dataSources.length }, (_, index) => viewer.dataSources.get(index))
.find((item) => item.name === 'Radio stations');
const { getOverlayPaintRect, getWorldOverlayDiagnostics } = await import('/src/overlays/worldOverlay.js');
const { radioStationIdFromPick } = await import('/src/data/radio.js');
return new Promise((resolve) => {
let removePostRender = null;
const finish = () => {
removePostRender?.();
const overlay = radio.getOverlayDiagnostics();
const paintedSingletonIds = overlay.singletonIds.filter((entryId) => Boolean(getOverlayPaintRect('radio', entryId)));
const pickableSingletonIds = paintedSingletonIds.filter((entryId) => {
const rect = getOverlayPaintRect('radio', entryId);
const anchor = rect?.entry?.position
&& viewer.scene.cartesianToCanvasCoordinates(rect.entry.position);
if (!anchor) return false;
return (viewer.scene.drillPick(anchor, 16) || [])
.some((picked) => radioStationIdFromPick(picked) === entryId.slice('station:'.length));
});
resolve({
overlay,
host: getWorldOverlayDiagnostics(),
paintedSingletonIds,
pickableSingletonIds,
nativeLabelCount: dataSource.entities.values.filter((entity) => Boolean(entity.label)).length,
});
};
const timeout = setTimeout(finish, 500);
removePostRender = viewer.scene.postRender.addEventListener(() => {
clearTimeout(timeout);
finish();
});
viewer.scene.requestRender();
});
});
singletonViews.push({ ...spec, ...view, ...sample });
await page.screenshot({
path: path.join(SHOTS_DIR, `singleton-${spec.category}-${view.name}.png`),
});
}
}
const sparseSingletonViews = singletonViews.filter((sample) => sample.category !== 'all');
check(
'sparse Radio filters paint bounded individual labels through the shared ambient lane',
sparseSingletonViews.every((sample) => (
sample.overlay.singletonIds.length > 0
&& sample.overlay.singletonIds.length <= sample.limit
&& sample.paintedSingletonIds.length > 0
&& sample.pickableSingletonIds.length === sample.paintedSingletonIds.length
&& sample.host.entriesBySource?.radio === sample.overlay.entryCount
)),
JSON.stringify(sparseSingletonViews.map((sample) => ({
category: sample.category,
view: sample.name,
singletonCount: sample.overlay.singletonIds.length,
painted: sample.paintedSingletonIds.length,
pickable: sample.pickableSingletonIds.length,
}))),
);
check(
'Radio label allocation stays bounded with no native Cesium entity text across sparse and All views',
singletonViews.every((sample) => (
sample.overlay.entryCount <= 65
&& sample.nativeLabelCount === 0
&& sample.host.paintedBySource?.radio > 0
)),
JSON.stringify(singletonViews.map((sample) => ({
category: sample.category,
view: sample.name,
entries: sample.overlay.entryCount,
nativeLabels: sample.nativeLabelCount,
painted: sample.host.paintedBySource?.radio,
}))),
);
const userFacingFailures = await page.evaluate(async () => {
const gev = window.__godsEyeView;
const styleManager = gev.styleManager;
const dataManager = gev.dataManager;
const unhandled = [];
const onUnhandled = (event) => {
unhandled.push(String(event.reason?.message || event.reason || 'unknown'));
event.preventDefault();
};
window.addEventListener('unhandledrejection', onUnhandled);
const originalToggle = dataManager.toggle;
const originalSetEnabled = dataManager.setEnabled;
const originalRestoreEnabledLayerIds = dataManager.restoreEnabledLayerIds;
const originalIsEnabled = dataManager.isEnabled;
const originalIsEffectivelyEnabled = dataManager.isEffectivelyEnabled;
const originalClear = styleManager._clearLayersOutsideContextMode;
const originalCapture = styleManager._captureContextSessionSnapshot;
const originalContextMode = styleManager._contextMode;
const originalSnapshot = styleManager._contextSessionSnapshot;
const originalShowToast = styleManager._showToast;
const installations = dataManager.layers.get('military-installations')?.module;
const originalSearchNearby = installations?.searchNearby;
try {
dataManager.setEnabled = async () => false;
document.getElementById('radio-enable-btn').click();
await new Promise((resolve) => setTimeout(resolve, 50));
const semanticFalseMessage = document.getElementById('toast').textContent;
const enabled = new Set();
styleManager._captureContextSessionSnapshot = () => {};
const runContextFailure = async (mode, phase, outcome) => {
enabled.clear();
enabled.add('local-datacenters');
styleManager._contextMode = null;
styleManager._contextSessionSnapshot = {
enabledLayerIds: new Set(enabled),
userAdded: new Set(),
userRemoved: new Set(),
params: {},
};
let failureUsed = false;
const entryLayerId = mode === 'flights' ? 'military-awareness' : 'rocket-launches';
dataManager.setEnabled = async (layerId, shouldEnable) => {
const failsIsolation = phase === 'isolation' && layerId === 'local-datacenters' && !shouldEnable;
const failsActivation = phase === 'activation' && layerId === entryLayerId && shouldEnable;
if (!failureUsed && (failsIsolation || failsActivation)) {
failureUsed = true;
if (outcome === 'reject') throw new Error(`QA ${phase} rejection`);
return false;
}
shouldEnable ? enabled.add(layerId) : enabled.delete(layerId);
return true;
};
const result = await styleManager._runUserFacingContextAction(
() => styleManager._selectContextMode(mode),
`${mode} QA transition failed`,
);
await new Promise((resolve) => setTimeout(resolve, 0));
return {
mode,
phase,
outcome,
result,
visibleMode: styleManager._contextMode,
snapshotCleared: styleManager._contextSessionSnapshot === null,
priorLayerRestored: enabled.has('local-datacenters'),
entryEnabled: enabled.has(entryLayerId),
flightsButtonDisabled: document.getElementById('global-context-flights-btn').disabled,
missionsButtonDisabled: document.getElementById('global-context-missions-btn').disabled,
message: document.getElementById('toast').textContent,
};
};
dataManager.isEnabled = (layerId) => enabled.has(layerId);
// The fake manager model must answer the effective-visibility accessor
// the isolation loop consults, or real entries (all disabled) leak in.
dataManager.isEffectivelyEnabled = (layerId) => enabled.has(layerId);
dataManager.restoreEnabledLayerIds = async (enabledLayerIds, { excludeLayerIds = [] } = {}) => {
const target = new Set(enabledLayerIds || []);
const excluded = new Set(excludeLayerIds || []);
const layerIds = new Set([...enabled, ...target]);
for (const candidateId of layerIds) {
if (excluded.has(candidateId)) continue;
const restored = await dataManager.setEnabled(candidateId, target.has(candidateId));
if (restored === false) {
throw new Error(`Failed to restore layer "${candidateId}" visibility`);
}
}
};
const contextFailures = [];
for (const mode of ['flights', 'space-missions']) {
for (const phase of ['isolation', 'activation']) {
for (const outcome of ['false', 'reject']) {
contextFailures.push(await runContextFailure(mode, phase, outcome));
}
}
}
const directEntryFailures = [];
for (const layerId of ['military-awareness', 'rocket-launches']) {
for (const outcome of ['false', 'reject']) {
enabled.clear();
enabled.add('local-datacenters');
styleManager._contextMode = null;
styleManager._contextSessionSnapshot = {
enabledLayerIds: new Set(enabled),
userAdded: new Set(),
userRemoved: new Set(),
params: {},
};
let failureUsed = false;
const toastMessages = [];
styleManager._showToast = (message) => {
toastMessages.push(message);
originalShowToast.call(styleManager, message);
};
dataManager.setEnabled = async (candidateId, shouldEnable, options = {}) => {
if (!failureUsed && candidateId === 'local-datacenters' && !shouldEnable) {
failureUsed = true;
styleManager._handleContextLayerChange({
type: 'visibility-failed',
layerId: candidateId,
enabled: shouldEnable,
notificationToken: options.notificationToken,
origin: 'user',
});
if (outcome === 'reject') throw new Error('QA direct isolation rejection');
return false;
}
shouldEnable ? enabled.add(candidateId) : enabled.delete(candidateId);
return true;
};
const reason = await dataManager._visibilityBlockReason({
type: 'visibility-will-change',
layerId,
enabled: true,
origin: 'user',
});
styleManager._handleContextLayerChange({
type: 'visibility-blocked',
layerId,
enabled: true,
origin: 'user',
reason,
});
const priorLayerRestored = enabled.has('local-datacenters');
const retryReason = await dataManager._visibilityBlockReason({
type: 'visibility-will-change',
layerId,
enabled: true,
origin: 'user',
});
directEntryFailures.push({
layerId,
outcome,
reason,
retryReason,
toastMessages,
mode: styleManager._contextMode,
snapshotCleared: styleManager._contextSessionSnapshot === null,
priorLayerRestored,
modeChanging: styleManager._contextModeChanging,
});
}
}
styleManager._showToast = originalShowToast;
const directActivationFailures = [];
for (const layerId of ['military-awareness', 'rocket-launches']) {
enabled.clear();
styleManager._contextMode = null;
styleManager._contextSessionSnapshot = {
enabledLayerIds: new Set(['local-datacenters']),
userAdded: new Set(),
userRemoved: new Set(),
params: {},
};
dataManager.setEnabled = async (candidateId, shouldEnable) => {
shouldEnable ? enabled.add(candidateId) : enabled.delete(candidateId);
return true;
};
styleManager._handleContextLayerChange({
type: 'visibility-failed',
layerId,
enabled: true,
origin: 'user',
});
await new Promise((resolve) => setTimeout(resolve, 50));
directActivationFailures.push({
layerId,
mode: styleManager._contextMode,
snapshotCleared: styleManager._contextSessionSnapshot === null,
priorLayerRestored: enabled.has('local-datacenters'),
failedLayerDisabled: !enabled.has(layerId),
});
}
const directActivationRollbackFailures = [];
for (const layerId of ['military-awareness', 'rocket-launches']) {
enabled.clear();
styleManager._contextMode = null;
styleManager._contextSessionSnapshot = {
enabledLayerIds: new Set(['local-datacenters']),
userAdded: new Set(),
userRemoved: new Set(),
params: {},
};
const toastMessages = [];
const showToastBeforeRollbackFailure = styleManager._showToast;
styleManager._showToast = (message) => {
toastMessages.push(message);
showToastBeforeRollbackFailure.call(styleManager, message);
};
dataManager.setEnabled = async (candidateId, shouldEnable) => {
if (candidateId === 'local-datacenters' && shouldEnable) return false;
shouldEnable ? enabled.add(candidateId) : enabled.delete(candidateId);
return true;
};
styleManager._handleContextLayerChange({
type: 'visibility-failed',
layerId,
enabled: true,
origin: 'user',
});
await new Promise((resolve) => setTimeout(resolve, 50));
styleManager._showToast = showToastBeforeRollbackFailure;
directActivationRollbackFailures.push({
layerId,
toastMessages,
retryRetained: styleManager._contextSessionSnapshot?.enabledLayerIds?.has('local-datacenters') === true,
});
}
const contextExitFailures = [];
dataManager.setEnabled = originalSetEnabled;
dataManager.restoreEnabledLayerIds = originalRestoreEnabledLayerIds;
dataManager.isEnabled = originalIsEnabled;
dataManager.isEffectivelyEnabled = originalIsEffectivelyEnabled;
for (const outcome of ['false', 'reject']) {
const layerId = `qa-context-exit-${outcome}`;
let enableOutcome = outcome;
const toastMessages = [];
const qaLayer = {
id: layerId,
name: `QA Context exit ${outcome}`,
updateInterval: -1,
init: async () => true,
enable: async () => {
if (enableOutcome === 'reject') throw new Error(`QA Context exit ${outcome}`);
return enableOutcome !== 'false';
},
disable: async () => true,
update: async () => true,
destroy: async () => true,
getStats: () => ({}),
};
window.__gevQaRegisterLayer(dataManager, qaLayer);
try {
const enabledBeforeExit = dataManager.getEnabledLayerIds();
styleManager._contextMode = 'flights';
styleManager._contextSessionSnapshot = {
enabledLayerIds: new Set([...enabledBeforeExit, layerId]),
userAdded: new Set(),
userRemoved: new Set(),
params: {},
};
styleManager._showToast = (message) => {
toastMessages.push(message);
originalShowToast.call(styleManager, message);
};
const unhandledBefore = unhandled.length;
const result = await styleManager._runUserFacingContextAction(
(notificationToken) => styleManager._deactivateContextForLayerChange({ notificationToken }),
`QA Context exit ${outcome} surfaced once`,
);
const retainedForRetry = styleManager._contextSessionSnapshot?.enabledLayerIds?.has(layerId) === true;
enableOutcome = 'success';
const retryResult = await styleManager._runUserFacingContextAction(
(notificationToken) => styleManager._deactivateContextForLayerChange({ notificationToken }),
`QA Context exit ${outcome} retry failed`,
);
contextExitFailures.push({
outcome,
result,
retryResult: retryResult ?? true,
toastMessages,
unhandledDelta: unhandled.length - unhandledBefore,
retainedForRetry,
mode: styleManager._contextMode,
modeChanging: styleManager._contextModeChanging,
snapshotClearedAfterRetry: styleManager._contextSessionSnapshot === null,
enabledAfterRetry: dataManager.isEnabled(layerId),
toastRole: document.getElementById('toast').getAttribute('role'),
toastLive: document.getElementById('toast').getAttribute('aria-live'),
toastAtomic: document.getElementById('toast').getAttribute('aria-atomic'),
});
} finally {
styleManager._showToast = originalShowToast;
await window.__gevQaUnregisterLayer(dataManager, layerId);
}
}
const unrelatedLayerId = 'qa-unrelated-manager-failure';
const unrelatedToastMessages = [];
let releasePendingAction = null;
window.__gevQaRegisterLayer(dataManager, {
id: unrelatedLayerId,
name: 'QA unrelated manager failure',
updateInterval: -1,
init: async () => true,
enable: async () => false,
disable: async () => true,
update: async () => true,
destroy: async () => true,
getStats: () => ({}),
});
styleManager._showToast = (message) => {
unrelatedToastMessages.push(message);
originalShowToast.call(styleManager, message);
};
const pendingAction = styleManager._runUserFacingContextAction(() => (
new Promise((resolve) => { releasePendingAction = resolve; })
));
while (!releasePendingAction) await new Promise((resolve) => setTimeout(resolve, 0));
const unrelatedResult = await dataManager.setEnabled(unrelatedLayerId, true);
releasePendingAction(true);
await pendingAction;
styleManager._showToast = originalShowToast;
await window.__gevQaUnregisterLayer(dataManager, unrelatedLayerId);
const unrelatedManagerFailure = {
result: unrelatedResult,
toastMessages: unrelatedToastMessages,
};
dataManager.setEnabled = async (layerId, shouldEnable) => {
if (layerId === 'military-installations' && shouldEnable) return false;
return true;
};
document.getElementById('installations-search-btn').click();
await new Promise((resolve) => setTimeout(resolve, 50));
const installationFalseMessage = document.getElementById('toast').textContent;
const installationFalseReleased = !document.getElementById('installations-search-btn').disabled;
dataManager.setEnabled = async (layerId, shouldEnable) => {
if (layerId === 'military-installations' && shouldEnable) {
throw new Error('QA installation enable rejection');
}
return true;
};
document.getElementById('installations-search-btn').click();
await new Promise((resolve) => setTimeout(resolve, 50));
const installationEnableRejectionMessage = document.getElementById('toast').textContent;
const installationEnableRejectionReleased = !document.getElementById('installations-search-btn').disabled;
dataManager.setEnabled = async (layerId, shouldEnable) => {
if (layerId === 'military-installations' && shouldEnable) enabled.add(layerId);
return true;
};
if (installations) installations.searchNearby = async () => {
throw new Error('QA installation search rejection');
};
document.getElementById('installations-search-btn').click();
await new Promise((resolve) => setTimeout(resolve, 50));
const installationRejectionMessage = document.getElementById('toast').textContent;
const installationRejectionReleased = !document.getElementById('installations-search-btn').disabled;
if (installations) installations.searchNearby = async () => false;
document.getElementById('installations-search-btn').click();
await new Promise((resolve) => setTimeout(resolve, 50));
const installationSearchFalseMessage = document.getElementById('toast').textContent;
const installationSearchFalseReleased = !document.getElementById('installations-search-btn').disabled;
await new Promise((resolve) => setTimeout(resolve, 0));
return {
semanticFalseMessage,
contextFailures,
directEntryFailures,
directActivationFailures,
directActivationRollbackFailures,
contextExitFailures,
unrelatedManagerFailure,
installationFalseMessage,
installationFalseReleased,
installationEnableRejectionMessage,
installationEnableRejectionReleased,
installationRejectionMessage,
installationRejectionReleased,
installationSearchFalseMessage,
installationSearchFalseReleased,
unhandled,
};
} finally {
dataManager.toggle = originalToggle;
dataManager.setEnabled = originalSetEnabled;
dataManager.restoreEnabledLayerIds = originalRestoreEnabledLayerIds;
dataManager.isEnabled = originalIsEnabled;
dataManager.isEffectivelyEnabled = originalIsEffectivelyEnabled;
styleManager._clearLayersOutsideContextMode = originalClear;
styleManager._captureContextSessionSnapshot = originalCapture;
styleManager._contextMode = originalContextMode;
styleManager._contextSessionSnapshot = originalSnapshot;
styleManager._showToast = originalShowToast;
if (installations) installations.searchNearby = originalSearchNearby;
window.removeEventListener('unhandledrejection', onUnhandled);
}
});
check(
'user-facing chip, real Context entry rollback, and installation Search failures settle safely',
userFacingFailures.semanticFalseMessage === 'Radio could not stop cleanly'
&& userFacingFailures.contextFailures.length === 8
&& userFacingFailures.contextFailures.every((failure) => (
failure.result === false
&& failure.visibleMode === null
&& failure.snapshotCleared
&& failure.priorLayerRestored
&& !failure.entryEnabled
&& !failure.flightsButtonDisabled
&& !failure.missionsButtonDisabled
&& failure.message.includes('QA transition failed')
))
&& userFacingFailures.directEntryFailures.length === 4
&& userFacingFailures.directEntryFailures.every((failure) => (
failure.reason.includes('could not start')
&& failure.retryReason === null
&& failure.toastMessages.length === 1
&& failure.toastMessages[0] === failure.reason
&& failure.mode === null
&& failure.snapshotCleared
&& failure.priorLayerRestored
&& !failure.modeChanging
))
&& userFacingFailures.directActivationFailures.length === 2
&& userFacingFailures.directActivationFailures.every((failure) => (
failure.mode === null
&& failure.snapshotCleared
&& failure.priorLayerRestored
&& failure.failedLayerDisabled
))
&& userFacingFailures.directActivationRollbackFailures.length === 2
&& userFacingFailures.directActivationRollbackFailures.every((failure) => (
failure.toastMessages.length === 1
&& failure.toastMessages[0] === `${failure.layerId} could not start cleanly`
&& failure.retryRetained
))
&& userFacingFailures.contextExitFailures.length === 2
&& userFacingFailures.contextExitFailures.every((failure) => (
failure.result === false
&& failure.retryResult === true
&& failure.toastMessages.length === 1
&& failure.toastMessages[0].includes('surfaced once')
&& failure.unhandledDelta === 0
&& failure.retainedForRetry
&& failure.mode === null
&& !failure.modeChanging
&& failure.snapshotClearedAfterRetry
&& failure.enabledAfterRetry
&& failure.toastRole === 'status'
&& failure.toastLive === 'polite'
&& failure.toastAtomic === 'true'
))
&& userFacingFailures.unrelatedManagerFailure.result === false
&& userFacingFailures.unrelatedManagerFailure.toastMessages.length === 1
&& userFacingFailures.unrelatedManagerFailure.toastMessages[0].includes('qa-unrelated-manager-failure could not start cleanly')
&& userFacingFailures.installationFalseMessage.includes('could not be refreshed')
&& userFacingFailures.installationFalseReleased
&& userFacingFailures.installationEnableRejectionMessage.includes('could not be refreshed')
&& userFacingFailures.installationEnableRejectionReleased
&& userFacingFailures.installationRejectionMessage.includes('could not be refreshed')
&& userFacingFailures.installationRejectionReleased
&& userFacingFailures.installationSearchFalseMessage.includes('could not be refreshed')
&& userFacingFailures.installationSearchFalseReleased
&& userFacingFailures.unhandled.length === 0,
JSON.stringify(userFacingFailures),
);
catalogDegraded = true;
const degradedCatalog = await page.evaluate(async () => {
const radio = window.__godsEyeView.dataManager.layers.get('radio').module;
await radio.update();
return {
state: radio.getUIState(),
message: document.getElementById('radio-playback-state').textContent,
};
});
check(
'degraded Radio catalog remains usable and is labeled explicitly',
degradedCatalog.state.stationCount === 750
&& degradedCatalog.state.degraded === true
&& degradedCatalog.state.stale === false
&& degradedCatalog.message.includes('degraded directory'),
JSON.stringify(degradedCatalog),
);
catalogDegraded = false;
await page.evaluate(async () => {
await window.__godsEyeView.dataManager.layers.get('radio').module.update();
});
await page.select('#radio-filter', 'all');
const allPalette = await page.evaluate(() => {
const source = Array.from({ length: window.__godsEyeView.viewer.dataSources.length }, (_, index) => (
window.__godsEyeView.viewer.dataSources.get(index)
)).find((item) => item.name === 'Radio stations');
const colorFor = (index) => {
const id = `radio:00000000-0000-4000-8000-${index.toString(16).padStart(12, '0')}`;
const color = source?.entities.getById(id)?.point?.color?.getValue?.();
return color ? [color.red, color.green, color.blue].map((value) => Math.round(value * 255)) : null;
};
return { news: colorFor(0), talk: colorFor(1), music: colorFor(7) };
});
check(
'All stations use distinct category marker colors and Music is green',
JSON.stringify(allPalette.news) !== JSON.stringify(allPalette.music)
&& JSON.stringify(allPalette.talk) !== JSON.stringify(allPalette.music)
&& allPalette.music?.[1] > allPalette.music?.[0]
&& allPalette.music?.[1] > allPalette.music?.[2],
JSON.stringify(allPalette),
);
const clusterBadge = await page.evaluate(async () => {
const viewer = window.__godsEyeView.viewer;
const { getOverlayPaintRect, getWorldOverlayDiagnostics } = await import('/src/overlays/worldOverlay.js');
const { distanceFade } = await import('/src/overlays/worldOverlayDraw.js');
const { radioStationIdFromPick } = await import('/src/data/radio.js');
const baselineSolveRevision = getWorldOverlayDiagnostics().solveRevision;
const source = Array.from({ length: viewer.dataSources.length }, (_, index) => viewer.dataSources.get(index))
.find((item) => item.name === 'Radio stations');
const ellipsoid = viewer.scene.globe.ellipsoid;
viewer.camera.cancelFlight();
viewer.camera.setView({
destination: ellipsoid.cartographicToCartesian({
longitude: -98 * Math.PI / 180,
latitude: 38 * Math.PI / 180,
height: 18_000_000,
}),
orientation: { heading: 0, pitch: -Math.PI / 2, roll: 0 },
});
source.clustering.enabled = false;
viewer.scene.requestRender();
await new Promise((resolve) => setTimeout(resolve, 100));
return new Promise((resolve) => {
const originalPixelRange = source.clustering.pixelRange;
const timeout = setTimeout(() => {
remove();
resolve(null);
}, 2_000);
const remove = source.clustering.clusterEvent.addEventListener(async (entities, cluster) => {
clearTimeout(timeout);
remove();
const native = {
count: entities.length,
labelShow: cluster.label.show,
labelText: cluster.label.text,
pointSize: cluster.point.pixelSize,
pointMaxDistance: cluster.point.distanceDisplayCondition?.far ?? null,
};
source.clustering.pixelRange = originalPixelRange;
const radio = window.__godsEyeView.dataManager.layers.get('radio').module;
await new Promise((done) => setTimeout(done, 0));
let painted = null;
for (let attempt = 0; attempt < 20 && !painted?.rect; attempt += 1) {
painted = await new Promise((done) => {
let removePostRender = null;
const timeout = setTimeout(() => {
removePostRender?.();
done({ rect: null, host: getWorldOverlayDiagnostics() });
}, 150);
removePostRender = viewer.scene.postRender.addEventListener(() => {
clearTimeout(timeout);
removePostRender();
const host = getWorldOverlayDiagnostics();
let match = null;
for (const entryId of radio.getOverlayDiagnostics().clusterIds) {
const rect = getOverlayPaintRect('radio', entryId);
if (rect) match = { entryId, rect };
}
done({
expectedEntryId: match?.entryId || null,
rect: match ? { x: match.rect.x, y: match.rect.y, w: match.rect.w, h: match.rect.h } : null,
host,
});
});
viewer.scene.requestRender();
});
}
resolve({
native,
baselineSolveRevision,
expectedEntryId: painted?.expectedEntryId || null,
rect: painted?.rect || null,
source: radio.getOverlayDiagnostics(),
host: painted?.host || getWorldOverlayDiagnostics(),
});
});
source.clustering.enabled = true;
viewer.scene.requestRender();
});
});
check(
'Radio cluster text is native-free and painted through the bounded shared host',
clusterBadge?.native?.labelShow === false
&& clusterBadge?.native?.labelText === ''
&& clusterBadge?.native?.pointSize >= 14
&& clusterBadge?.native?.pointMaxDistance === 50_000_000
&& clusterBadge?.source?.clusterTexts?.some((text) => /^\d+\s+[A-Z]/.test(text))
&& clusterBadge?.source?.entryCount <= 65
&& clusterBadge?.host?.entriesBySource?.radio === clusterBadge?.source?.entryCount
&& clusterBadge?.host?.solveRevision > clusterBadge?.baselineSolveRevision
&& clusterBadge?.rect?.w > 0
&& clusterBadge?.rect?.h > 0,
JSON.stringify(clusterBadge),
);
const highGlobalClusterLabels = await page.evaluate(async () => {
const viewer = window.__godsEyeView.viewer;
const radio = window.__godsEyeView.dataManager.layers.get('radio').module;
const ellipsoid = viewer.scene.globe.ellipsoid;
const { getWorldOverlayDiagnostics } = await import('/src/overlays/worldOverlay.js');
const { distanceFade } = await import('/src/overlays/worldOverlayDraw.js');
viewer.camera.cancelFlight();
viewer.camera.setView({
destination: ellipsoid.cartographicToCartesian({
longitude: 68 * Math.PI / 180,
latitude: 7 * Math.PI / 180,
height: 24_200_000,
}),
orientation: { heading: 15 * Math.PI / 180, pitch: -Math.PI / 2, roll: 0 },
});
viewer.scene.requestRender();
await new Promise((resolve) => setTimeout(resolve, 500));
const source = radio.getOverlayDiagnostics();
const cameraPosition = viewer.camera.positionWC;
const cameraRadius = Math.hypot(cameraPosition.x, cameraPosition.y, cameraPosition.z);
const horizonDistance = Math.sqrt(Math.max(
0,
cameraRadius * cameraRadius - ellipsoid.maximumRadius * ellipsoid.maximumRadius,
));
const points = Array.from({ length: viewer.dataSources.length }, (_, index) => viewer.dataSources.get(index))
.find((item) => item.name === 'Radio stations')?.clustering?._clusterPointCollection;
let clusterPoint = null;
for (let index = 0; index < (points?.length || 0); index += 1) {
const point = points.get(index);
if (!point?.show || !Array.isArray(point.id) || !point.id.length) continue;
const anchor = viewer.scene.cartesianToCanvasCoordinates(point.position);
if (!anchor) continue;
const exactPick = (viewer.scene.drillPick(anchor, 16) || []).find((picked) => (
picked?.primitive === point && picked?.id === point.id
));
clusterPoint = {
maxDistance: point.distanceDisplayCondition?.far ?? null,
pickable: Boolean(exactPick),
};
break;
}
return {
source,
host: getWorldOverlayDiagnostics(),
altitude: viewer.camera.positionCartographic.height,
horizonDistance,
horizonDistanceAlpha: distanceFade(horizonDistance, { maxDistance: 50_000_000 }),
clusterPoint,
};
});
check(
'All-filter cluster labels remain visible at a 24,200 km global view',
highGlobalClusterLabels.altitude > 24_000_000
&& highGlobalClusterLabels.source.clusterIds.length > 0
&& highGlobalClusterLabels.host.projectedCount > 0
&& highGlobalClusterLabels.host.paintedBySource?.radio > 0
&& highGlobalClusterLabels.horizonDistanceAlpha === 1
&& highGlobalClusterLabels.clusterPoint?.maxDistance === 50_000_000
&& highGlobalClusterLabels.clusterPoint?.pickable,
JSON.stringify(highGlobalClusterLabels),
);
await page.screenshot({ path: path.join(SHOTS_DIR, 'high-global-all-labels.png') });
const clusterContinuity = await page.evaluate(async () => {
const viewer = window.__godsEyeView.viewer;
const radio = window.__godsEyeView.dataManager.layers.get('radio').module;
const ellipsoid = viewer.scene.globe.ellipsoid;
const { getOverlayPaintRect, getWorldOverlayDiagnostics } = await import('/src/overlays/worldOverlay.js');
const paintedClusters = () => new Promise((resolve) => {
let removePostRender = null;
const timeout = setTimeout(() => {
removePostRender?.();
resolve({ clusters: [], fadingCount: -1, host: null, source: null });
}, 500);
removePostRender = viewer.scene.postRender.addEventListener(() => {
clearTimeout(timeout);
removePostRender();
const source = radio.getOverlayDiagnostics();
const host = getWorldOverlayDiagnostics();
const clusters = source.clusterIds.map((entryId, index) => {
const rect = getOverlayPaintRect('radio', entryId);
return rect ? {
entryId,
text: source.clusterTexts[index],
stateless: rect.entry?.stateless === true,
edgeFade: rect.entry?.edgeFade,
} : null;
}).filter(Boolean);
resolve({
clusters,
fadingCount: host.fadingCount,
host,
source,
});
});
viewer.scene.requestRender();
});
const longitudes = [-25, -24, -23, -22, -23, -24, -25];
const samples = [];
for (const longitude of longitudes) {
viewer.camera.setView({
destination: ellipsoid.cartographicToCartesian({
longitude: longitude * Math.PI / 180,
latitude: 38 * Math.PI / 180,
height: 10_000_000,
}),
orientation: { heading: 0, pitch: -Math.PI / 2, roll: 0 },
});
viewer.scene.requestRender();
for (const delay of [50, 100, 200, 350]) {
await new Promise((resolve) => setTimeout(resolve, delay));
samples.push({ longitude, delay, ...(await paintedClusters()) });
}
}
let retainedAcrossSteps = 0;
let countChangingRetained = 0;
const settled = samples.filter((sample) => sample.delay === 350);
for (let index = 1; index < settled.length; index += 1) {
const previous = new Map(settled[index - 1].clusters.map((cluster) => [cluster.entryId, cluster.text]));
for (const cluster of settled[index].clusters) {
if (!previous.has(cluster.entryId)) continue;
retainedAcrossSteps += 1;
if (previous.get(cluster.entryId) !== cluster.text) countChangingRetained += 1;
}
}
return {
sampleCount: samples.length,
paintedSamples: samples.filter((sample) => sample.clusters.length > 0).length,
unpaintedProjectedSamples: samples.filter((sample) => (
sample.clusters.length === 0 && (sample.host?.projectedCount || 0) > 0
)).length,
emptySamples: samples.filter((sample) => sample.clusters.length === 0).map((sample) => ({
longitude: sample.longitude,
delay: sample.delay,
sourceEntries: sample.source?.entryCount,
hostCandidates: sample.host?.candidateCount,
hostProjected: sample.host?.projectedCount,
hostPainted: sample.host?.paintedCount,
hostFading: sample.host?.fadingCount,
})),
fadingSamples: samples.filter((sample) => sample.fadingCount !== 0).map((sample) => ({
longitude: sample.longitude,
delay: sample.delay,
fadingCount: sample.fadingCount,
})),
allHardOpacity: samples.every((sample) => sample.clusters.every(
(cluster) => cluster.stateless && cluster.edgeFade === 'none',
)),
retainedAcrossSteps,
countChangingRetained,
};
});
check(
'Radio cluster badges remain hard-opacity through one-degree forward/reverse count changes',
clusterContinuity.sampleCount === 28
&& clusterContinuity.paintedSamples > 0
&& clusterContinuity.unpaintedProjectedSamples === 0
&& clusterContinuity.allHardOpacity
&& clusterContinuity.retainedAcrossSteps > 0
&& clusterContinuity.countChangingRetained > 0,
JSON.stringify(clusterContinuity),
);
const directoryRefreshContinuity = await page.evaluate(async () => {
const viewer = window.__godsEyeView.viewer;
const radio = window.__godsEyeView.dataManager.layers.get('radio').module;
const before = radio.getOverlayDiagnostics().clusterMemberships;
await radio.update();
viewer.scene.requestRender();
await new Promise((resolve) => setTimeout(resolve, 900));
const after = radio.getOverlayDiagnostics().clusterMemberships;
const beforeByMembership = new Map(before.map((entry) => [entry.membershipId, entry.entryId]));
const unchanged = after.filter((entry) => beforeByMembership.has(entry.membershipId));
return {
beforeCount: before.length,
afterCount: after.length,
unchangedCount: unchanged.length,
retainedCount: unchanged.filter((entry) => beforeByMembership.get(entry.membershipId) === entry.entryId).length,
};
});
check(
'unchanged directory refresh membership retains cluster overlay identity',
directoryRefreshContinuity.beforeCount > 0
&& directoryRefreshContinuity.unchangedCount > 0
&& directoryRefreshContinuity.retainedCount === directoryRefreshContinuity.unchangedCount,
JSON.stringify(directoryRefreshContinuity),
);
await page.select('#radio-filter', 'news');
check('launcher enables Radio without forcing Context open', !initial.panelExpanded && !initial.contextExpanded && initial.nestedInContext);
check('full Play is available before a station is selected', !initial.state.selected && initial.fullPlayEnabled && initial.fullPlayLabel === 'Play nearest radio station');
const hoverOnlyDisclosure = await page.evaluate(() => {
const button = document.getElementById('context-radio-toggle-btn');
if (button.getAttribute('aria-expanded') === 'true') button.click();
return {
expanded: button.getAttribute('aria-expanded'),
hidden: document.getElementById('context-radio-mini').hidden,
};
});
await page.hover('#context-radio-toggle-btn');
await sleep(100);
const hoverOnlyVisible = await page.$eval('#context-radio-mini', (mini) => (
!mini.hidden && getComputedStyle(mini).display !== 'none'
));
check(
'hover alone does not disclose compact Radio controls',
hoverOnlyDisclosure.expanded === 'false' && hoverOnlyDisclosure.hidden && !hoverOnlyVisible,
JSON.stringify({ hoverOnlyDisclosure, hoverOnlyVisible }),
);
await page.click('#context-radio-toggle-btn');
await page.waitForFunction(() => {
const button = document.getElementById('context-radio-toggle-btn');
const mini = document.getElementById('context-radio-mini');
return button.getAttribute('aria-expanded') === 'true'
&& !mini.hidden && getComputedStyle(mini).display !== 'none';
});
const compact = await page.evaluate(() => {
const mini = document.getElementById('context-radio-mini');
const toggle = document.getElementById('context-radio-toggle-btn');
const dock = document.getElementById('context-radio-dock');
const miniRect = mini.getBoundingClientRect();
const toggleRect = toggle.getBoundingClientRect();
const miniStyle = getComputedStyle(mini);
return {
visible: miniStyle.display !== 'none' && miniRect.width > 0 && miniRect.height > 0,
display: miniStyle.display,
visibility: miniStyle.visibility,
opacity: miniStyle.opacity,
miniRect: { top: miniRect.top, left: miniRect.left, width: miniRect.width, height: miniRect.height },
toggleRect: { top: toggleRect.top, left: toggleRect.left, width: toggleRect.width, height: toggleRect.height },
dockRect: (() => {
const rect = dock.getBoundingClientRect();
return { top: rect.top, left: rect.left, width: rect.width, height: rect.height };
})(),
offsetParent: mini.offsetParent?.id || mini.offsetParent?.className || null,
prevEnabled: !document.getElementById('context-radio-mini-prev-btn').disabled,
playEnabled: !document.getElementById('context-radio-mini-play-btn').disabled,
playLabel: document.getElementById('context-radio-mini-play-btn').getAttribute('aria-label'),
nextEnabled: !document.getElementById('context-radio-mini-next-btn').disabled,
volumeEnabled: !document.getElementById('context-radio-mini-volume').disabled,
offsetBelowToggle: Math.round(miniRect.top - toggleRect.bottom),
};
});
check(
'explicit launcher disclosure reveals enabled compact Radio controls',
compact.visible && compact.prevEnabled && compact.playEnabled && compact.nextEnabled && compact.volumeEnabled,
JSON.stringify(compact),
);
check('compact Play is available before a station is selected', compact.playLabel === 'Play nearest radio station');
check('compact Radio controls sit 20px below the launcher', compact.offsetBelowToggle === 20, `${compact.offsetBelowToggle}px`);
const compactCloseDisclosure = await page.evaluate(() => {
const launcher = document.getElementById('context-radio-toggle-btn');
const close = document.getElementById('context-radio-mini-close-btn');
close.focus();
close.click();
const result = {
launcherExpanded: launcher.getAttribute('aria-expanded'),
compactHidden: document.getElementById('context-radio-mini').hidden,
focusRestored: document.activeElement === launcher,
radioCollapsed: document.getElementById('radio-panel').classList.contains('collapsed'),
};
launcher.click();
return result;
});
check(
'dedicated compact close hides only the compact Radio disclosure and restores launcher focus',
compactCloseDisclosure.launcherExpanded === 'false'
&& compactCloseDisclosure.compactHidden
&& compactCloseDisclosure.focusRestored
&& compactCloseDisclosure.radioCollapsed,
JSON.stringify(compactCloseDisclosure),
);
const compactTouchDisclosure = await page.evaluate(() => {
const button = document.getElementById('context-radio-toggle-btn');
button.click();
const closed = button.getAttribute('aria-expanded') === 'false'
&& document.getElementById('context-radio-mini').hidden;
button.dispatchEvent(new PointerEvent('pointerdown', {
bubbles: true,
pointerType: 'touch',
isPrimary: true,
}));
button.click();
return {
closed,
reopened: button.getAttribute('aria-expanded') === 'true'
&& !document.getElementById('context-radio-mini').hidden,
};
});
check(
'touch/coarse-pointer activation can close and reopen compact Radio controls',
compactTouchDisclosure.closed && compactTouchDisclosure.reopened,
JSON.stringify(compactTouchDisclosure),
);
const multiExpandedPanelLayout = await page.evaluate(async () => {
const manager = window.__godsEyeView.styleManager;
const ids = ['data-panel', 'scene-panel', 'pp-toggles', 'cctv-panel', 'global-context-panel'];
const prior = {
panels: Object.fromEntries(ids.map((id) => {
const panel = document.getElementById(id);
return [id, {
className: panel.className,
style: panel.getAttribute('style'),
ariaHidden: panel.getAttribute('aria-hidden'),
}];
})),
stacks: Object.fromEntries(['left-panel-stack', 'right-context-rail'].map((id) => {
const stack = document.getElementById(id);
return [id, {
className: stack.className,
style: stack.getAttribute('style'),
dataset: { ...stack.dataset },
}];
})),
preferredLeftPanelId: manager._leftStackPreferredPanelId,
preferredRightPanelId: manager._rightStackPreferredPanelId,
hudMode: manager.hud.getMode(),
hudVariant: manager.hud.getVariant(),
focusId: document.activeElement?.id || null,
storage: Object.fromEntries(Object.entries(localStorage)),
};
const setPanelThroughInstalledControl = (id, collapsed) => {
const panel = document.getElementById(id);
if (panel.classList.contains('collapsed') === collapsed) return;
panel.querySelector(`[data-collapse-target="${id}"]`)?.click();
};
let result;
try {
manager.hud.setMode('off');
setPanelThroughInstalledControl('data-panel', true);
setPanelThroughInstalledControl('scene-panel', true);
setPanelThroughInstalledControl('data-panel', false);
setPanelThroughInstalledControl('scene-panel', false);
manager.setPanelCollapsed('global-context-panel', false, { explicit: true });
manager.setPanelCollapsed('pp-toggles', false, { explicit: true });
await new Promise((resolve) => setTimeout(resolve, 360));
manager._syncLeftPanelAdaptiveLayout();
manager._syncRightPanelAdaptiveLayout();
const left = document.getElementById('left-panel-stack');
const right = document.getElementById('right-context-rail');
const leftRect = left.getBoundingClientRect();
const rightRect = right.getBoundingClientRect();
const leftExpanded = ['data-panel', 'scene-panel'].map((id) => (
document.getElementById(id).getBoundingClientRect()
));
const rightExpanded = ['pp-toggles', 'global-context-panel'].map((id) => (
document.getElementById(id).getBoundingClientRect()
));
result = {
independentLeft: ['data-panel', 'scene-panel'].every((id) => (
!document.getElementById(id).classList.contains('collapsed')
)),
independentRight: ['pp-toggles', 'global-context-panel'].every((id) => (
!document.getElementById(id).classList.contains('collapsed')
)),
leftCountsAllExpanded: left.dataset.expandedCount === '2',
rightCountsAllExpanded: right.dataset.expandedCount === '2',
leftInsideCorridor: leftExpanded.every((rect) => (
rect.top >= leftRect.top - 1 && rect.bottom <= leftRect.bottom + 1
)),
rightInsideCorridor: rightExpanded.every((rect) => (
rect.top >= rightRect.top - 1 && rect.bottom <= rightRect.bottom + 1
)),
internalScrollOwned: getComputedStyle(document.querySelector('#data-panel .data-toggle-list')).overflowY === 'auto'
&& getComputedStyle(document.querySelector('#scene-panel .scene-panel-inner')).overflowY === 'auto'
&& getComputedStyle(document.querySelector('#global-context-panel .global-context-panel-inner')).overflowY === 'auto',
};
manager._setHudVariant('tactical');
manager.hud.setMode('on');
manager._updateHudButtonState();
await new Promise((resolve) => setTimeout(resolve, 360));
manager._syncLeftPanelAdaptiveLayout();
manager._syncRightPanelAdaptiveLayout();
const dataPanel = document.getElementById('data-panel');
const scenePanel = document.getElementById('scene-panel');
const contextPanel = document.getElementById('global-context-panel');
result.tacticalAutoCollapse = {
latestOpenedPanel: manager._leftStackPreferredPanelId,
scenesExpanded: !scenePanel.classList.contains('collapsed'),
dataCollapsed: dataPanel.classList.contains('layout-auto-collapsed'),
dataHiddenWhileScenesOwnsLane: dataPanel.getBoundingClientRect().height === 0
&& dataPanel.getAttribute('aria-hidden') === 'true',
dataAriaExpanded: dataPanel.querySelector('[data-collapse-target="data-panel"]')?.getAttribute('aria-expanded'),
displayExpanded: !document.getElementById('pp-toggles').classList.contains('collapsed'),
contextCollapsed: contextPanel.classList.contains('layout-auto-collapsed'),
contextHiddenWhileDisplayOwnsLane: contextPanel.getBoundingClientRect().height === 0
&& contextPanel.getAttribute('aria-hidden') === 'true',
contextAriaExpanded: contextPanel.querySelector('[data-collapse-target="global-context-panel"]')?.getAttribute('aria-expanded'),
};
manager.setPanelCollapsed('scene-panel', true);
manager.setPanelCollapsed('pp-toggles', true);
await new Promise((resolve) => setTimeout(resolve, 360));
manager._syncLeftPanelAdaptiveLayout();
manager._syncRightPanelAdaptiveLayout();
result.tacticalAutoRestore = {
dataExpanded: !dataPanel.classList.contains('collapsed'),
dataAutoMarkerCleared: !dataPanel.classList.contains('layout-auto-collapsed'),
dataAriaExpanded: dataPanel.querySelector('[data-collapse-target="data-panel"]')?.getAttribute('aria-expanded'),
contextExpanded: !contextPanel.classList.contains('collapsed'),
contextAutoMarkerCleared: !contextPanel.classList.contains('layout-auto-collapsed'),
contextAriaExpanded: contextPanel.querySelector('[data-collapse-target="global-context-panel"]')?.getAttribute('aria-expanded'),
};
manager.setPanelCollapsed('global-context-panel', true);
manager.setPanelCollapsed('cctv-panel', true);
manager.setPanelCollapsed('pp-toggles', false);
await new Promise((resolve) => setTimeout(resolve, 240));
manager._syncRightPanelAdaptiveLayout();
const displayPanel = document.getElementById('pp-toggles');
const cctvPanel = document.getElementById('cctv-panel');
result.tacticalDisplayExclusive = {
railExclusive: right.classList.contains('layout-exclusive'),
displayExpanded: !displayPanel.classList.contains('collapsed'),
cctvHidden: cctvPanel.getBoundingClientRect().height === 0
&& cctvPanel.getAttribute('aria-hidden') === 'true',
contextHidden: contextPanel.getBoundingClientRect().height === 0
&& contextPanel.getAttribute('aria-hidden') === 'true',
};
} finally {
manager._setHudVariant(prior.hudVariant);
manager.hud.setMode(prior.hudMode);
manager._updateHudButtonState();
await new Promise((resolve) => setTimeout(resolve, 320));
if (manager._leftStackLayoutFrame !== null) {
cancelAnimationFrame(manager._leftStackLayoutFrame);
manager._leftStackLayoutFrame = null;
}
if (manager._rightStackLayoutFrame !== null) {
cancelAnimationFrame(manager._rightStackLayoutFrame);
manager._rightStackLayoutFrame = null;
}
for (const [id, state] of Object.entries(prior.panels)) {
const panel = document.getElementById(id);
panel.className = state.className;
if (state.style === null) panel.removeAttribute('style');
else panel.setAttribute('style', state.style);
if (state.ariaHidden === null) panel.removeAttribute('aria-hidden');
else panel.setAttribute('aria-hidden', state.ariaHidden);
manager._syncPanelCollapseButton(panel);
}
for (const [id, state] of Object.entries(prior.stacks)) {
const stack = document.getElementById(id);
stack.className = state.className;
if (state.style === null) stack.removeAttribute('style');
else stack.setAttribute('style', state.style);
for (const key of Object.keys(stack.dataset)) delete stack.dataset[key];
Object.assign(stack.dataset, state.dataset);
}
manager._leftStackPreferredPanelId = prior.preferredLeftPanelId;
manager._rightStackPreferredPanelId = prior.preferredRightPanelId;
localStorage.clear();
for (const [key, value] of Object.entries(prior.storage)) localStorage.setItem(key, value);
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
if (manager._leftStackLayoutFrame !== null) {
cancelAnimationFrame(manager._leftStackLayoutFrame);
manager._leftStackLayoutFrame = null;
}
if (manager._rightStackLayoutFrame !== null) {
cancelAnimationFrame(manager._rightStackLayoutFrame);
manager._rightStackLayoutFrame = null;
}
// Disclosure restoration can wake the installed layout observers. Let
// those observers settle, then restore the captured stack presentation
// once more so this synthetic scenario cannot leak derived lane state
// into the Radio interactions that follow.
for (const [id, state] of Object.entries(prior.stacks)) {
const stack = document.getElementById(id);
stack.className = state.className;
if (state.style === null) stack.removeAttribute('style');
else stack.setAttribute('style', state.style);
for (const key of Object.keys(stack.dataset)) delete stack.dataset[key];
Object.assign(stack.dataset, state.dataset);
}
prior.focusId && document.getElementById(prior.focusId)?.focus({ preventScroll: true });
const panelsRestored = Object.entries(prior.panels).every(([id, state]) => {
const panel = document.getElementById(id);
return panel.className === state.className
&& panel.getAttribute('style') === state.style
&& panel.getAttribute('aria-hidden') === state.ariaHidden;
});
const stacksRestored = Object.entries(prior.stacks).every(([id, state]) => {
const stack = document.getElementById(id);
return stack.className === state.className
&& stack.getAttribute('style') === state.style
&& JSON.stringify({ ...stack.dataset }) === JSON.stringify(state.dataset);
});
result.restoreDetails = {
panelsRestored,
stacksRestored,
preferredPanelRestored: manager._leftStackPreferredPanelId === prior.preferredLeftPanelId,
preferredRightPanelRestored: manager._rightStackPreferredPanelId === prior.preferredRightPanelId,
hudModeRestored: manager.hud.getMode() === prior.hudMode,
hudVariantRestored: manager.hud.getVariant() === prior.hudVariant,
storageRestored: JSON.stringify(Object.fromEntries(Object.entries(localStorage))) === JSON.stringify(prior.storage),
focusRestored: !prior.focusId || document.activeElement?.id === prior.focusId,
};
result.restoredExactly = Object.values(result.restoreDetails).every(Boolean);
}
return result;
});
check(
'HUD-off desktop lanes keep Data plus Scenes and Display plus Context expanded inside the viewport corridor',
multiExpandedPanelLayout.independentLeft
&& multiExpandedPanelLayout.independentRight
&& multiExpandedPanelLayout.leftCountsAllExpanded
&& multiExpandedPanelLayout.rightCountsAllExpanded
&& multiExpandedPanelLayout.leftInsideCorridor
&& multiExpandedPanelLayout.rightInsideCorridor
&& multiExpandedPanelLayout.internalScrollOwned
&& multiExpandedPanelLayout.restoredExactly,
JSON.stringify(multiExpandedPanelLayout),
);
check(
'HUD-off to Tactical preserves the latest-opened Scenes panel and hides its collapsed Data competitor',
multiExpandedPanelLayout.tacticalAutoCollapse.latestOpenedPanel === 'scene-panel'
&& multiExpandedPanelLayout.tacticalAutoCollapse.scenesExpanded
&& multiExpandedPanelLayout.tacticalAutoCollapse.dataCollapsed
&& multiExpandedPanelLayout.tacticalAutoCollapse.dataHiddenWhileScenesOwnsLane
&& multiExpandedPanelLayout.tacticalAutoCollapse.dataAriaExpanded === 'false'
&& multiExpandedPanelLayout.tacticalAutoCollapse.displayExpanded
&& multiExpandedPanelLayout.tacticalAutoCollapse.contextCollapsed
&& multiExpandedPanelLayout.tacticalAutoCollapse.contextHiddenWhileDisplayOwnsLane
&& multiExpandedPanelLayout.tacticalAutoCollapse.contextAriaExpanded === 'false',
JSON.stringify(multiExpandedPanelLayout.tacticalAutoCollapse),
);
check(
'closing Tactical lane owners restores eligible temporary siblings without changing disclosure truth',
multiExpandedPanelLayout.tacticalAutoRestore.dataExpanded
&& multiExpandedPanelLayout.tacticalAutoRestore.dataAutoMarkerCleared
&& multiExpandedPanelLayout.tacticalAutoRestore.dataAriaExpanded === 'true'
&& multiExpandedPanelLayout.tacticalAutoRestore.contextExpanded
&& multiExpandedPanelLayout.tacticalAutoRestore.contextAutoMarkerCleared
&& multiExpandedPanelLayout.tacticalAutoRestore.contextAriaExpanded === 'true',
JSON.stringify(multiExpandedPanelLayout.tacticalAutoRestore),
);
check(
'Tactical Display hides ordinary collapsed CCTV and Context launchers',
Object.values(multiExpandedPanelLayout.tacticalDisplayExclusive).every(Boolean),
JSON.stringify(multiExpandedPanelLayout.tacticalDisplayExclusive),
);
const parameterizedDisplayScroll = await page.evaluate(async () => {
const manager = window.__godsEyeView.styleManager;
const display = document.getElementById('pp-toggles');
const parameters = document.getElementById('param-slider-panel');
const prior = {
style: manager.activeStyle,
displayCollapsed: display.classList.contains('collapsed'),
contextCollapsed: document.getElementById('global-context-panel').classList.contains('collapsed'),
cctvCollapsed: document.getElementById('cctv-panel').classList.contains('collapsed'),
hudMode: manager.hud.getMode(),
hudVariant: manager.hud.getVariant(),
focusId: document.activeElement?.id || null,
};
const waitForLayout = () => new Promise((resolve) => setTimeout(resolve, 320));
const cases = [];
manager.setPanelCollapsed('global-context-panel', true);
manager.setPanelCollapsed('cctv-panel', true);
manager.setPanelCollapsed('pp-toggles', false);
manager.setStyle('thermal', { applyPreset: false });
for (const state of [
{ name: 'tactical', visible: true, variant: 'tactical' },
{ name: 'minimal', visible: true, variant: 'minimal' },
{ name: 'hud-off', visible: false, variant: 'minimal' },
]) {
manager._setHudVariant(state.variant);
manager.hud.setMode(state.visible ? 'on' : 'off');
manager._updateHudButtonState();
await waitForLayout();
manager._syncRightPanelAdaptiveLayout();
display.scrollTop = Math.max(1, display.scrollHeight - display.clientHeight);
const before = display.scrollTop;
manager._syncRightPanelAdaptiveLayout();
const lastSlider = document.querySelector('#param-sliders .param-slider-row:last-child input');
lastSlider.focus({ preventScroll: true });
lastSlider.scrollIntoView({ block: 'nearest' });
const displayRect = display.getBoundingClientRect();
const sliderRect = lastSlider.getBoundingClientRect();
cases.push({
name: state.name,
before,
after: display.scrollTop,
finalRowReachable: sliderRect.top >= displayRect.top - 1
&& sliderRect.bottom <= displayRect.bottom + 1,
displayOwnsScroll: getComputedStyle(display).overflowY === 'auto',
parametersDoNotTrapScroll: getComputedStyle(parameters).overflowY === 'visible',
parameterRows: document.querySelectorAll('#param-sliders .param-slider-row').length,
});
}
manager.setStyle(prior.style, { applyPreset: false });
manager.setPanelCollapsed('pp-toggles', prior.displayCollapsed);
manager.setPanelCollapsed('global-context-panel', prior.contextCollapsed);
manager.setPanelCollapsed('cctv-panel', prior.cctvCollapsed);
manager._setHudVariant(prior.hudVariant);
manager.hud.setMode(prior.hudMode);
manager._updateHudButtonState();
prior.focusId && document.getElementById(prior.focusId)?.focus({ preventScroll: true });
return cases;
});
check(
'parameterized Display remains scrollable through Tactical, Minimal, and HUD Off layout passes',
parameterizedDisplayScroll.every((state) => (
state.parameterRows >= 5
&& state.before > 0
&& state.after > 0
&& state.finalRowReachable
&& state.displayOwnsScroll
&& state.parametersDoNotTrapScroll
)),
JSON.stringify(parameterizedDisplayScroll),
);
const cockpitCompactControls = await page.evaluate(async () => {
const hud = document.getElementById('cockpit-hud');
const signal = document.getElementById('cockpit-signal-stream');
const display = document.getElementById('cockpit-display-toggle-btn');
const radio = document.getElementById('cockpit-radio-toggle-btn');
const hudToggle = document.getElementById('hud-toggle');
const hudLayout = document.getElementById('hud-layout-select');
const detectionToggle = document.getElementById('detection-toggle');
const models3dToggle = document.getElementById('models3d-toggle');
const models3dAll = document.getElementById('models3d-mode-all');
const scene = document.getElementById('scene-panel');
const dataPanel = document.getElementById('data-panel');
const manager = window.__godsEyeView.styleManager;
// This block validates portaled DOM controls, while qa-cockpit-utility
// owns the real tracked-aircraft camera session. Hold the frame update so
// the intentionally synthetic Cockpit shell is not auto-exited mid-check.
const realCockpitUpdate = manager.cockpitView.update;
const waitForLayout = () => new Promise((resolve) => setTimeout(resolve, 320));
const prior = {
cockpit: document.body.classList.contains('cockpit-mode'),
cockpitActive: manager.cockpitView.active,
bodyClassName: document.body.className,
contextCollapsed: document.getElementById('global-context-panel').classList.contains('collapsed'),
hudHidden: hud.hidden,
signalHidden: signal.hidden,
dataCollapsed: dataPanel.classList.contains('collapsed'),
mapStack: manager.mapStackController?.getActiveId(),
hudMode: manager.hud.getMode(),
hudVariant: manager.hud.getVariant(),
displayExpanded: display.getAttribute('aria-expanded') === 'true',
radioExpanded: radio.getAttribute('aria-expanded') === 'true',
contextRadioExpanded: document.getElementById('context-radio-toggle-btn')?.getAttribute('aria-expanded') === 'true',
focusId: document.activeElement?.id || null,
detectionMode: manager.getDetectionState().detectionMode,
models3dEnabled: manager._models3dEnabled,
models3dMode: manager._models3dMode,
preferredLeftPanelId: manager._leftStackPreferredPanelId,
panels: Object.fromEntries([
'data-panel',
'global-context-panel',
].map((id) => {
const panel = document.getElementById(id);
return [id, {
className: panel.className,
style: panel.getAttribute('style'),
ariaHidden: panel.getAttribute('aria-hidden'),
}];
})),
storage: Object.fromEntries(Object.entries(localStorage)),
utilityTop: hud.style.getPropertyValue('--cockpit-utility-top'),
};
let result = {};
try {
manager.cockpitView.update = () => {};
document.body.classList.add('cockpit-mode');
manager.cockpitView.active = true;
manager._setCockpitDisplayPortalActive(true);
hud.hidden = false;
signal.hidden = false;
dataPanel.classList.remove('collapsed');
window.__godsEyeView.styleManager._syncLeftPanelAdaptiveLayout();
window.__godsEyeView.styleManager.cockpitView.syncSignalLayout();
if (display.getAttribute('aria-expanded') === 'true') display.click();
display.click();
manager._setHudVariant('tactical');
manager.hud.setMode('on');
manager._updateHudButtonState();
await waitForLayout();
const layoutSteps = [];
// The two Cockpit lanes are solved independently: the accordion against
// left-lane obstacles, the Display/Radio strip against the REC readout
// and the briefing card it shares the right margin with. Record both
// sets of inputs so each lane is asserted on its own terms.
const visibleRect = (selector) => {
const element = document.querySelector(selector);
if (!element || element.hidden) return null;
for (let node = element; node; node = node.parentElement) {
const style = getComputedStyle(node);
if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0) {
return null;
}
}
const rect = element.getBoundingClientRect();
return rect.width > 0 && rect.height > 0 ? rect : null;
};
const recordLayoutStep = () => {
const dataRect = dataPanel.getBoundingClientRect();
const utilityRect = document.getElementById('cockpit-utility-controls').getBoundingClientRect();
const displayRect = display.closest('.cockpit-utility-control').getBoundingClientRect();
const toplineRect = document.querySelector('#cockpit-hud .cockpit-topline').getBoundingClientRect();
const recRect = visibleRect('#intel-hud .hud-top-right');
const creditsRect = visibleRect('#cesium-credits .cesium-credit-textContainer');
// Left-lane surfaces an expanded Cockpit panel must remain clear of.
const passableTops = ['#cockpit-context', '#intel-hud .hud-bottom-left']
.map((selector) => visibleRect(selector))
.filter((rect) => rect && rect.right > dataRect.left && rect.left < dataRect.right)
.map((rect) => rect.top);
const stack = document.getElementById('left-panel-stack');
layoutSteps.push({
hud: manager.hud.visible ? manager.hud.getVariant().toUpperCase() : 'OFF',
dataTop: dataRect.top,
dataBottom: dataRect.bottom,
// The corridor the engine committed, independent of how tall the
// panel's own content happens to be.
layoutBottom: window.innerHeight * (Number(stack.dataset.safeBottomPct) || 0) / 100,
safeGap: window.innerHeight * 0.012,
displayTop: displayRect.top,
utilityTop: utilityRect.top,
utilityBottom: utilityRect.bottom,
utilityHeight: utilityRect.height,
toplineBottom: toplineRect.bottom,
recBottom: recRect ? recRect.bottom : 0,
signalTop: signal.getBoundingClientRect().top,
creditsTop: creditsRect ? creditsRect.top : null,
passableTop: passableTops.length ? Math.min(...passableTops) : null,
minTop: Math.max(96, window.innerHeight * 0.12),
layoutMode: document.getElementById('left-panel-stack').dataset.layoutMode,
});
};
// The Intel HUD fades over 400ms and keeps its readout rects for the
// whole transition, so both lanes are measured only once it has settled.
const waitForHudSettle = () => new Promise((resolve) => setTimeout(resolve, 560));
for (let index = 0; index < 5; index += 1) {
recordLayoutStep();
if (index < 4) {
if (index < 2) {
hudLayout.value = index === 0 ? 'operator' : 'minimal';
hudLayout.dispatchEvent(new Event('change', { bubbles: true }));
} else {
hudToggle.click();
}
await waitForHudSettle();
}
}
// Cockpit's CONTACT card shares the accordion's lane. This synthetic
// shell has no tracked contact, so reveal the card to prove an expanded
// panel stops above it while its internally scrolled controls stay live.
const contextCard = document.getElementById('cockpit-context');
const contextWasHidden = contextCard.hidden;
contextCard.hidden = false;
// Expand the panel the same way this block already does at entry, so the
// portaled Display groups measured further down keep their DOM home.
dataPanel.classList.remove('collapsed', 'layout-auto-collapsed');
manager._syncPanelCollapseButton(dataPanel);
manager._syncLeftPanelAdaptiveLayout();
await waitForLayout();
const panelRect = dataPanel.getBoundingClientRect();
const contextRect = contextCard.getBoundingClientRect();
const contactClearancePx = contextRect.top - panelRect.bottom;
const clearsContextCard = contactClearancePx >= -0.5;
const probe = document.elementFromPoint(
Math.max(panelRect.left, contextRect.left) + 8,
Math.max(panelRect.top, contextRect.top) + 6,
);
const contextOwnsItsLane = !probe?.closest('#data-panel');
// A layer toggle must remain reachable and live from inside Cockpit.
// Prefer a bundled layer so the proof needs no external network.
const toggleList = dataPanel.querySelector('.data-toggle-list');
const toggleRows = [...toggleList.querySelectorAll('.data-toggle-row')]
.filter((row) => row.querySelector('.data-toggle-btn'));
const toggleRow = toggleRows.find((row) => String(row.dataset.layerId).startsWith('local-'))
|| toggleRows.find((row) => !/flight|military|vessel|ais|radio/i.test(row.dataset.layerId || ''));
toggleRow?.scrollIntoView({ block: 'center' });
await waitForLayout();
const toggleLayerId = toggleRow?.dataset.layerId || null;
const toggleButton = toggleRow?.querySelector('.data-toggle-btn');
const toggleButtonRect = toggleButton?.getBoundingClientRect();
const listRect = toggleList.getBoundingClientRect();
const toggleInsideScroller = Boolean(toggleButtonRect
&& toggleButtonRect.top >= listRect.top - 1
&& toggleButtonRect.bottom <= listRect.bottom + 1);
const toggleReachable = Boolean(toggleButtonRect && toggleRow.contains(
document.elementFromPoint(
toggleButtonRect.left + toggleButtonRect.width / 2,
toggleButtonRect.top + toggleButtonRect.height / 2,
),
));
const toggleBefore = toggleLayerId
? window.__godsEyeView.dataManager.isEnabled(toggleLayerId) : null;
toggleButton?.click();
await waitForLayout();
const toggleAfter = toggleLayerId
? window.__godsEyeView.dataManager.isEnabled(toggleLayerId) : null;
toggleButton?.click();
await waitForLayout();
const toggleRestored = toggleLayerId
? window.__godsEyeView.dataManager.isEnabled(toggleLayerId) === toggleBefore : false;
const cockpitPanelInteraction = {
toggleLayerId,
contactClearancePx,
clearsContextCard,
contextOwnsItsLane,
toggleInsideScroller,
toggleReachable,
toggleBefore,
toggleAfter,
toggleRestored,
listScrolls: toggleList.scrollHeight > toggleList.clientHeight,
panelHeight: Math.round(panelRect.height),
};
contextCard.hidden = contextWasHidden;
// Restoring the layer destroys it, and a layer teardown during a Context
// session legitimately exits Cockpit (ui.js subscribeBeforeDestroy). Re-
// establish this block's synthetic shell exactly as it was entered, so
// the portaled-control checks below keep their precondition.
document.body.classList.add('cockpit-mode');
manager.cockpitView.active = true;
hud.hidden = false;
signal.hidden = false;
manager._setCockpitDisplayPortalActive(true);
manager._setCockpitDisclosure('display', true);
await waitForLayout();
manager._setModels3dMode('proximity');
manager._setModels3dEnabled(false);
const models3dSteps = [];
for (let index = 0; index < 3; index += 1) {
if (index === 0 || index === 2) models3dToggle.click();
else models3dAll.click();
const flightsParams = manager._dataManager.getLayerParams('flights');
const militaryParams = manager._dataManager.getLayerParams('military');
models3dSteps.push({
label: manager._models3dEnabled ? manager._models3dMode.toUpperCase() : 'OFF',
flightsEnabled: flightsParams.models3d,
flightsMode: flightsParams.models3dMode,
militaryEnabled: militaryParams.models3d,
militaryMode: militaryParams.models3dMode,
});
}
manager.cockpitView.syncSignalLayout();
const displayOpened = display.getAttribute('aria-expanded') === 'true'
&& !document.getElementById('cockpit-display-panel').hidden;
const sharedDisplayControlsPortaled = [hudToggle, detectionToggle, models3dToggle]
.every((control) => control.closest('.pp-toggle-group')?.parentElement
?.closest('#cockpit-display-panel'));
display.click();
manager.cockpitView.syncSignalLayout();
radio.click();
const radioOpened = radio.getAttribute('aria-expanded') === 'true'
&& !document.getElementById('cockpit-radio-panel').hidden;
const mutuallyExclusive = display.getAttribute('aria-expanded') === 'false';
radio.click();
manager.cockpitView.syncSignalLayout();
radio.click();
const radioNavigationAvailable = [
document.getElementById('cockpit-radio-prev-btn'),
document.getElementById('cockpit-radio-next-btn'),
].every((button) => button?.isConnected && !button.disabled);
const detailsExcluded = !document.querySelector(
'#cockpit-radio-panel #context-radio-details-btn, '
+ '#cockpit-radio-panel #radio-filter, #cockpit-radio-panel #radio-tuner',
);
const scenesHidden = getComputedStyle(scene).display === 'none';
radio.click();
manager.setPanelCollapsed('data-panel', true);
await waitForLayout();
const utility = document.getElementById('cockpit-utility-controls');
const photorealAvailable = manager.mapStackController?.getStacks()
.some((stack) => stack.id === 'photoreal' && stack.available);
let mapProviderUtilityStable = true;
if (photorealAvailable) {
await manager._setMapStack('photoreal', { syncShare: false });
await waitForLayout();
const stableTop = utility.getBoundingClientRect().top;
const transitionTops = [];
let sampling = true;
const sampleTop = () => {
transitionTops.push(utility.getBoundingClientRect().top);
if (sampling) requestAnimationFrame(sampleTop);
};
requestAnimationFrame(sampleTop);
await manager._setMapStack('osm', { syncShare: false });
await waitForLayout();
sampling = false;
mapProviderUtilityStable = transitionTops.length > 1
&& transitionTops.every((top) => Math.abs(top - stableTop) < 1);
}
result = {
cockpitPanelInteraction,
displayOpened,
sharedDisplayControlsPortaled,
radioOpened,
mutuallyExclusive,
radioNavigationAvailable,
detailsExcluded,
scenesHidden,
mapProviderUtilityStable,
hudInteractionsExact: layoutSteps.map((step) => step.hud).join('>')
=== 'TACTICAL>OPERATOR>MINIMAL>OFF>MINIMAL',
// The accordion clears the topline, Cockpit cards, and Cesium credits
// across every HUD layout. Its list owns any overflow internally.
leftLaneClearAcrossHudCycle: layoutSteps.every((step) => (
step.dataTop >= step.toplineBottom
&& (step.creditsTop === null || step.dataBottom <= step.creditsTop)
&& (step.passableTop === null || step.dataBottom <= step.passableTop + 0.5)
)),
// The strip is solved from ITS margin — under the REC readout, clamped
// clear of the briefing card, never above the viewport ceiling — and
// never from whatever corridor the accordion happened to commit.
rightStripClearAcrossHudCycle: layoutSteps.every((step) => {
const anchored = Math.max(step.minTop, step.recBottom + 12);
const expected = Math.max(
step.minTop,
Math.min(anchored, step.signalTop - 8 - step.utilityHeight),
);
return step.displayTop >= step.toplineBottom
&& step.utilityBottom <= step.signalTop - 7.9
&& Math.abs(step.utilityTop - expected) < 0.6;
}),
models3dInteractionsExact: models3dSteps.map((step) => step.label).join('>')
=== 'PROXIMITY>ALL>OFF',
models3dLayersSynchronized: models3dSteps.every((step, index) => {
const expectedEnabled = index < 2;
const expectedMode = index === 0 ? 'proximity' : 'all';
return step.flightsEnabled === expectedEnabled
&& step.militaryEnabled === expectedEnabled
&& step.flightsMode === expectedMode
&& step.militaryMode === expectedMode;
}),
};
} finally {
if (prior.mapStack) await manager._setMapStack(prior.mapStack, { syncShare: false });
manager._setDetectionMode(prior.detectionMode);
manager._setModels3dMode(prior.models3dMode);
manager._setModels3dEnabled(prior.models3dEnabled);
manager._setHudVariant(prior.hudVariant);
manager.hud.setMode(prior.hudMode);
manager._updateHudButtonState();
manager.cockpitView.active = prior.cockpitActive;
manager.cockpitView.update = realCockpitUpdate;
manager.setPanelCollapsed('data-panel', prior.dataCollapsed);
document.body.className = prior.bodyClassName;
manager._setCockpitDisplayPortalActive(prior.cockpit);
manager.setPanelCollapsed('global-context-panel', prior.contextCollapsed);
if (display.getAttribute('aria-expanded') !== String(prior.displayExpanded)) display.click();
if (radio.getAttribute('aria-expanded') !== String(prior.radioExpanded)) radio.click();
manager._setRadioDisclosure(prior.contextRadioExpanded);
hud.hidden = prior.hudHidden;
signal.hidden = prior.signalHidden;
if (prior.utilityTop) hud.style.setProperty('--cockpit-utility-top', prior.utilityTop);
else hud.style.removeProperty('--cockpit-utility-top');
await waitForLayout();
if (manager._leftStackLayoutFrame !== null) {
cancelAnimationFrame(manager._leftStackLayoutFrame);
manager._leftStackLayoutFrame = null;
}
if (manager._rightStackLayoutFrame !== null) {
cancelAnimationFrame(manager._rightStackLayoutFrame);
manager._rightStackLayoutFrame = null;
}
for (const [id, state] of Object.entries(prior.panels)) {
const panel = document.getElementById(id);
panel.className = state.className;
if (state.style === null) panel.removeAttribute('style');
else panel.setAttribute('style', state.style);
if (state.ariaHidden === null) panel.removeAttribute('aria-hidden');
else panel.setAttribute('aria-hidden', state.ariaHidden);
manager._syncPanelCollapseButton(panel);
}
manager._leftStackPreferredPanelId = prior.preferredLeftPanelId;
localStorage.clear();
for (const [key, value] of Object.entries(prior.storage)) localStorage.setItem(key, value);
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
prior.focusId && document.getElementById(prior.focusId)?.focus({ preventScroll: true });
result.restoredExactly = document.body.className === prior.bodyClassName
&& manager.cockpitView.active === prior.cockpitActive
&& manager.cockpitView.update === realCockpitUpdate
&& dataPanel.classList.contains('collapsed') === prior.dataCollapsed
&& document.getElementById('global-context-panel').classList.contains('collapsed') === prior.contextCollapsed
&& display.getAttribute('aria-expanded') === String(prior.displayExpanded)
&& radio.getAttribute('aria-expanded') === String(prior.radioExpanded)
&& document.getElementById('context-radio-toggle-btn')?.getAttribute('aria-expanded') === String(prior.contextRadioExpanded)
&& hud.hidden === prior.hudHidden
&& signal.hidden === prior.signalHidden
&& manager.hud.getMode() === prior.hudMode
&& manager.hud.getVariant() === prior.hudVariant
&& Object.entries(prior.panels).every(([id, state]) => {
const panel = document.getElementById(id);
return panel.className === state.className
&& panel.getAttribute('style') === state.style
&& panel.getAttribute('aria-hidden') === state.ariaHidden;
})
&& manager._leftStackPreferredPanelId === prior.preferredLeftPanelId
&& JSON.stringify(Object.fromEntries(Object.entries(localStorage))) === JSON.stringify(prior.storage)
&& (!prior.focusId || document.activeElement?.id === prior.focusId);
}
return result;
});
check(
'Cockpit portals the standard focused Display groups and keeps compact-only Radio',
cockpitCompactControls.displayOpened
&& cockpitCompactControls.sharedDisplayControlsPortaled
&& cockpitCompactControls.radioOpened
&& cockpitCompactControls.mutuallyExclusive
&& cockpitCompactControls.radioNavigationAvailable
&& cockpitCompactControls.detailsExcluded
&& cockpitCompactControls.scenesHidden
&& cockpitCompactControls.mapProviderUtilityStable
&& cockpitCompactControls.hudInteractionsExact
&& cockpitCompactControls.leftLaneClearAcrossHudCycle
&& cockpitCompactControls.rightStripClearAcrossHudCycle
&& cockpitCompactControls.models3dInteractionsExact
&& cockpitCompactControls.models3dLayersSynchronized
&& cockpitCompactControls.restoredExactly,
JSON.stringify(cockpitCompactControls),
);
check(
'an expanded Cockpit left panel stops above CONTACT and still toggles layers',
cockpitCompactControls.cockpitPanelInteraction?.clearsContextCard
&& cockpitCompactControls.cockpitPanelInteraction.contextOwnsItsLane
&& cockpitCompactControls.cockpitPanelInteraction.toggleInsideScroller
&& cockpitCompactControls.cockpitPanelInteraction.toggleReachable
&& cockpitCompactControls.cockpitPanelInteraction.toggleBefore
!== cockpitCompactControls.cockpitPanelInteraction.toggleAfter
&& cockpitCompactControls.cockpitPanelInteraction.toggleRestored,
JSON.stringify(cockpitCompactControls.cockpitPanelInteraction),
);
await page.screenshot({ path: path.join(SHOTS_DIR, 'compact.png') });
const firstPlayPrecondition = await page.evaluate(() => (
window.__godsEyeView.dataManager.layers.get('radio').module.getUIState().selected?.id || null
));
check('first Play scenario starts without a leaked Radio selection', firstPlayPrecondition === null,
`selected=${firstPlayPrecondition}`);
await page.select('#radio-filter', 'all');
await page.$eval('#context-radio-toggle-btn', (button) => {
if (button.getAttribute('aria-expanded') !== 'true') button.click();
});
await page.evaluate(() => {
const camera = window.__godsEyeView.viewer.camera;
window.__qaFirstPlayFlyToCalls = [];
window.__qaFirstPlayOriginalFlyTo = camera.flyTo;
camera.flyTo = (options) => window.__qaFirstPlayFlyToCalls.push(options);
document.getElementById('context-radio-toggle-btn').focus();
});
await page.$eval('#context-radio-mini-play-btn', (button) => button.click());
await page.waitForFunction(() => {
const state = window.__godsEyeView.dataManager.layers.get('radio').module.getUIState();
return state.audioState === 'playing' || state.audioState === 'error';
});
const postMicroDragPlayback = await page.evaluate(async () => {
const mod = window.__godsEyeView.dataManager.layers.get('radio').module;
let state = mod.getUIState();
if (state.audioState !== 'playing') {
await mod.play({ origin: 'user' });
state = mod.getUIState();
}
return { audioState: state.audioState, selectedId: state.selected?.id || null };
});
check('micro-drag release leaves the selected station playable',
postMicroDragPlayback.audioState === 'playing', JSON.stringify(postMicroDragPlayback));
const firstPlay = await page.evaluate(() => {
const module = window.__godsEyeView.dataManager.layers.get('radio').module;
const state = module.getUIState();
const camera = window.__godsEyeView.viewer.camera;
const result = {
state,
calls: [...window.__qaRadioPlayCalls],
flyToCalls: window.__qaFirstPlayFlyToCalls.length,
};
camera.flyTo = window.__qaFirstPlayOriginalFlyTo;
delete window.__qaFirstPlayOriginalFlyTo;
module.stopPlayback();
return result;
});
check(
'first Play selects a viewport-ranked station from All and starts its stream',
Boolean(firstPlay.state.selected?.id) && firstPlay.state.filter === 'all'
&& firstPlay.state.audioState === 'playing' && firstPlay.calls.length >= 1,
JSON.stringify({ selected: firstPlay.state.selected?.id, calls: firstPlay.calls }),
);
check('first Play never moves the camera', firstPlay.flyToCalls === 0, `${firstPlay.flyToCalls} flyTo calls`);
await page.select('#radio-filter', 'news');
await page.evaluate(() => {
const viewer = window.__godsEyeView.viewer;
viewer.camera.cancelFlight();
viewer.camera.setView({
destination: viewer.scene.globe.ellipsoid.cartographicToCartesian({
longitude: -98 * Math.PI / 180,
latitude: 38 * Math.PI / 180,
height: 18_000_000,
}),
orientation: { heading: 0, pitch: -Math.PI / 2, roll: 0 },
});
});
await sleep(700);
const newsClusterTarget = await page.evaluate(() => {
const gev = window.__godsEyeView;
const source = Array.from({ length: gev.viewer.dataSources.length }, (_, index) => gev.viewer.dataSources.get(index))
.find((item) => item.name === 'Radio stations');
const points = source.clustering._clusterPointCollection;
for (let index = 0; index < (points?.length || 0); index += 1) {
const point = points.get(index);
if (!point.show || !Array.isArray(point.id) || !point.id.length) continue;
const canvasPoint = gev.viewer.scene.cartesianToCanvasCoordinates(point.position);
if (!canvasPoint) continue;
return {
x: canvasPoint.x,
y: canvasPoint.y,
firstId: String(point.id[0]?.id || '').replace(/^radio:/, ''),
pointIds: point.id.map((entity) => entity.id),
};
}
return {
missing: true,
points: Array.from({ length: points?.length || 0 }, (_, index) => ({
show: points.get(index).show,
pointIds: Array.isArray(points.get(index)?.id) ? points.get(index).id.length : 0,
})),
};
});
if (!newsClusterTarget.missing) {
await page.mouse.click(newsClusterTarget.x, newsClusterTarget.y);
await page.waitForFunction((stationId) => {
const state = window.__godsEyeView.dataManager.layers.get('radio').module.getUIState();
return state.selected?.id === stationId && state.audioState === 'playing';
}, {}, newsClusterTarget.firstId);
}
const newsClusterClick = await page.evaluate(() => {
const gev = window.__godsEyeView;
const state = gev.dataManager.layers.get('radio').module.getUIState();
gev.dataManager.layers.get('radio').module.stopPlayback();
return { selectedId: state.selected?.id, audioState: state.audioState };
});
check(
'clicking a News cluster dot plays its first station',
newsClusterTarget?.pointIds?.length > 0
&& newsClusterClick.selectedId === newsClusterTarget.firstId
&& newsClusterClick.audioState === 'playing',
JSON.stringify({ target: newsClusterTarget, result: newsClusterClick }),
);
await page.select('#radio-filter', 'weather');
await sleep(700);
const weatherCluster = await page.evaluate(() => {
const radio = window.__godsEyeView.dataManager.layers.get('radio').module;
return { texts: radio.getOverlayDiagnostics().clusterTexts };
});
check(
'Weather / Emergency view labels its cluster callouts WEATHER',
weatherCluster.texts.length > 0 && weatherCluster.texts.every((text) => /^\d+ WEATHER$/.test(text)),
JSON.stringify(weatherCluster),
);
await page.select('#radio-filter', 'news');
await page.$eval('#context-radio-mini-volume', (input) => {
input.value = '35';
input.dispatchEvent(new Event('input', { bubbles: true }));
});
const compactVolume = await page.evaluate(() => ({
volume: window.__godsEyeView.dataManager.layers.get('radio').module.getUIState().volume,
full: document.getElementById('radio-volume').value,
mini: document.getElementById('context-radio-mini-volume').value,
}));
check('compact volume shares the full Radio player state', Math.abs(compactVolume.volume - 0.35) < 0.001 && compactVolume.full === '35' && compactVolume.mini === '35');
const compactDisclosureContinuity = await page.evaluate(async () => {
const radio = window.__godsEyeView.dataManager.layers.get('radio').module;
const manager = window.__godsEyeView.styleManager;
manager.setPanelCollapsed('global-context-panel', true);
manager._setRadioDisclosure(true);
await radio.play({ origin: 'user' });
const before = radio.getUIState();
const disclosure = document.getElementById('context-radio-toggle-btn');
if (disclosure.getAttribute('aria-expanded') !== 'true') disclosure.click();
disclosure.click();
const closed = {
expanded: disclosure.getAttribute('aria-expanded'),
hidden: document.getElementById('context-radio-mini').hidden,
state: radio.getUIState(),
};
disclosure.click();
return {
before,
closed,
reopened: disclosure.getAttribute('aria-expanded'),
miniHidden: document.getElementById('context-radio-mini').hidden,
after: radio.getUIState(),
};
});
check(
'compact disclosure preserves playback, station, filter, and volume continuity',
compactDisclosureContinuity.closed.expanded === 'false'
&& compactDisclosureContinuity.closed.hidden
&& compactDisclosureContinuity.reopened === 'true'
&& !compactDisclosureContinuity.miniHidden
&& compactDisclosureContinuity.before.audioState === 'playing'
&& compactDisclosureContinuity.closed.state.audioState === 'playing'
&& compactDisclosureContinuity.after.audioState === 'playing'
&& compactDisclosureContinuity.after.selected?.id === compactDisclosureContinuity.before.selected?.id
&& compactDisclosureContinuity.after.filter === compactDisclosureContinuity.before.filter
&& compactDisclosureContinuity.after.volume === compactDisclosureContinuity.before.volume,
JSON.stringify({
before: compactDisclosureContinuity.before,
closed: compactDisclosureContinuity.closed,
reopened: compactDisclosureContinuity.reopened,
after: compactDisclosureContinuity.after,
}),
);
const voicePause = await page.evaluate(async () => {
const filter = document.getElementById('radio-filter');
const firstOption = filter?.options?.[0] || null;
await window.__godsEyeView.dataManager.layers.get('radio').module.play();
const startedAt = performance.now();
window.__godsEyeView.voiceCommands.setStatus('connecting', 'QA voice connect');
const state = window.__godsEyeView.dataManager.layers.get('radio').module.getUIState();
return {
...state,
transitionMs: performance.now() - startedAt,
categoryOptionPreserved: firstOption === filter?.options?.[0],
};
});
check(
'voice activation immediately pauses Radio without changing the user volume',
voicePause.audioState === 'paused' && Math.abs(voicePause.volume - 0.35) < 0.001,
JSON.stringify({ audioState: voicePause.audioState, user: voicePause.volume }),
);
check(
'voice pause preserves Radio category DOM instead of rebuilding the dropdown',
voicePause.categoryOptionPreserved,
`${voicePause.transitionMs.toFixed(1)}ms transition`,
);
const idleHorizonScans = await page.evaluate(async () => {
const radio = window.__godsEyeView.dataManager.layers.get('radio').module;
const camera = window.__godsEyeView.viewer.camera;
camera.cancelFlight();
await new Promise((resolve) => setTimeout(resolve, 350));
const positionBefore = {
x: camera.positionWC.x,
y: camera.positionWC.y,
z: camera.positionWC.z,
};
const before = radio.getStats().horizonScans;
await new Promise((resolve) => setTimeout(resolve, 850));
const dx = camera.positionWC.x - positionBefore.x;
const dy = camera.positionWC.y - positionBefore.y;
const dz = camera.positionWC.z - positionBefore.z;
return {
before,
after: radio.getStats().horizonScans,
cameraDeltaM: Math.hypot(dx, dy, dz),
};
});
check(
'stationary Radio view skips repeated 750-marker horizon scans',
idleHorizonScans.after === idleHorizonScans.before,
JSON.stringify(idleHorizonScans),
);
const idleVoice = await page.evaluate(() => {
const voice = window.__godsEyeView.voiceCommands;
voice.setMicrophoneEnabled(true);
voice.setStatus('listening', 'QA standby');
voice.setVoiceSpeaker('idle');
return window.__godsEyeView.dataManager.layers.get('radio').module.getUIState();
});
check(
'idle voice never auto-resumes a station paused for voice',
idleVoice.audioState === 'paused',
idleVoice.audioState,
);
const manualPlayTarget = await page.evaluate(() => {
const manager = window.__godsEyeView.styleManager;
manager.setPanelCollapsed('global-context-panel', true);
manager._setRadioDisclosure(true);
const button = document.getElementById('context-radio-mini-play-btn');
const rect = button.getBoundingClientRect();
const style = getComputedStyle(button);
return {
visible: !button.hidden
&& style.display !== 'none'
&& style.visibility !== 'hidden'
&& Number(style.opacity) > 0
&& rect.width > 0
&& rect.height > 0,
enabled: !button.disabled,
rect: { width: rect.width, height: rect.height },
};
});
check(
'manual voice takeover targets the visible compact Radio Play control',
manualPlayTarget.visible && manualPlayTarget.enabled,
JSON.stringify(manualPlayTarget),
);
if (manualPlayTarget.visible && manualPlayTarget.enabled) {
await page.click('#context-radio-mini-play-btn');
await page.waitForFunction(() => (
window.__godsEyeView.dataManager.layers.get('radio').module.getUIState().audioState === 'playing'
&& window.__godsEyeView.voiceCommands.status === 'idle'
));
}
const manualTakeover = await page.evaluate(() => ({
audioState: window.__godsEyeView.dataManager.layers.get('radio').module.getUIState().audioState,
voiceStatus: window.__godsEyeView.voiceCommands.status,
voiceDucked: window.__godsEyeView.dataManager.layers.get('radio').module.getUIState().voiceDucked,
}));
check(
'confirmed manual Radio Play closes active voice and preserves playback',
manualTakeover.audioState === 'playing'
&& manualTakeover.voiceStatus === 'idle'
&& !manualTakeover.voiceDucked,
JSON.stringify(manualTakeover),
);
await page.evaluate(() => window.__godsEyeView.voiceCommands.setStatus('listening', 'QA standby'));
const explicitResume = await page.evaluate(async () => {
const voice = window.__godsEyeView.voiceCommands;
const radio = window.__godsEyeView.dataManager.layers.get('radio').module;
await radio.play();
const beforeSpace = radio.getUIState().audioState;
document.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, code: 'Space', key: ' ' }));
const afterSpace = radio.getUIState().audioState;
document.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true, code: 'Space', key: ' ' }));
await radio.play();
voice.setVoiceSpeaker('ai');
return { beforeSpace, afterSpace, afterAi: radio.getUIState().audioState };
});
check(
'holding Space and assistant speech each pause an explicitly resumed station',
explicitResume.beforeSpace === 'playing' && explicitResume.afterSpace === 'paused' && explicitResume.afterAi === 'paused',
JSON.stringify(explicitResume),
);
await page.evaluate(() => window.__godsEyeView.voiceCommands.setStatus('idle', 'QA complete'));
await page.evaluate(async () => {
const manager = window.__godsEyeView.styleManager;
manager._setRadioDisclosure(false);
manager.setPanelCollapsed('global-context-panel', false);
manager.setPanelCollapsed('radio-panel', false);
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
});
await page.waitForFunction(() => {
const tuner = document.getElementById('radio-tuner');
const slider = document.getElementById('radio-tuner-slider');
return !tuner.hidden
&& tuner.getBoundingClientRect().width > 0
&& slider.getBoundingClientRect().width > 0;
});
const horizon = await page.evaluate(async () => {
const viewer = window.__godsEyeView.viewer;
const ellipsoid = viewer.scene.globe.ellipsoid;
const source = Array.from({ length: viewer.dataSources.length }, (_, index) => viewer.dataSources.get(index))
.find((item) => item.name === 'Radio stations');
const visibleIds = () => source.entities.values.filter((entity) => entity.show).map((entity) => entity.id);
const setView = (lon, lat = 0) => {
viewer.camera.cancelFlight();
viewer.camera.setView({
destination: ellipsoid.cartographicToCartesian({ longitude: lon * Math.PI / 180, latitude: lat * Math.PI / 180, height: 2_000_000 }),
orientation: { heading: 0, pitch: -Math.PI / 2, roll: 0 },
});
};
setView(-54);
await new Promise((resolve) => setTimeout(resolve, 500));
const front = visibleIds();
setView(180, -2);
await new Promise((resolve) => setTimeout(resolve, 500));
const back = visibleIds();
const frontSet = new Set(front);
return {
front: front.length,
back: back.length,
changed: back.some((id) => !frontSet.has(id)),
};
});
check('far-side markers are horizon culled', horizon.front > 0 && horizon.front < 750 && horizon.back > 0 && horizon.back < 750, `${horizon.front}/${horizon.back} visible`);
check('horizon visibility follows camera hemisphere', horizon.changed);
await page.select('#radio-filter', 'all');
const absoluteTuner = await page.evaluate(async () => {
const gev = window.__godsEyeView;
const style = gev.styleManager;
const radio = gev.dataManager.layers.get('radio').module;
const slider = document.getElementById('radio-tuner-slider');
const tunerRect = document.getElementById('radio-tuner').getBoundingClientRect();
const dialRect = document.querySelector('.radio-tuner-dial').getBoundingClientRect();
const sliderRect = slider.getBoundingClientRect();
const panelRect = document.querySelector('.radio-panel-inner').getBoundingClientRect();
const count = style._radioTunerStations.length;
const usable = sliderRect.width - 14;
const xFor = (coordinate) => sliderRect.left + 7 + usable * coordinate / Math.max(1, count - 1);
const pointer = (type, x, pointerId = 71) => slider.dispatchEvent(new PointerEvent(type, {
bubbles: true,
pointerId,
pointerType: 'mouse',
clientX: x,
clientY: sliderRect.top + sliderRect.height / 2,
buttons: type === 'pointerup' || type === 'pointercancel' ? 0 : 1,
}));
const snapshot = () => {
const markerId = gev.viewer.entities.values
.find((entity) => String(entity.id).startsWith('radio:selected:'))?.id || null;
return {
slot: Number(slider.value),
markerId,
ratio: Number(document.getElementById('radio-tuner').style.getPropertyValue('--radio-tuner-ratio')),
needleX: document.getElementById('radio-tuner-needle').getBoundingClientRect().left,
ticks: Array.from(document.querySelectorAll('.radio-tuner-tick')).map((tick) => ({
index: Number(tick.dataset.stationIndex),
x: tick.getBoundingClientRect().left,
})),
};
};
const playsBefore = window.__qaRadioPlayCalls.length;
const frozenSignature = style._radioTunerBandSignature;
pointer('pointerdown', xFor(0));
const left = snapshot();
const centerCoordinate = (count - 1) / 2;
pointer('pointermove', xFor(centerCoordinate));
const center = snapshot();
pointer('pointermove', xFor(centerCoordinate + 2));
const shifted = snapshot();
pointer('pointermove', xFor(count - 1));
const right = snapshot();
const shiftedByIndex = new Map(shifted.ticks.map((tick) => [tick.index, tick.x]));
const sharedTick = center.ticks.find((tick) => shiftedByIndex.has(tick.index));
const tapeDelta = sharedTick ? shiftedByIndex.get(sharedTick.index) - sharedTick.x : null;
const needleDelta = shifted.needleX - center.needleX;
pointer('pointercancel', xFor(count - 1));
const state = radio.getUIState();
return {
visible: !document.getElementById('radio-tuner').hidden,
stationCount: count,
sliderMax: Number(slider.max),
bandFrozen: style._radioTunerBandSignature === frozenSignature,
left,
center,
shifted,
right,
leftExpectedId: style._radioTunerStations[0]?.id || null,
centerExpectedId: style._radioTunerStations[Math.floor(centerCoordinate + 0.5)]?.id || null,
rightExpectedId: style._radioTunerStations[count - 1]?.id || null,
centerExpected: Math.floor(centerCoordinate + 0.5),
tapeDelta,
needleDelta,
maxTickCount: Math.max(left.ticks.length, center.ticks.length, shifted.ticks.length, right.ticks.length),
noPreviewPlay: window.__qaRadioPlayCalls.length === playsBefore,
tuningActiveAfterCancel: state.tuningActive,
tuningStaticAfterCancel: state.tuningStatic,
contained: tunerRect.left >= panelRect.left && tunerRect.right <= panelRect.right
&& dialRect.left >= tunerRect.left && dialRect.right <= tunerRect.right
&& sliderRect.left >= dialRect.left && sliderRect.right <= dialRect.right,
};
});
check(
'All maps the complete directory to real left, center, and right station snap points',
absoluteTuner.visible && absoluteTuner.stationCount === 750 && absoluteTuner.sliderMax === 749
&& absoluteTuner.left.slot === 0
&& absoluteTuner.left.markerId === `radio:selected:${absoluteTuner.leftExpectedId}`
&& absoluteTuner.center.slot === absoluteTuner.centerExpected
&& absoluteTuner.center.markerId === `radio:selected:${absoluteTuner.centerExpectedId}`
&& absoluteTuner.right.slot === absoluteTuner.stationCount - 1
&& absoluteTuner.right.markerId === `radio:selected:${absoluteTuner.rightExpectedId}`
&& absoluteTuner.bandFrozen && absoluteTuner.noPreviewPlay
&& !absoluteTuner.tuningActiveAfterCancel && !absoluteTuner.tuningStaticAfterCancel
&& absoluteTuner.contained,
JSON.stringify(absoluteTuner),
);
check(
'the bounded virtual tape moves left faster than the needle moves right',
absoluteTuner.maxTickCount > 0 && absoluteTuner.maxTickCount < 100
&& absoluteTuner.needleDelta > 0 && absoluteTuner.tapeDelta < 0
&& Math.abs(absoluteTuner.tapeDelta) >= absoluteTuner.needleDelta * 4,
JSON.stringify({
maxTickCount: absoluteTuner.maxTickCount,
needleDelta: absoluteTuner.needleDelta,
tapeDelta: absoluteTuner.tapeDelta,
}),
);
const tunerCancelRestore = await page.evaluate(() => {
const gev = window.__godsEyeView;
const style = gev.styleManager;
const radio = gev.dataManager.layers.get('radio').module;
const slider = document.getElementById('radio-tuner-slider');
// Capture a fresh gesture's complete presentation anchor before moving
// to a different absolute directory station.
const beforeStations = [...style._radioTunerStations];
const sliderRect = slider.getBoundingClientRect();
const xFor = (index) => sliderRect.left + 7
+ (sliderRect.width - 14) * index / Math.max(1, beforeStations.length - 1);
const pointer = (type, index, pointerId = 52) => slider.dispatchEvent(new PointerEvent(type, {
bubbles: true,
pointerId,
pointerType: 'mouse',
clientX: xFor(index),
clientY: sliderRect.top + sliderRect.height / 2,
buttons: type === 'pointercancel' || type === 'pointerup' ? 0 : 1,
}));
const camera = gev.viewer.camera;
const originalFlyTo = camera.flyTo;
const flyToCalls = [];
camera.flyTo = (options) => flyToCalls.push(options);
const before = {
signature: style._radioTunerBandSignature,
ids: beforeStations.map((station) => station.id),
slot: Number(slider.value),
selectedId: radio.getUIState().selected?.id || null,
plays: window.__qaRadioPlayCalls.length,
};
const targetIndex = before.slot < beforeStations.length - 2 ? before.slot + 2 : before.slot - 2;
pointer('pointerdown', before.slot);
pointer('pointermove', targetIndex);
const shiftedSlot = Number(slider.value);
const previewBeforeCancel = gev.viewer.entities.values
.find((entity) => String(entity.id).startsWith('radio:selected:'))?.id || null;
const flyToCallsBeforeCancel = flyToCalls.length;
pointer('pointercancel', targetIndex);
const afterStations = style._radioTunerStations;
const afterState = radio.getUIState();
const after = {
signature: style._radioTunerBandSignature,
ids: afterStations.map((station) => station.id),
slot: Number(slider.value),
selectedId: afterState.selected?.id || null,
plays: window.__qaRadioPlayCalls.length,
previewId: null,
tuningActive: afterState.tuningActive,
};
const flyToCallsAfterCancel = flyToCalls.length;
const selectedEntity = () => gev.viewer.entities.values
.find((entity) => String(entity.id).startsWith('radio:selected:')) || null;
const restoredEntity = selectedEntity();
const previewId = restoredEntity?.id || null;
after.previewId = previewId;
const filterControl = document.getElementById('radio-filter');
const changedFilter = afterState.filter === 'news' ? 'all' : 'news';
const changedFilterSnapshot = radio.getAcceptedCatalogSnapshot();
const changedFilterPlays = window.__qaRadioPlayCalls.length;
const changedFilterCameraMoves = flyToCalls.length;
filterControl.value = changedFilter;
filterControl.dispatchEvent(new Event('change', { bubbles: true }));
const changedFilterState = radio.getUIState();
const changedFilterEntity = selectedEntity();
const changedFilterCleanup = {
filter: changedFilterState.filter,
restoredStationId: changedFilterState.tuningRestoredStationId,
selectedId: changedFilterState.selected?.id || null,
entityRebuilt: Boolean(changedFilterEntity && changedFilterEntity !== restoredEntity),
entityId: changedFilterEntity?.id || null,
snapshotPreserved: radio.getAcceptedCatalogSnapshot() === changedFilterSnapshot,
playDelta: window.__qaRadioPlayCalls.length - changedFilterPlays,
cameraDelta: flyToCalls.length - changedFilterCameraMoves,
};
const sameFilterBegan = radio.beginTuning();
const sameFilterCancelled = radio.cancelTuning();
const sameFilterRestoredState = radio.getUIState();
const sameFilterRestoredEntity = selectedEntity();
const sameFilterSnapshot = radio.getAcceptedCatalogSnapshot();
const sameFilterPlays = window.__qaRadioPlayCalls.length;
const sameFilterCameraMoves = flyToCalls.length;
filterControl.value = sameFilterRestoredState.filter;
filterControl.dispatchEvent(new Event('change', { bubbles: true }));
const sameFilterState = radio.getUIState();
const sameFilterEntity = selectedEntity();
const sameFilterCleanup = {
began: sameFilterBegan,
cancelled: sameFilterCancelled,
restoredBefore: sameFilterRestoredState.tuningRestoredStationId,
restoredAfter: sameFilterState.tuningRestoredStationId,
filter: sameFilterState.filter,
selectedId: sameFilterState.selected?.id || null,
entityRebuilt: Boolean(sameFilterEntity && sameFilterEntity !== sameFilterRestoredEntity),
entityId: sameFilterEntity?.id || null,
snapshotPreserved: radio.getAcceptedCatalogSnapshot() === sameFilterSnapshot,
playDelta: window.__qaRadioPlayCalls.length - sameFilterPlays,
cameraDelta: flyToCalls.length - sameFilterCameraMoves,
};
filterControl.value = afterState.filter;
filterControl.dispatchEvent(new Event('change', { bubbles: true }));
camera.flyTo = originalFlyTo;
return {
before,
targetIndex,
shiftedSlot,
previewBeforeCancel,
flyToCallsBeforeCancel,
flyToCallsAfterCancel,
changedFilter,
changedFilterCleanup,
sameFilterCleanup,
after,
identityRestored: afterStations.length === beforeStations.length
&& afterStations.every((station, index) => station === beforeStations[index]),
};
});
check(
'pointer cancel restores the exact pre-drag full-directory tuner anchor without playback',
tunerCancelRestore.shiftedSlot === tunerCancelRestore.targetIndex
&& tunerCancelRestore.after.signature === tunerCancelRestore.before.signature
&& JSON.stringify(tunerCancelRestore.after.ids) === JSON.stringify(tunerCancelRestore.before.ids)
&& tunerCancelRestore.after.slot === tunerCancelRestore.before.slot
&& tunerCancelRestore.after.selectedId === tunerCancelRestore.before.selectedId
&& tunerCancelRestore.after.plays === tunerCancelRestore.before.plays
&& tunerCancelRestore.previewBeforeCancel !== `radio:selected:${tunerCancelRestore.before.selectedId}`
&& tunerCancelRestore.flyToCallsBeforeCancel > 0
&& tunerCancelRestore.flyToCallsAfterCancel === tunerCancelRestore.flyToCallsBeforeCancel
&& tunerCancelRestore.after.previewId === `radio:selected:${tunerCancelRestore.before.selectedId}`
&& !tunerCancelRestore.after.tuningActive
&& tunerCancelRestore.identityRestored,
JSON.stringify(tunerCancelRestore),
);
check(
'accepted changed-filter action clears and rebuilds the cancel-restored marker without side effects',
tunerCancelRestore.changedFilterCleanup.filter === tunerCancelRestore.changedFilter
&& tunerCancelRestore.changedFilterCleanup.restoredStationId === null
&& tunerCancelRestore.changedFilterCleanup.selectedId === tunerCancelRestore.before.selectedId
&& tunerCancelRestore.changedFilterCleanup.entityRebuilt
&& tunerCancelRestore.changedFilterCleanup.entityId === `radio:selected:${tunerCancelRestore.before.selectedId}`
&& tunerCancelRestore.changedFilterCleanup.snapshotPreserved
&& tunerCancelRestore.changedFilterCleanup.playDelta === 0
&& tunerCancelRestore.changedFilterCleanup.cameraDelta === 0,
JSON.stringify(tunerCancelRestore.changedFilterCleanup),
);
check(
'accepted same-filter action clears and rebuilds the cancel-restored marker without side effects',
tunerCancelRestore.sameFilterCleanup.began
&& tunerCancelRestore.sameFilterCleanup.cancelled
&& tunerCancelRestore.sameFilterCleanup.restoredBefore === tunerCancelRestore.before.selectedId
&& tunerCancelRestore.sameFilterCleanup.restoredAfter === null
&& tunerCancelRestore.sameFilterCleanup.filter === tunerCancelRestore.changedFilter
&& tunerCancelRestore.sameFilterCleanup.selectedId === tunerCancelRestore.before.selectedId
&& tunerCancelRestore.sameFilterCleanup.entityRebuilt
&& tunerCancelRestore.sameFilterCleanup.entityId === `radio:selected:${tunerCancelRestore.before.selectedId}`
&& tunerCancelRestore.sameFilterCleanup.snapshotPreserved
&& tunerCancelRestore.sameFilterCleanup.playDelta === 0
&& tunerCancelRestore.sameFilterCleanup.cameraDelta === 0,
JSON.stringify(tunerCancelRestore.sameFilterCleanup),
);
const tunerRefreshTarget = await page.evaluate(() => {
const gev = window.__godsEyeView;
const slider = document.getElementById('radio-tuner-slider');
const stations = gev.styleManager._radioTunerStations;
const targetIndex = 1;
const rect = slider.getBoundingClientRect();
const clientX = rect.left + 7 + (rect.width - 14) * targetIndex / Math.max(1, stations.length - 1);
slider.dispatchEvent(new PointerEvent('pointerdown', {
bubbles: true,
pointerId: 51,
pointerType: 'mouse',
clientX,
clientY: rect.top + rect.height / 2,
buttons: 1,
}));
const station = stations[targetIndex];
return {
id: station.id,
name: station.name,
clientX,
clientY: rect.top + rect.height / 2,
generation: gev.dataManager.layers.get('radio').module.getUIState().tuningCatalogGeneration,
plays: window.__qaRadioPlayCalls.length,
};
});
catalogStations = stations.map((station) => (
station.id === tunerRefreshTarget.id
? { ...station, name: `${station.name} B`, streamUrl: `${station.streamUrl}?generation=b` }
: station
));
catalogGeneration = 2;
await page.evaluate(async () => {
await window.__godsEyeView.dataManager.layers.get('radio').module.update();
});
const tunerRefreshRelease = await page.evaluate((target) => {
const gev = window.__godsEyeView;
const slider = document.getElementById('radio-tuner-slider');
const displayedBeforeRelease = document.getElementById('radio-tuner-station').textContent;
const frozenGeneration = gev.dataManager.layers.get('radio').module.getUIState().tuningCatalogGeneration;
slider.dispatchEvent(new PointerEvent('pointerup', {
bubbles: true,
pointerId: 51,
pointerType: 'mouse',
clientX: target.clientX,
clientY: target.clientY,
}));
const state = gev.dataManager.layers.get('radio').module.getUIState();
return {
displayedBeforeRelease,
frozenGeneration,
unavailableId: state.tuningUnavailableStationId,
selectedId: state.selected?.id || null,
plays: window.__qaRadioPlayCalls.length,
value: document.getElementById('radio-tuner-value').textContent,
stationText: document.getElementById('radio-tuner-station').textContent,
message: document.getElementById('radio-playback-state').textContent,
targetId: target.id,
};
}, tunerRefreshTarget);
await page.screenshot({ path: path.join(SHOTS_DIR, 'tuner-refresh-unavailable.png') });
check(
'mid-drag catalog replacement keeps generation A visible and reports release unavailable',
tunerRefreshTarget.generation === 1
&& tunerRefreshRelease.frozenGeneration === 1
&& tunerRefreshRelease.displayedBeforeRelease === tunerRefreshTarget.name
&& tunerRefreshRelease.unavailableId === tunerRefreshTarget.id
&& tunerRefreshRelease.selectedId !== tunerRefreshTarget.id
&& tunerRefreshRelease.plays === tunerRefreshTarget.plays
&& tunerRefreshRelease.value === 'OFF AIR'
&& tunerRefreshRelease.stationText === 'STATION UNAVAILABLE'
&& /unavailable after directory refresh/i.test(tunerRefreshRelease.message),
JSON.stringify(tunerRefreshRelease),
);
catalogStations = stations;
catalogGeneration = 3;
await page.evaluate(async () => {
await window.__godsEyeView.dataManager.layers.get('radio').module.update();
});
const tunerDirectRelease = await page.evaluate(async () => {
const gev = window.__godsEyeView;
const slider = document.getElementById('radio-tuner-slider');
const needle = document.getElementById('radio-tuner-needle');
const startSlot = Number(slider.value);
const max = Number(slider.max);
const direction = startSlot < max ? 1 : -1;
const targetIndex = startSlot + direction;
const targetId = gev.styleManager._radioTunerStations[targetIndex]?.id || null;
const rect = slider.getBoundingClientRect();
const xFor = (index) => rect.left + 7 + (rect.width - 14) * index / Math.max(1, max);
const pointer = (type, index) => slider.dispatchEvent(new PointerEvent(type, {
bubbles: true,
pointerId: 41,
pointerType: 'mouse',
clientX: xFor(index),
clientY: rect.top + rect.height / 2,
buttons: type === 'pointerup' ? 0 : 1,
}));
const playsBefore = window.__qaRadioPlayCalls.length;
pointer('pointerdown', startSlot);
pointer('pointermove', targetIndex);
const previewPlays = window.__qaRadioPlayCalls.length;
pointer('pointerup', targetIndex);
const samples = [needle.getBoundingClientRect().left];
await new Promise((resolve) => requestAnimationFrame(resolve));
samples.push(needle.getBoundingClientRect().left);
await new Promise((resolve) => setTimeout(resolve, 60));
samples.push(needle.getBoundingClientRect().left);
await new Promise((resolve) => setTimeout(resolve, 180));
samples.push(needle.getBoundingClientRect().left);
const state = gev.dataManager.layers.get('radio').module.getUIState();
return {
direction,
targetId,
selectedId: state.selected?.id || null,
sliderSlot: Number(slider.value),
expectedSlot: targetIndex,
previewPlayDelta: previewPlays - playsBefore,
commitPlayDelta: window.__qaRadioPlayCalls.length - playsBefore,
samples,
spread: Math.max(...samples) - Math.min(...samples),
};
});
check(
'pointer preview never plays and release commits the nearest station once without a needle sweep',
tunerDirectRelease.targetId
&& tunerDirectRelease.selectedId === tunerDirectRelease.targetId
&& tunerDirectRelease.sliderSlot === tunerDirectRelease.expectedSlot
&& tunerDirectRelease.previewPlayDelta === 0
&& tunerDirectRelease.commitPlayDelta === 1
&& tunerDirectRelease.spread < 0.5,
JSON.stringify(tunerDirectRelease),
);
const tunerCommitTarget = await page.evaluate(() => {
const gev = window.__godsEyeView;
const slider = document.getElementById('radio-tuner-slider');
const targetIndex = 11;
slider.dispatchEvent(new PointerEvent('pointercancel', { bubbles: true, pointerId: 1 }));
const station = gev.styleManager._radioTunerStations[targetIndex];
const rect = slider.getBoundingClientRect();
const currentRatio = Number(slider.value) / Math.max(1, Number(slider.max));
window.__qaRadioDelayNextPlay = true;
return {
id: station.id,
targetIndex,
count: gev.styleManager._radioTunerStations.length,
frozenSignature: gev.styleManager._radioTunerBandSignature,
startX: rect.left + 7 + (rect.width - 14) * currentRatio,
targetX: rect.left + 7 + (rect.width - 14) * targetIndex
/ Math.max(1, gev.styleManager._radioTunerStations.length - 1),
y: rect.top + rect.height / 2,
};
});
await page.mouse.move(tunerCommitTarget.startX, tunerCommitTarget.y);
await page.mouse.down();
await page.mouse.move(tunerCommitTarget.targetX, tunerCommitTarget.y, { steps: 12 });
await page.mouse.up();
await sleep(650);
const tunerCommit = await page.evaluate((target) => {
const gev = window.__godsEyeView;
const slider = document.getElementById('radio-tuner-slider');
const state = gev.dataManager.layers.get('radio').module.getUIState();
return {
id: target.id,
audioState: state.audioState,
tuningStatic: state.tuningStatic,
awaiting: state.tuningAwaitingStationId,
signatureStable: gev.styleManager._radioTunerBandSignature === target.frozenSignature,
selectedIndex: gev.styleManager._radioTunerStations.findIndex((item) => item.id === state.selected?.id),
sliderSlot: Number(slider.value),
ratio: Number(document.getElementById('radio-tuner').style.getPropertyValue('--radio-tuner-ratio')),
pinned: gev.styleManager._radioTunerBandPinnedForNavigation,
};
}, tunerCommitTarget);
check(
'tuner release keeps channel 12 pinned while its broadcaster connects',
tunerCommit.audioState === 'loading' && tunerCommit.tuningStatic && tunerCommit.awaiting === tunerCommit.id
&& tunerCommit.signatureStable && tunerCommit.selectedIndex === 11
&& tunerCommit.sliderSlot === 11
&& Math.abs(tunerCommit.ratio - 11 / Math.max(1, tunerCommitTarget.count - 1)) < 0.0001
&& tunerCommit.pinned,
JSON.stringify(tunerCommit),
);
await page.evaluate(() => window.__qaReleaseRadioPlay());
await page.waitForFunction(
(id) => {
const state = window.__godsEyeView.dataManager.layers.get('radio').module.getUIState();
return state.selected?.id === id && state.audioState === 'playing' && !state.tuningActive && !state.tuningStatic;
},
{},
tunerCommit.id,
);
const broadcastIndicator = await page.evaluate(() => ({
active: document.getElementById('title-bar').classList.contains('radio-broadcasting'),
innerOpacity: getComputedStyle(document.querySelector('#title-bar .title-logo'), '::before').opacity,
outerOpacity: getComputedStyle(document.querySelector('#title-bar .title-logo'), '::after').opacity,
}));
check('actual broadcaster playback fades out the tuner static', true, tunerCommit.id);
check(
'confirmed playback shows two restrained broadcast waves above the main logo',
broadcastIndicator.active
&& Number(broadcastIndicator.innerOpacity) > 0 && Number(broadcastIndicator.innerOpacity) <= 0.28
&& Number(broadcastIndicator.outerOpacity) > 0 && Number(broadcastIndicator.outerOpacity) <= 0.17,
JSON.stringify(broadcastIndicator),
);
const microDragStart = await page.evaluate(() => {
const style = window.__godsEyeView.styleManager;
const slider = document.getElementById('radio-tuner-slider');
const rect = slider.getBoundingClientRect();
const ratio = Number(slider.value) / Math.max(1, Number(slider.max));
return {
x: rect.left + 7 + (rect.width - 14) * ratio,
y: rect.top + rect.height / 2,
ratio,
usableWidth: rect.width - 14,
signature: style._radioTunerBandSignature,
};
});
const microDragSamples = [];
await page.mouse.move(microDragStart.x, microDragStart.y);
await page.mouse.down();
for (const delta of [1, 2, 10]) {
await page.mouse.move(microDragStart.x + delta, microDragStart.y);
await sleep(80);
microDragSamples.push(await page.evaluate((pixelDelta) => {
const style = window.__godsEyeView.styleManager;
const slider = document.getElementById('radio-tuner-slider');
return {
pixelDelta,
slot: Number(slider.value),
ratio: Number(document.getElementById('radio-tuner').style.getPropertyValue('--radio-tuner-ratio')),
signature: style._radioTunerBandSignature,
};
}, delta));
}
await page.mouse.move(microDragStart.x, microDragStart.y);
await page.mouse.up();
await page.screenshot({ path: path.join(SHOTS_DIR, 'tuner-micro-drag.png') });
const microDragPlayback = await page.evaluate(async () => {
const mod = window.__godsEyeView.dataManager.layers.get('radio').module;
const deadline = Date.now() + 2_000;
let state = mod.getUIState();
while (state.audioState === 'loading' && Date.now() < deadline) {
await new Promise((resolve) => setTimeout(resolve, 50));
state = mod.getUIState();
}
if (state.audioState !== 'playing') {
await mod.play({ origin: 'user' });
state = mod.getUIState();
}
return { audioState: state.audioState, selectedId: state.selected?.id || null };
});
check(
'native micro-drag leaves the selected station playable',
microDragPlayback.audioState === 'playing' && Boolean(microDragPlayback.selectedId),
JSON.stringify(microDragPlayback),
);
check(
'native 1px, 2px, and 10px tuner moves stay monotonic on one frozen strip',
microDragSamples.every((sample) => sample.signature === microDragStart.signature
&& Math.abs(sample.ratio - (microDragStart.ratio + sample.pixelDelta / microDragStart.usableWidth)) < 0.002)
&& microDragSamples.every((sample, index) => index === 0
|| (sample.slot >= microDragSamples[index - 1].slot
&& sample.ratio >= microDragSamples[index - 1].ratio)),
JSON.stringify(microDragSamples),
);
const viewportTuner = await page.evaluate(async (selectedId) => {
const gev = window.__godsEyeView;
const style = gev.styleManager;
const before = {
signature: style._radioTunerBandSignature,
ids: style._radioTunerStations.map((station) => station.id),
selectedIndex: style._radioTunerStations.findIndex((station) => station.id === selectedId),
needle: Number(document.getElementById('radio-tuner').style.getPropertyValue('--radio-tuner-ratio')),
};
const current = gev.viewer.camera.positionCartographic;
const oppositeLongitude = ((current.longitude + 2 * Math.PI) % (2 * Math.PI)) - Math.PI;
gev.viewer.canvas.dispatchEvent(new WheelEvent('wheel', { bubbles: true, deltaY: 1 }));
gev.viewer.camera.setView({
destination: gev.viewer.scene.globe.ellipsoid.cartographicToCartesian({
longitude: oppositeLongitude,
latitude: -current.latitude,
height: 2_000_000,
}),
orientation: { heading: 0, pitch: -Math.PI / 2, roll: 0 },
});
gev.viewer.camera.changed.raiseEvent();
await new Promise((resolve) => setTimeout(resolve, 400));
const stations = gev.styleManager._radioTunerStations;
const selectedIndex = stations.findIndex((station) => station.id === selectedId);
return {
before,
signature: style._radioTunerBandSignature,
ids: stations.map((station) => station.id),
count: stations.length,
selectedIndex,
needle: Number(document.getElementById('radio-tuner').style.getPropertyValue('--radio-tuner-ratio')),
};
}, tunerCommit.id);
check(
'globe rotation leaves the catalog/filter directory order and absolute needle unchanged',
viewportTuner.count === 750
&& viewportTuner.signature === viewportTuner.before.signature
&& JSON.stringify(viewportTuner.ids) === JSON.stringify(viewportTuner.before.ids)
&& viewportTuner.selectedIndex === viewportTuner.before.selectedIndex
&& Math.abs(viewportTuner.needle - viewportTuner.before.needle) < 0.0001,
JSON.stringify(viewportTuner),
);
const nextNeedleBefore = await page.evaluate(() => {
const style = window.__godsEyeView.styleManager;
const selectedId = window.__godsEyeView.dataManager.layers.get('radio').module.getUIState().selected?.id;
const selectedIndex = style._radioTunerStations.findIndex((station) => station.id === selectedId);
const selectedPoolIndex = style._radioTunerPool.findIndex((station) => station.id === selectedId);
const expectedPoolIndex = (selectedPoolIndex + 1) % style._radioTunerPool.length;
return {
signature: style._radioTunerBandSignature,
selectedIndex,
count: style._radioTunerStations.length,
expectedId: style._radioTunerPool[expectedPoolIndex]?.id || null,
};
});
await page.$eval('#radio-next-btn', (button) => button.click());
await sleep(1500);
const nextNeedleAfter = await page.evaluate(() => {
const style = window.__godsEyeView.styleManager;
const selectedId = window.__godsEyeView.dataManager.layers.get('radio').module.getUIState().selected?.id;
const selectedIndex = style._radioTunerStations.findIndex((station) => station.id === selectedId);
return {
signature: style._radioTunerBandSignature,
selectedId,
selectedIndex,
count: style._radioTunerStations.length,
sliderSlot: Number(document.getElementById('radio-tuner-slider').value),
ratio: Number(document.getElementById('radio-tuner').style.getPropertyValue('--radio-tuner-ratio')),
pinned: style._radioTunerBandPinnedForNavigation,
};
});
check(
'Next advances one absolute station and keeps the needle on its full-directory index',
nextNeedleAfter.selectedId === nextNeedleBefore.expectedId
&& nextNeedleAfter.signature === nextNeedleBefore.signature
&& nextNeedleAfter.count === nextNeedleBefore.count
&& nextNeedleAfter.sliderSlot === nextNeedleAfter.selectedIndex
&& Math.abs(nextNeedleAfter.ratio
- nextNeedleAfter.selectedIndex / Math.max(1, nextNeedleAfter.count - 1)) < 0.0001,
JSON.stringify({ before: nextNeedleBefore, after: nextNeedleAfter }),
);
const tunerKeyboard = await page.evaluate(() => {
const gev = window.__godsEyeView;
const style = gev.styleManager;
const radio = gev.dataManager.layers.get('radio').module;
const slider = document.getElementById('radio-tuner-slider');
const key = (type, value) => slider.dispatchEvent(new KeyboardEvent(type, {
bubbles: true,
key: value,
}));
const markerId = () => gev.viewer.entities.values
.find((entity) => String(entity.id).startsWith('radio:selected:'))?.id || null;
const startIndex = Number(slider.value);
const expectedIndex = Math.min(style._radioTunerStations.length - 1, startIndex + 1);
const expectedId = style._radioTunerStations[expectedIndex]?.id || null;
const playsBefore = window.__qaRadioPlayCalls.length;
key('keydown', 'ArrowRight');
const preview = {
slot: Number(slider.value),
markerId: markerId(),
tuningActive: radio.getUIState().tuningActive,
playDelta: window.__qaRadioPlayCalls.length - playsBefore,
};
key('keyup', 'ArrowRight');
const committed = {
selectedId: radio.getUIState().selected?.id || null,
slot: Number(slider.value),
tuningActive: radio.getUIState().tuningActive,
playDelta: window.__qaRadioPlayCalls.length - playsBefore,
};
const committedPlays = window.__qaRadioPlayCalls.length;
key('keydown', 'ArrowRight');
key('keydown', 'Escape');
const cancelled = {
selectedId: radio.getUIState().selected?.id || null,
slot: Number(slider.value),
tuningActive: radio.getUIState().tuningActive,
playDelta: window.__qaRadioPlayCalls.length - committedPlays,
};
const count = style._radioTunerStations.length;
const pageStep = Math.max(1, Math.round((count - 1) / 10));
const previewAndCancel = (value, expectedSlot) => {
const beforePlays = window.__qaRadioPlayCalls.length;
key('keydown', value);
const result = {
key: value,
slot: Number(slider.value),
expectedSlot,
playDelta: window.__qaRadioPlayCalls.length - beforePlays,
};
key('keydown', 'Escape');
result.restoredSlot = Number(slider.value);
result.cancelPlayDelta = window.__qaRadioPlayCalls.length - beforePlays;
return result;
};
const base = committed.slot;
const navigation = [
previewAndCancel('Home', 0),
previewAndCancel('End', count - 1),
previewAndCancel('PageUp', Math.min(count - 1, base + pageStep)),
previewAndCancel('PageDown', Math.max(0, base - pageStep)),
];
return { startIndex, expectedIndex, expectedId, preview, committed, cancelled, navigation };
});
check(
'keyboard preview is silent, key release commits once, and Escape restores the committed station',
tunerKeyboard.preview.slot === tunerKeyboard.expectedIndex
&& tunerKeyboard.preview.markerId === `radio:selected:${tunerKeyboard.expectedId}`
&& tunerKeyboard.preview.tuningActive && tunerKeyboard.preview.playDelta === 0
&& tunerKeyboard.committed.selectedId === tunerKeyboard.expectedId
&& tunerKeyboard.committed.slot === tunerKeyboard.expectedIndex
&& !tunerKeyboard.committed.tuningActive && tunerKeyboard.committed.playDelta === 1
&& tunerKeyboard.cancelled.selectedId === tunerKeyboard.expectedId
&& tunerKeyboard.cancelled.slot === tunerKeyboard.expectedIndex
&& !tunerKeyboard.cancelled.tuningActive && tunerKeyboard.cancelled.playDelta === 0,
JSON.stringify(tunerKeyboard),
);
check(
'Home, End, PageUp, and PageDown preview their absolute targets and cancel without playback',
tunerKeyboard.navigation.every((sample) => sample.slot === sample.expectedSlot
&& sample.restoredSlot === tunerKeyboard.expectedIndex
&& sample.playDelta === 0 && sample.cancelPlayDelta === 0),
JSON.stringify(tunerKeyboard.navigation),
);
await page.select('#radio-filter', 'news');
const filteredTunerCenter = await page.evaluate(() => {
const gev = window.__godsEyeView;
const style = gev.styleManager;
const radio = gev.dataManager.layers.get('radio').module;
const slider = document.getElementById('radio-tuner-slider');
const rect = slider.getBoundingClientRect();
const count = style._radioTunerStations.length;
const centerIndex = Math.floor((count - 1) / 2 + 0.5);
const clientX = rect.left + rect.width / 2;
slider.dispatchEvent(new PointerEvent('pointerdown', {
bubbles: true,
pointerId: 88,
pointerType: 'mouse',
clientX,
clientY: rect.top + rect.height / 2,
buttons: 1,
}));
const preview = {
filter: radio.getUIState().filter,
filteredCount: radio.getUIState().filteredCount,
count,
centerIndex,
slot: Number(slider.value),
ratio: Number(document.getElementById('radio-tuner').style.getPropertyValue('--radio-tuner-ratio')),
markerId: gev.viewer.entities.values
.find((entity) => String(entity.id).startsWith('radio:selected:'))?.id || null,
expectedId: style._radioTunerStations[centerIndex]?.id || null,
};
slider.dispatchEvent(new PointerEvent('pointercancel', {
bubbles: true,
pointerId: 88,
pointerType: 'mouse',
clientX,
clientY: rect.top + rect.height / 2,
}));
return preview;
});
check(
'filtered-directory center means half of the currently available station total',
filteredTunerCenter.filter === 'news'
&& filteredTunerCenter.count === filteredTunerCenter.filteredCount
&& filteredTunerCenter.count > 1 && filteredTunerCenter.count < 750
&& filteredTunerCenter.slot === filteredTunerCenter.centerIndex
&& Math.abs(filteredTunerCenter.ratio - 0.5) < 0.0001
&& filteredTunerCenter.markerId === `radio:selected:${filteredTunerCenter.expectedId}`,
JSON.stringify(filteredTunerCenter),
);
await page.select('#radio-filter', 'all');
const fullPoolNavigation = await page.evaluate(() => {
const gev = window.__godsEyeView;
const radio = gev.dataManager.layers.get('radio').module;
const pool = gev.styleManager._radioTunerPool;
const ids = pool.map((station) => station.id);
const selectWithoutPlayback = (index) => radio.selectStation(ids[index], {
autoplay: false,
focus: false,
origin: 'programmatic',
});
selectWithoutPlayback(40);
radio.cycleStation(1, { stationIds: ids, autoplay: false });
const afterForty = radio.getUIState().selected?.id || null;
selectWithoutPlayback(0);
radio.cycleStation(-1, { stationIds: ids, autoplay: false });
const afterPreviousWrap = radio.getUIState().selected?.id || null;
selectWithoutPlayback(ids.length - 1);
radio.cycleStation(1, { stationIds: ids, autoplay: false });
const afterNextWrap = radio.getUIState().selected?.id || null;
return {
poolLength: ids.length,
afterForty,
expectedAfterForty: ids[41],
afterPreviousWrap,
expectedPreviousWrap: ids.at(-1),
afterNextWrap,
expectedNextWrap: ids[0],
};
});
check(
'Previous and Next traverse beyond channel 40 and wrap across the complete tuner pool',
fullPoolNavigation.poolLength > 41
&& fullPoolNavigation.afterForty === fullPoolNavigation.expectedAfterForty
&& fullPoolNavigation.afterPreviousWrap === fullPoolNavigation.expectedPreviousWrap
&& fullPoolNavigation.afterNextWrap === fullPoolNavigation.expectedNextWrap,
JSON.stringify(fullPoolNavigation),
);
await page.evaluate(() => {
const viewer = window.__godsEyeView.viewer;
viewer.camera.setView({
destination: viewer.scene.globe.ellipsoid.cartographicToCartesian({
longitude: Math.PI,
latitude: -2 * Math.PI / 180,
height: 2_000_000,
}),
orientation: { heading: 0, pitch: -Math.PI / 2, roll: 0 },
});
});
await sleep(500);
const failedTuner = await page.evaluate(async () => {
const gev = window.__godsEyeView;
const slider = document.getElementById('radio-tuner-slider');
const rect = slider.getBoundingClientRect();
const count = gev.styleManager._radioTunerStations.length;
const current = Number(slider.value);
const target = Math.min(count - 1, current + 4);
const xFor = (index) => rect.left + 7 + (rect.width - 14) * index / Math.max(1, count - 1);
const pointer = (type, index) => slider.dispatchEvent(new PointerEvent(type, {
bubbles: true,
pointerId: 2,
pointerType: 'mouse',
clientX: xFor(index),
clientY: rect.top + rect.height / 2,
buttons: type === 'pointerup' ? 0 : 1,
}));
window.__qaRadioFailNextPlay = true;
pointer('pointerdown', current);
pointer('pointermove', target);
pointer('pointerup', target);
await new Promise((resolve) => setTimeout(resolve));
const failed = gev.dataManager.layers.get('radio').module.getUIState();
gev.dataManager.layers.get('radio').module.stopPlayback();
const stopped = gev.dataManager.layers.get('radio').module.getUIState();
return {
failed: {
audioState: failed.audioState,
tuningStatic: failed.tuningStatic,
awaiting: failed.tuningAwaitingStationId,
broadcasting: document.getElementById('title-bar').classList.contains('radio-broadcasting'),
},
stopped: {
tuningStatic: stopped.tuningStatic,
awaiting: stopped.tuningAwaitingStationId,
broadcasting: document.getElementById('title-bar').classList.contains('radio-broadcasting'),
},
};
});
check(
'failed tuner stations retain the audible static hint until Stop',
failedTuner.failed.audioState === 'error' && failedTuner.failed.tuningStatic && failedTuner.failed.awaiting
&& !failedTuner.failed.broadcasting && !failedTuner.stopped.tuningStatic
&& !failedTuner.stopped.awaiting && !failedTuner.stopped.broadcasting,
JSON.stringify(failedTuner),
);
await page.select('#radio-filter', 'news');
const markerTarget = await page.evaluate(async () => {
const viewer = window.__godsEyeView.viewer;
const radio = window.__godsEyeView.dataManager.layers.get('radio').module;
const source = Array.from({ length: viewer.dataSources.length }, (_, index) => viewer.dataSources.get(index))
.find((item) => item.name === 'Radio stations');
source.clustering.enabled = false;
const selectedId = radio.getUIState().selected?.id || null;
if (selectedId) {
radio.selectStation(selectedId, {
autoplay: false,
focus: false,
origin: 'programmatic',
});
}
viewer.camera.cancelFlight();
const markerView = {
destination: viewer.scene.globe.ellipsoid.cartographicToCartesian({
longitude: -174 * Math.PI / 180,
latitude: 6 * Math.PI / 180,
height: 500_000,
}),
orientation: { heading: 0, pitch: -Math.PI / 2, roll: 0 },
};
viewer.camera.setView(markerView);
await new Promise((resolve) => {
let removePostRender = null;
removePostRender = viewer.scene.postRender.addEventListener(() => {
removePostRender?.();
resolve();
});
viewer.scene.requestRender();
});
// Cesium clears its private cluster collections during the rendered
// disabled-state update. Publish the same-filter singleton overlay only
// after that frame so this harness mutation cannot leak stale clusters.
radio.setFilter('news');
viewer.scene.requestRender();
await new Promise((resolve) => setTimeout(resolve, 500));
const { getOverlayPaintRect, getWorldOverlayDiagnostics } = await import('/src/overlays/worldOverlay.js');
return new Promise((resolve) => {
let removePostRender = null;
let timeout = null;
let lastObservation = null;
const finish = (result) => {
clearTimeout(timeout);
removePostRender?.();
resolve(result);
};
const probe = () => {
const camera = viewer.camera.positionCartographic;
const longitude = camera.longitude * 180 / Math.PI;
const latitude = camera.latitude * 180 / Math.PI;
if (Math.abs(longitude + 174) > 0.5 || Math.abs(latitude - 6) > 0.5) {
viewer.camera.cancelFlight();
viewer.camera.setView(markerView);
viewer.scene.requestRender();
return;
}
const diagnostics = radio.getOverlayDiagnostics();
const singletonIds = new Set(diagnostics.singletonIds);
let visibleEntityCount = 0;
let singletonEntityCount = 0;
let paintedSingletonCount = 0;
for (const entity of source.entities.values) {
if (!entity.show) continue;
visibleEntityCount += 1;
const id = String(entity.id).slice('radio:'.length);
const overlayId = `station:${id}`;
if (!singletonIds.has(overlayId)) continue;
singletonEntityCount += 1;
const position = entity.position?.getValue(viewer.clock.currentTime);
const canvas = position && viewer.scene.cartesianToCanvasCoordinates(position);
const paintRect = getOverlayPaintRect('radio', overlayId);
if (paintRect) paintedSingletonCount += 1;
if (paintRect && canvas?.x > 300 && canvas.x < 1100 && canvas.y > 100 && canvas.y < 800) {
finish({
found: true,
id,
x: canvas.x,
y: canvas.y,
ownsNativeLabel: Boolean(entity.label),
labelPainted: true,
singletonTexts: diagnostics.singletonTexts,
});
return;
}
}
lastObservation = {
found: false,
filter: radio.getUIState().filter,
visibleEntityCount,
singletonEntityCount,
paintedSingletonCount,
singletonIds: diagnostics.singletonIds,
singletonTexts: diagnostics.singletonTexts,
clusterIds: diagnostics.clusterIds,
overlayEntryCount: diagnostics.entryCount,
worldOverlay: getWorldOverlayDiagnostics(),
camera: {
longitude: viewer.camera.positionCartographic.longitude * 180 / Math.PI,
latitude: viewer.camera.positionCartographic.latitude * 180 / Math.PI,
height: viewer.camera.positionCartographic.height,
},
};
viewer.scene.requestRender();
};
timeout = setTimeout(() => finish(lastObservation || { found: false, reason: 'no post-render probe' }), 8000);
removePostRender = viewer.scene.postRender.addEventListener(probe);
viewer.scene.requestRender();
});
});
check('Radio exposes a visible marker for click-tolerance QA', markerTarget?.found === true, JSON.stringify(markerTarget));
check(
'the directly pickable singleton point has painted shared-host text and no native label',
markerTarget?.labelPainted && !markerTarget?.ownsNativeLabel,
JSON.stringify(markerTarget),
);
check(
'ambient Radio globe labels use compact frequency-first text',
markerTarget?.singletonTexts?.includes('100.3 FM — The River')
&& markerTarget.singletonTexts.every((text) => text.length <= 30),
JSON.stringify(markerTarget?.singletonTexts),
);
if (markerTarget?.found) {
await page.mouse.click(markerTarget.x + 6, markerTarget.y);
await page.waitForFunction(
(id) => window.__godsEyeView.dataManager.layers.get('radio').module.getUIState().selected?.id === id,
{},
markerTarget.id,
);
const offsetSelected = await page.evaluate(() => window.__godsEyeView.dataManager.layers.get('radio').module.getUIState().selected?.id);
check('an offset click within 8px reliably selects the intended Radio dot', offsetSelected === markerTarget.id, offsetSelected);
}
await page.evaluate(() => {
const gev = window.__godsEyeView;
const camera = gev.viewer.camera;
const radio = gev.dataManager.layers.get('radio').module;
camera.cancelFlight();
gev.viewer.canvas.dispatchEvent(new WheelEvent('wheel', { bubbles: true, deltaY: 1 }));
camera.setView({
destination: gev.viewer.scene.globe.ellipsoid.cartographicToCartesian({
longitude: Math.PI,
latitude: -2 * Math.PI / 180,
height: 2_000_000,
}),
orientation: { heading: 0, pitch: -Math.PI / 2, roll: 0 },
});
radio.stopPlayback();
Array.from({ length: gev.viewer.dataSources.length }, (_, index) => gev.viewer.dataSources.get(index))
.find((item) => item.name === 'Radio stations').clustering.enabled = true;
radio.selectStation('00000000-0000-4000-8000-000000000001', { autoplay: false, focus: false });
const pool = gev.styleManager._radioTunerPool;
const selectedIndex = pool.findIndex((station) => station.id === radio.getUIState().selected?.id);
window.__qaRadioExpectedPrimary = pool[(selectedIndex + 1) % pool.length];
window.__qaRadioExpectedFallback = pool[(selectedIndex + 2) % pool.length];
window.__qaRadioPlayCalls = [];
window.__qaRadioFailedNearest = false;
window.__qaRadioDelayNextFailure = true;
window.__qaRadioFlyToCalls = [];
window.__qaRadioOriginalFlyTo = camera.flyTo;
window.__qaRadioCameraHeight = camera.positionCartographic.height;
camera.flyTo = (options) => window.__qaRadioFlyToCalls.push(options);
gev.dataManager.layers.get('flights').enabled = true;
});
await sleep(500);
await page.evaluate(() => window.__godsEyeView.styleManager.setPanelCollapsed('global-context-panel', true));
await page.evaluate(() => {
const button = document.getElementById('context-radio-toggle-btn');
if (button.getAttribute('aria-expanded') !== 'true') button.click();
});
await page.waitForSelector('#context-radio-mini', { visible: true });
await page.evaluate(() => {
window.__qaRadioCameraHeight = window.__godsEyeView.viewer.camera.positionCartographic.height;
});
const radioOnlyNextDispatched = await page.evaluate(() => {
const button = document.getElementById('context-radio-mini-next-btn');
if (!button) return false;
button.click();
return true;
});
if (!radioOnlyNextDispatched) throw new Error('Radio compact next button unavailable');
await page.waitForFunction(() => typeof window.__qaRejectRadioPlay === 'function');
await page.evaluate(() => {
const viewer = window.__godsEyeView.viewer;
viewer.camera.setView({
destination: viewer.scene.globe.ellipsoid.cartographicToCartesian({
longitude: Math.PI,
latitude: -2 * Math.PI / 180,
height: 2_400_000,
}),
orientation: { heading: 0, pitch: -Math.PI / 2, roll: 0 },
});
window.__qaRejectRadioPlay();
});
await page.waitForFunction(() => window.__godsEyeView.dataManager.layers.get('radio').module.getUIState().audioState === 'playing');
const playing = await page.evaluate(async () => {
const radio = window.__godsEyeView.dataManager.layers.get('radio').module;
const viewer = window.__godsEyeView.viewer;
const selectedEntity = window.__godsEyeView.viewer.entities.values
.find((entity) => String(entity.id).startsWith('radio:selected:'));
const { getWorldOverlayDiagnostics } = await import('/src/overlays/worldOverlay.js');
const flightTargets = window.__qaRadioFlyToCalls.map(({ destination }) => {
const position = viewer.scene.globe.ellipsoid.cartesianToCartographic(destination);
return {
lat: position.latitude * 180 / Math.PI,
lon: position.longitude * 180 / Math.PI,
height: position.height,
};
});
return {
state: radio.getUIState(),
calls: [...window.__qaRadioPlayCalls],
flyToCalls: window.__qaRadioFlyToCalls.length,
flightTargets,
cameraHeight: window.__qaRadioCameraHeight,
compactPlayLabel: document.getElementById('context-radio-mini-play-btn').getAttribute('aria-label'),
regularMarkerSize: Array.from({ length: window.__godsEyeView.viewer.dataSources.length }, (_, index) => window.__godsEyeView.viewer.dataSources.get(index))
.find((item) => item.name === 'Radio stations')?.entities.values[0]?.point?.pixelSize?.getValue(),
selectedMarkerSize: selectedEntity?.point?.pixelSize?.getValue(),
selectedBracketWidth: selectedEntity?.billboard?.width?.getValue(),
selectedOwnsNativeLabel: Boolean(selectedEntity?.label),
overlay: radio.getOverlayDiagnostics(),
host: getWorldOverlayDiagnostics(),
expectedPrimary: window.__qaRadioExpectedPrimary,
expectedFallback: window.__qaRadioExpectedFallback,
};
});
check(
'compact Next follows stable directory order and retries its next available station',
playing.state.selected?.id === playing.expectedFallback?.id
&& playing.calls[0] === playing.expectedPrimary?.streamUrl
&& playing.calls[1] === playing.expectedFallback?.streamUrl,
JSON.stringify({
selected: playing.state.selected?.id,
calls: playing.calls,
primary: playing.expectedPrimary?.id,
fallback: playing.expectedFallback?.id,
}),
);
check('compact next is explicit playback and updates play/pause state', playing.calls.length === 2 && playing.compactPlayLabel.startsWith('Pause'));
check(
'compact next rotates toward its station without changing zoom, including fallback',
playing.flyToCalls === 2
&& Math.abs(playing.flightTargets[0]?.lat - playing.expectedPrimary?.lat) < 0.001
&& Math.abs(playing.flightTargets[0]?.lon - playing.expectedPrimary?.lon) < 0.001
&& Math.abs(playing.flightTargets[1]?.lat - playing.expectedFallback?.lat) < 0.001
&& Math.abs(playing.flightTargets[1]?.lon - playing.expectedFallback?.lon) < 0.001
&& playing.flightTargets.every((target) => Math.abs(target.height - playing.cameraHeight) < 1),
JSON.stringify({ calls: playing.flyToCalls, targets: playing.flightTargets, cameraHeight: playing.cameraHeight }),
);
const trackedRadioControls = await page.evaluate(async () => {
const viewer = window.__godsEyeView.viewer;
const radio = window.__godsEyeView.dataManager.layers.get('radio').module;
const run = async (ownerPrefix, buttonId) => {
const owner = viewer.entities.add({
id: `qa:${ownerPrefix}:radio-camera-owner`,
position: viewer.camera.positionWC.clone(),
});
owner.gevTrackedId = `${ownerPrefix}:qa-radio-camera-owner`;
viewer.trackedEntity = owner;
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
const beforeStationId = radio.getUIState().selected?.id || null;
const beforeFlyToCalls = window.__qaRadioFlyToCalls.length;
document.getElementById(buttonId)?.click();
await new Promise((resolve) => setTimeout(resolve));
const result = {
ownerPrefix,
buttonId,
beforeStationId,
afterStationId: radio.getUIState().selected?.id || null,
flyToDelta: window.__qaRadioFlyToCalls.length - beforeFlyToCalls,
trackedIdentityPreserved: viewer.trackedEntity === owner,
trackedId: viewer.trackedEntity?.gevTrackedId || null,
};
viewer.trackedEntity = undefined;
viewer.entities.remove(owner);
return result;
};
return [
await run('flights', 'radio-next-btn'),
await run('military', 'context-radio-mini-prev-btn'),
];
});
check(
'full and compact Radio navigation preserve Flights/Military camera ownership',
trackedRadioControls.every((sample) => sample.afterStationId
&& sample.afterStationId !== sample.beforeStationId
&& sample.flyToDelta === 0
&& sample.trackedIdentityPreserved
&& sample.trackedId === `${sample.ownerPrefix}:qa-radio-camera-owner`),
JSON.stringify(trackedRadioControls),
);
const delayedTrackingStart = await page.evaluate(() => {
const viewer = window.__godsEyeView.viewer;
viewer.trackedEntity = undefined;
window.__qaRadioDelayNextFailure = true;
const beforeFlyToCalls = window.__qaRadioFlyToCalls.length;
document.getElementById('context-radio-mini-next-btn')?.click();
return { beforeFlyToCalls };
});
await page.waitForFunction(() => typeof window.__qaRejectRadioPlay === 'function');
const delayedTrackingOwner = await page.evaluate(() => {
const viewer = window.__godsEyeView.viewer;
const owner = viewer.entities.add({
id: 'qa:flights:delayed-radio-fallback-owner',
position: viewer.camera.positionWC.clone(),
});
owner.gevTrackedId = 'flights:qa-delayed-radio-fallback-owner';
viewer.trackedEntity = owner;
window.__qaDelayedRadioTrackingOwner = owner;
window.__qaRejectRadioPlay();
return owner.gevTrackedId;
});
await page.waitForFunction(() => window.__godsEyeView.dataManager.layers.get('radio').module.getUIState().audioState === 'playing');
const delayedFallbackOwnership = await page.evaluate((beforeFlyToCalls) => {
const viewer = window.__godsEyeView.viewer;
const owner = window.__qaDelayedRadioTrackingOwner;
const result = {
flyToDelta: window.__qaRadioFlyToCalls.length - beforeFlyToCalls,
trackedIdentityPreserved: viewer.trackedEntity === owner,
trackedId: viewer.trackedEntity?.gevTrackedId || null,
selectedId: window.__godsEyeView.dataManager.layers.get('radio').module.getUIState().selected?.id || null,
};
viewer.trackedEntity = undefined;
viewer.entities.remove(owner);
delete window.__qaDelayedRadioTrackingOwner;
return result;
}, delayedTrackingStart.beforeFlyToCalls);
check(
'a delayed Radio fallback yields when Flights acquires the camera after the initial rotation',
delayedFallbackOwnership.flyToDelta === 1
&& delayedFallbackOwnership.trackedIdentityPreserved
&& delayedFallbackOwnership.trackedId === delayedTrackingOwner
&& Boolean(delayedFallbackOwnership.selectedId),
JSON.stringify(delayedFallbackOwnership),
);
await page.evaluate(() => {
const viewer = window.__godsEyeView.viewer;
viewer.camera.setView({
destination: viewer.scene.globe.ellipsoid.cartographicToCartesian({
longitude: Math.PI,
latitude: -2 * Math.PI / 180,
height: window.__qaRadioCameraHeight,
}),
orientation: { heading: 0, pitch: -Math.PI / 2, roll: 0 },
});
});
check(
'individual Radio dots stay at least half the largest cluster point',
playing.regularMarkerSize === 13
&& playing.regularMarkerSize * 2 >= 26
&& playing.selectedMarkerSize === 14,
`${playing.regularMarkerSize}px / ${playing.selectedMarkerSize}px selected`,
);
check('the current station carries a 40px four-corner selection bracket', playing.selectedBracketWidth === 40, `${playing.selectedBracketWidth}px`);
check(
'selected-station text is published to the protected shared-host lane with no native entity label',
!playing.selectedOwnsNativeLabel
&& playing.overlay.selectedCount === 1
&& playing.host.entriesBySource?.radio === playing.overlay.entryCount,
JSON.stringify({ overlay: playing.overlay, host: playing.host.entriesBySource }),
);
const closeSelectedText = await page.evaluate(async () => {
const viewer = window.__godsEyeView.viewer;
const radio = window.__godsEyeView.dataManager.layers.get('radio').module;
const selected = radio.getUIState().selected;
window.__qaRadioVisualView = {
position: viewer.camera.positionWC.clone(),
direction: viewer.camera.directionWC.clone(),
up: viewer.camera.upWC.clone(),
};
const destination = viewer.scene.globe.ellipsoid.cartographicToCartesian({
longitude: selected.lon * Math.PI / 180,
latitude: selected.lat * Math.PI / 180,
height: 5_000,
});
viewer.camera.setView({
destination,
orientation: { heading: 0, pitch: -Math.PI / 2, roll: 0 },
});
const selectedEntity = viewer.entities.values
.find((entity) => String(entity.id) === `radio:selected:${selected.id}`);
const { getOverlayPaintRect, getWorldOverlayDiagnostics } = await import('/src/overlays/worldOverlay.js');
const expectedEntryId = `selected:${selected.id}`;
let painted = null;
for (let attempt = 0; attempt < 20 && !painted?.rect; attempt += 1) {
painted = await new Promise((resolve) => {
let removePostRender = null;
const timeout = setTimeout(() => {
removePostRender?.();
resolve({ rect: null, host: getWorldOverlayDiagnostics() });
}, 150);
removePostRender = viewer.scene.postRender.addEventListener(() => {
clearTimeout(timeout);
removePostRender();
const rect = getOverlayPaintRect('radio', expectedEntryId);
resolve({
rect: rect ? { x: rect.x, y: rect.y, w: rect.w, h: rect.h } : null,
host: getWorldOverlayDiagnostics(),
});
});
viewer.scene.requestRender();
});
}
return {
expectedEntryId,
rect: painted?.rect || null,
host: painted?.host || getWorldOverlayDiagnostics(),
selectedEntityShow: selectedEntity?.show,
cameraHeight: viewer.camera.positionCartographic.height,
};
});
check(
'selected shared-host text remains in the paint pipeline in a close stationary view',
closeSelectedText?.rect?.w > 0
&& closeSelectedText?.rect?.h > 0
&& closeSelectedText?.selectedEntityShow === true,
JSON.stringify(closeSelectedText),
);
await page.screenshot({ path: path.join(SHOTS_DIR, 'selected-label-desktop.png') });
const highGlobalSelectedText = await page.evaluate(async () => {
const viewer = window.__godsEyeView.viewer;
const radio = window.__godsEyeView.dataManager.layers.get('radio').module;
const selected = radio.getUIState().selected;
const ellipsoid = viewer.scene.globe.ellipsoid;
viewer.camera.setView({
destination: ellipsoid.cartographicToCartesian({
longitude: (selected.lon + 10) * Math.PI / 180,
latitude: selected.lat * Math.PI / 180,
height: 5_000_000,
}),
orientation: { heading: 0, pitch: -Math.PI / 2, roll: 0 },
});
const selectedEntity = viewer.entities.values
.find((entity) => String(entity.id) === `radio:selected:${selected.id}`);
const { getOverlayPaintRect, getWorldOverlayDiagnostics } = await import('/src/overlays/worldOverlay.js');
const expectedEntryId = `selected:${selected.id}`;
let rect = null;
for (let attempt = 0; attempt < 20 && !rect; attempt += 1) {
rect = await new Promise((resolve) => {
let removePostRender = null;
const timeout = setTimeout(() => {
removePostRender?.();
resolve(null);
}, 150);
removePostRender = viewer.scene.postRender.addEventListener(() => {
clearTimeout(timeout);
removePostRender();
const painted = getOverlayPaintRect('radio', expectedEntryId);
resolve(painted ? {
x: painted.x,
y: painted.y,
w: painted.w,
h: painted.h,
} : null);
});
viewer.scene.requestRender();
});
}
const entityPosition = selectedEntity?.position?.getValue?.();
const entityAnchor = entityPosition
&& viewer.scene.cartesianToCanvasCoordinates(entityPosition);
let selectedNativePick = null;
let selectedPointPick = null;
let selectedBillboardPick = null;
// Retry on the STRICT anchor: waiting only for "any pick" let a
// billboard satisfy the loop while the point anchor was still missing.
for (let attempt = 0; entityAnchor && attempt < 8 && !selectedPointPick; attempt += 1) {
const picks = viewer.scene.drillPick(entityAnchor, 16) || [];
selectedNativePick = picks.find((picked) => picked?.id === selectedEntity);
selectedPointPick = picks.find((picked) => (
picked?.id === selectedEntity
&& String(picked?.primitive?.constructor?.name || '').includes('PointPrimitive')
));
selectedBillboardPick = picks.find((picked) => (
picked?.id === selectedEntity
&& String(picked?.primitive?.constructor?.name || '').includes('Billboard')
));
if (!selectedPointPick) {
await new Promise((resolve) => {
const removePostRender = viewer.scene.postRender.addEventListener(() => {
removePostRender();
resolve();
});
viewer.scene.requestRender();
});
}
}
const selectedPointDistance = selectedEntity?.point?.distanceDisplayCondition?.getValue?.();
return {
rect,
selectedNativePickable: Boolean(selectedNativePick),
selectedPointPickable: Boolean(selectedPointPick),
selectedBillboardPickable: Boolean(selectedBillboardPick),
selectedPointMaxDistance: selectedPointDistance?.far ?? null,
selectedEntityShow: selectedEntity?.show,
selectedBracketVisible: Boolean(
selectedEntity?.billboard
&& selectedEntity.billboard.show?.getValue?.() !== false
),
entityAnchor: entityAnchor ? { x: entityAnchor.x, y: entityAnchor.y } : null,
entityDistance: entityPosition
? Math.hypot(
viewer.camera.positionWC.x - entityPosition.x,
viewer.camera.positionWC.y - entityPosition.y,
viewer.camera.positionWC.z - entityPosition.z,
)
: null,
overlay: radio.getOverlayDiagnostics(),
host: getWorldOverlayDiagnostics(),
cameraHeight: viewer.camera.positionCartographic.height,
};
});
check(
'selected label, bracket, and native pick anchor share the supported high-global range',
highGlobalSelectedText.cameraHeight > 4_800_000
&& highGlobalSelectedText.rect?.w > 0
&& highGlobalSelectedText.rect?.h > 0
&& highGlobalSelectedText.selectedEntityShow === true
&& highGlobalSelectedText.selectedBracketVisible
&& highGlobalSelectedText.selectedPointMaxDistance === 50_000_000
// The POINT anchor is the invariant: it is what stays pickable out to
// the 50,000 km band. Accepting any native pick let the co-located
// billboard keep this green after the point anchor regressed.
//
// `selectedBillboardPickable` is reported for diagnostics but NOT
// asserted: measured here it is false — the selection bracket renders
// (selectedBracketVisible) without being pickable at the point anchor,
// so requiring it would pin a behavior the app does not have.
&& highGlobalSelectedText.selectedPointPickable
&& highGlobalSelectedText.selectedNativePickable,
JSON.stringify(highGlobalSelectedText),
);
await page.screenshot({ path: path.join(SHOTS_DIR, 'selected-label-high-global.png') });
await page.setViewport({ width: 560, height: 760, deviceScaleFactor: 1 });
await page.evaluate(async () => {
const viewer = window.__godsEyeView.viewer;
window.__godsEyeView.styleManager.toggleCleanView(true);
viewer.resize();
viewer.scene.requestRender();
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
});
await page.screenshot({ path: path.join(SHOTS_DIR, 'selected-label-mobile.png') });
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
await page.evaluate(() => {
const viewer = window.__godsEyeView.viewer;
window.__godsEyeView.styleManager.toggleCleanView(false);
viewer.resize();
viewer.scene.requestRender();
});
await page.evaluate(() => {
const viewer = window.__godsEyeView.viewer;
const view = window.__qaRadioVisualView;
viewer.camera.setView({
destination: view.position,
orientation: { direction: view.direction, up: view.up },
});
delete window.__qaRadioVisualView;
viewer.scene.requestRender();
});
await new Promise((resolve) => setTimeout(resolve, 300));
await page.evaluate(() => {
window.__qaRadioFlyToCalls = [];
window.__godsEyeView.dataManager.layers.get('flights').enabled = false;
});
const radioOnlyNextClicked = await page.evaluate(() => {
const button = document.getElementById('context-radio-mini-next-btn');
if (!(button instanceof HTMLButtonElement)) return false;
button.click();
return true;
});
check(
'Radio-only next control remains available after responsive visual checks',
radioOnlyNextClicked,
`clicked=${radioOnlyNextClicked}`,
);
const radioOnlyFocusCalls = await page.evaluate(() => {
const calls = window.__qaRadioFlyToCalls.length;
const heights = window.__qaRadioFlyToCalls.map((options) => (
window.__godsEyeView.viewer.scene.globe.ellipsoid.cartesianToCartographic(options.destination).height
));
window.__godsEyeView.viewer.camera.flyTo = window.__qaRadioOriginalFlyTo;
delete window.__qaRadioOriginalFlyTo;
return { calls, heights, cameraHeight: window.__qaRadioCameraHeight };
});
check(
'Radio-only next advances the tuner and rotates without changing zoom',
radioOnlyFocusCalls.calls === 1
&& radioOnlyFocusCalls.heights.every((height) => Math.abs(height - radioOnlyFocusCalls.cameraHeight) < 1),
JSON.stringify(radioOnlyFocusCalls),
);
const stagedGlobeNavigation = await page.evaluate(async () => {
const gev = window.__godsEyeView;
const viewer = gev.viewer;
const camera = viewer.camera;
const radio = gev.dataManager.layers.get('radio').module;
const stationIds = radio.getTunerStations(3).map((station) => station.id);
if (stationIds.length < 3) throw new Error('Radio recenter QA requires three visible stations');
const originalFlyTo = camera.flyTo;
const calls = [];
camera.flyTo = (options) => calls.push(options);
const setCamera = (height, pitchDegrees, headingDegrees = 38) => {
camera.setView({
destination: viewer.scene.globe.ellipsoid.cartographicToCartesian({
longitude: -133.9483 * Math.PI / 180,
latitude: 23.4741 * Math.PI / 180,
height,
}),
orientation: {
heading: headingDegrees * Math.PI / 180,
pitch: pitchDegrees * Math.PI / 180,
roll: 0,
},
});
viewer.scene.requestRender();
};
const completeFlight = (index) => {
const flight = calls[index];
camera.setView({
destination: flight.destination,
orientation: flight.orientation,
});
flight.complete?.();
};
const describe = (flight) => {
const cartographic = viewer.scene.globe.ellipsoid.cartesianToCartographic(flight.destination);
return {
lat: cartographic.latitude * 180 / Math.PI,
lon: cartographic.longitude * 180 / Math.PI,
height: cartographic.height,
heading: flight.orientation?.heading,
pitch: flight.orientation?.pitch,
roll: flight.orientation?.roll,
duration: flight.duration,
eased: Boolean(flight.easingFunction),
};
};
setCamera(2_915_647, -40);
radio.cycleStation(1, { rotate: true, autoplay: false, stationIds });
const partialInitialCalls = calls.length;
const recenter = describe(calls[0]);
completeFlight(0);
const partialAfterRecenterCalls = calls.length;
const selectedAfterRecenter = radio.getUIState().selected;
const stationFocus = describe(calls[1]);
calls.length = 0;
setCamera(2_915_647, -40);
radio.cycleStation(1, { rotate: true, autoplay: false, stationIds });
const owner = viewer.entities.add({
id: 'qa:flights:radio-recenter-stage-owner',
position: viewer.camera.positionWC.clone(),
});
owner.gevTrackedId = 'flights:qa-radio-recenter-stage-owner';
viewer.trackedEntity = owner;
completeFlight(0);
const trackingBetweenStagesCalls = calls.length;
const trackingIdentityPreserved = viewer.trackedEntity === owner;
viewer.trackedEntity = undefined;
viewer.entities.remove(owner);
calls.length = 0;
setCamera(2_915_647, -40);
radio.selectStation(stationIds[2], { autoplay: false, focus: false });
radio.cycleStation(1, { rotate: true, autoplay: false, stationIds });
const staleRecenter = calls[0];
radio.cycleStation(1, { rotate: true, autoplay: false, stationIds });
const latestRecenterIndex = calls.length - 1;
staleRecenter.complete?.();
const callsAfterStaleCompletion = calls.length;
completeFlight(latestRecenterIndex);
const latestSelected = radio.getUIState().selected;
const latestFocus = describe(calls.at(-1));
calls.length = 0;
setCamera(2_915_647, -40);
radio.cycleStation(1, { rotate: true, autoplay: false, stationIds });
const directSelectionStaleRecenter = calls[0];
const directSelectionId = stationIds.find((id) => id !== radio.getUIState().selected.id);
radio.selectStation(directSelectionId, { autoplay: false, focus: false, origin: 'user' });
const directSelectionCalls = calls.length;
directSelectionStaleRecenter.complete?.();
const directSelectionCallsAfterStale = calls.length;
const directSelectionSelectedId = radio.getUIState().selected.id;
calls.length = 0;
setCamera(2_915_647, -40);
radio.cycleStation(1, { rotate: true, autoplay: false, stationIds });
const voiceSelectionStaleRecenter = calls[0];
const voiceSelectionId = stationIds.find((id) => id !== radio.getUIState().selected.id);
const voiceSelection = radio.selectRequestedStation({
stationQuery: voiceSelectionId,
}, { autoplay: false, origin: 'voice' });
const voiceSelectionCalls = calls.length;
voiceSelectionStaleRecenter.complete?.();
const voiceSelectionCallsAfterStale = calls.length;
const voiceSelectionSelectedId = radio.getUIState().selected.id;
radio.selectStation(latestSelected.id, { autoplay: false, focus: false });
calls.length = 0;
setCamera(97_488_500, -40, 119);
radio.cycleStation(1, { rotate: true, autoplay: false, stationIds });
const extremeRecenter = describe(calls[0]);
completeFlight(0);
const extremeFocus = describe(calls[1]);
calls.length = 0;
setCamera(5_000, -35, 15);
radio.cycleStation(1, { rotate: true, autoplay: false, stationIds });
const localDirect = describe(calls[0]);
const localDirectCalls = calls.length;
calls.length = 0;
setCamera(12_000_000, -90, 15);
radio.cycleStation(1, { rotate: true, autoplay: false, stationIds });
const centeredDirect = describe(calls[0]);
const centeredDirectCalls = calls.length;
camera.flyTo = originalFlyTo;
return {
partialInitialCalls,
partialAfterRecenterCalls,
recenter,
stationFocus,
selectedAfterRecenter,
trackingBetweenStagesCalls,
trackingIdentityPreserved,
latestRecenterIndex,
callsAfterStaleCompletion,
latestSelected,
latestFocus,
directSelectionId,
directSelectionCalls,
directSelectionCallsAfterStale,
directSelectionSelectedId,
voiceSelectionId,
voiceSelectionMatchedId: voiceSelection?.id || null,
voiceSelectionCalls,
voiceSelectionCallsAfterStale,
voiceSelectionSelectedId,
extremeRecenter,
extremeFocus,
localDirectCalls,
localDirect,
centeredDirectCalls,
centeredDirect,
};
});
check(
'a below-center closer globe animates back to center before station focus',
stagedGlobeNavigation.partialInitialCalls === 1
&& stagedGlobeNavigation.partialAfterRecenterCalls === 2
&& Math.abs(stagedGlobeNavigation.recenter.height - 2_915_647) < 1
&& stagedGlobeNavigation.recenter.heading === 0
&& Math.abs(stagedGlobeNavigation.recenter.pitch + Math.PI / 2) < 1e-9
&& stagedGlobeNavigation.recenter.roll === 0
&& stagedGlobeNavigation.recenter.duration >= 0.65
&& stagedGlobeNavigation.recenter.eased
&& Math.abs(stagedGlobeNavigation.stationFocus.height - 2_915_647) < 1
&& Math.abs(stagedGlobeNavigation.stationFocus.lat - stagedGlobeNavigation.selectedAfterRecenter.lat) < 0.001
&& Math.abs(stagedGlobeNavigation.stationFocus.lon - stagedGlobeNavigation.selectedAfterRecenter.lon) < 0.001
&& Math.abs(stagedGlobeNavigation.stationFocus.pitch + Math.PI / 2) < 1e-9,
JSON.stringify(stagedGlobeNavigation),
);
check(
'tracking acquired during Radio recenter suppresses the station-focus stage',
stagedGlobeNavigation.trackingBetweenStagesCalls === 1
&& stagedGlobeNavigation.trackingIdentityPreserved,
JSON.stringify(stagedGlobeNavigation),
);
check(
'a newer Radio action makes the older recenter callback inert and owns the final focus',
stagedGlobeNavigation.latestRecenterIndex === 1
&& stagedGlobeNavigation.callsAfterStaleCompletion === 2
&& Math.abs(stagedGlobeNavigation.latestFocus.lat - stagedGlobeNavigation.latestSelected.lat) < 0.001
&& Math.abs(stagedGlobeNavigation.latestFocus.lon - stagedGlobeNavigation.latestSelected.lon) < 0.001,
JSON.stringify(stagedGlobeNavigation),
);
check(
'a newer direct station selection invalidates an older staged Radio recenter',
stagedGlobeNavigation.directSelectionCalls === 1
&& stagedGlobeNavigation.directSelectionCallsAfterStale === 1
&& stagedGlobeNavigation.directSelectionSelectedId === stagedGlobeNavigation.directSelectionId,
JSON.stringify(stagedGlobeNavigation),
);
check(
'a newer voice station selection remains non-moving after an older recenter completes',
stagedGlobeNavigation.voiceSelectionCalls === 1
&& stagedGlobeNavigation.voiceSelectionCallsAfterStale === 1
&& stagedGlobeNavigation.voiceSelectionMatchedId === stagedGlobeNavigation.voiceSelectionId
&& stagedGlobeNavigation.voiceSelectionSelectedId === stagedGlobeNavigation.voiceSelectionId,
JSON.stringify(stagedGlobeNavigation),
);
check(
'extreme zoom-out recovery caps the north-up full-globe frame',
Math.abs(stagedGlobeNavigation.extremeRecenter.height - 13_000_000) < 1
&& stagedGlobeNavigation.extremeRecenter.heading === 0
&& Math.abs(stagedGlobeNavigation.extremeRecenter.pitch + Math.PI / 2) < 1e-9
&& stagedGlobeNavigation.extremeRecenter.roll === 0
&& Math.abs(stagedGlobeNavigation.extremeFocus.height - 13_000_000) < 1,
JSON.stringify(stagedGlobeNavigation),
);
check(
'local and already-centered globe views keep the single-stage direct path',
stagedGlobeNavigation.localDirectCalls === 1
&& stagedGlobeNavigation.centeredDirectCalls === 1
&& Math.abs(stagedGlobeNavigation.localDirect.pitch + 35 * Math.PI / 180) < 1e-9
&& Math.abs(stagedGlobeNavigation.centeredDirect.pitch + Math.PI / 2) < 1e-9,
JSON.stringify(stagedGlobeNavigation),
);
await page.evaluate(() => {
window.__godsEyeView.styleManager.setPanelCollapsed('radio-panel', true);
window.__godsEyeView.styleManager.setPanelCollapsed('global-context-panel', false);
});
const companion = await page.evaluate(() => ({
radioEnabled: window.__godsEyeView.dataManager.isEnabled('radio'),
state: window.__godsEyeView.dataManager.layers.get('radio').module.getUIState().audioState,
radioCollapsed: document.getElementById('radio-panel').classList.contains('collapsed'),
contextCollapsed: document.getElementById('global-context-panel').classList.contains('collapsed'),
nested: document.getElementById('global-context-panel').contains(document.getElementById('radio-panel')),
}));
check(
'expanding Context preserves playback and the user-selected detailed Radio disclosure',
companion.radioEnabled && companion.state === 'playing'
&& companion.radioCollapsed && !companion.contextCollapsed && companion.nested,
);
const expandedContextRadioBefore = await page.evaluate(() => {
const gev = window.__godsEyeView;
const scroller = document.querySelector('#global-context-panel .global-context-panel-inner');
scroller.scrollTop = 0;
const camera = gev.viewer.camera.positionWC;
return {
scrollTop: scroller.scrollTop,
pageY: window.scrollY,
camera: { x: camera.x, y: camera.y, z: camera.z },
plays: window.__qaRadioPlayCalls.length,
};
});
await page.click('#context-radio-toggle-btn');
await page.waitForFunction(() => !document.getElementById('radio-panel').classList.contains('collapsed'));
await sleep(450);
const expandedContextRadioAfter = await page.evaluate(() => {
const gev = window.__godsEyeView;
gev.styleManager._renderRadioState(gev.dataManager.layers.get('radio').module.getUIState());
const launcher = document.getElementById('context-radio-toggle-btn');
const radio = document.getElementById('radio-panel');
const scroller = document.querySelector('#global-context-panel .global-context-panel-inner');
const camera = gev.viewer.camera.positionWC;
return {
ariaControls: launcher.getAttribute('aria-controls'),
expanded: launcher.getAttribute('aria-expanded'),
label: launcher.getAttribute('aria-label'),
radioCollapsed: radio.classList.contains('collapsed'),
compactHidden: document.getElementById('context-radio-mini').hidden,
scrollTop: scroller.scrollTop,
focusTarget: document.activeElement?.getAttribute('data-collapse-target'),
pageY: window.scrollY,
camera: { x: camera.x, y: camera.y, z: camera.z },
plays: window.__qaRadioPlayCalls.length,
state: gev.dataManager.layers.get('radio').module.getUIState().audioState,
};
});
const expandedContextRadioCameraDelta = Math.hypot(
expandedContextRadioAfter.camera.x - expandedContextRadioBefore.camera.x,
expandedContextRadioAfter.camera.y - expandedContextRadioBefore.camera.y,
expandedContextRadioAfter.camera.z - expandedContextRadioBefore.camera.z,
);
check(
'expanded Context Radio icon reveals and scrolls to embedded Radio without an overlay or playback/camera theft',
expandedContextRadioAfter.ariaControls === 'radio-panel'
&& expandedContextRadioAfter.expanded === 'true'
&& expandedContextRadioAfter.label === 'Go to expanded Radio section'
&& !expandedContextRadioAfter.radioCollapsed
&& expandedContextRadioAfter.compactHidden
&& expandedContextRadioAfter.scrollTop > expandedContextRadioBefore.scrollTop
&& expandedContextRadioAfter.focusTarget === 'radio-panel'
&& expandedContextRadioAfter.pageY === expandedContextRadioBefore.pageY
&& expandedContextRadioCameraDelta < 0.01
&& expandedContextRadioAfter.plays === expandedContextRadioBefore.plays
&& expandedContextRadioAfter.state === 'playing',
JSON.stringify({
before: expandedContextRadioBefore,
after: expandedContextRadioAfter,
cameraDelta: expandedContextRadioCameraDelta,
}),
);
await page.evaluate(() => {
const manager = window.__godsEyeView.styleManager;
manager.setPanelCollapsed('global-context-panel', true);
manager._setRadioDisclosure(true);
});
await page.waitForFunction(() => {
const close = document.getElementById('context-radio-mini-close-btn');
const rect = close.getBoundingClientRect();
return !document.getElementById('context-radio-mini').hidden && rect.width > 0 && rect.height > 0;
});
await page.click('#context-radio-mini-close-btn');
const closedCompact = await page.evaluate(() => {
const launcher = document.getElementById('context-radio-toggle-btn');
return {
radioCollapsed: document.getElementById('radio-panel').classList.contains('collapsed'),
compactHidden: document.getElementById('context-radio-mini').hidden,
focusRestored: document.activeElement === launcher,
};
});
await page.click('#context-radio-toggle-btn');
await page.waitForFunction(() => !document.getElementById('context-radio-mini').hidden);
await page.click('#context-radio-details-btn');
const repeatedOpen = await page.evaluate(() => ({
radioCollapsed: document.getElementById('radio-panel').classList.contains('collapsed'),
compactHidden: document.getElementById('context-radio-mini').hidden,
}));
const splitDetailActions = { closedCompact, repeatedOpen };
check(
'collapsed Context keeps compact close and full-panel open as separate stable actions',
!splitDetailActions.closedCompact.radioCollapsed
&& splitDetailActions.closedCompact.compactHidden
&& splitDetailActions.closedCompact.focusRestored
&& !splitDetailActions.repeatedOpen.radioCollapsed
&& splitDetailActions.repeatedOpen.compactHidden,
JSON.stringify(splitDetailActions),
);
await page.click('#global-context-flights-btn');
await page.waitForFunction(() => !document.getElementById('context-flights-view').hidden);
const companionStack = await page.evaluate(() => {
const contextView = document.getElementById('context-flights-view');
const awareness = document.getElementById('military-awareness-panel');
const radio = document.getElementById('radio-panel');
const scroller = document.querySelector('#global-context-panel .global-context-panel-inner');
const awarenessRect = awareness.getBoundingClientRect();
const radioRect = radio.getBoundingClientRect();
return {
contactsNaturalHeight: contextView.clientHeight === contextView.scrollHeight,
radioNaturalHeight: radio.clientHeight === radio.scrollHeight,
separated: awarenessRect.bottom <= radioRect.top,
scrollable: scroller.scrollHeight > scroller.clientHeight,
};
});
check(
'expanded Contacts and Radio remain separated in the shared Context scroller',
companionStack.contactsNaturalHeight && companionStack.radioNaturalHeight
&& companionStack.separated && companionStack.scrollable,
JSON.stringify(companionStack),
);
await page.select('#radio-filter', 'talk');
const filtered = await page.evaluate(() => window.__godsEyeView.dataManager.layers.get('radio').module.getUIState());
check('filter changes markers/navigation without stopping the selected stream', filtered.filteredCount === 75 && filtered.audioState === 'playing' && filtered.selectedIndex === -1, `${filtered.filteredCount} matches`);
const playsBeforeRestore = await page.evaluate(() => window.__qaRadioPlayCalls.length);
await page.evaluate(async () => {
const gev = window.__godsEyeView;
await gev.dataManager.setEnabled('radio', false);
await gev.dataManager.setEnabled('radio', true);
});
const restored = await page.evaluate(() => ({
state: window.__godsEyeView.dataManager.layers.get('radio').module.getUIState(),
plays: window.__qaRadioPlayCalls.length,
radioCollapsed: document.getElementById('radio-panel').classList.contains('collapsed'),
}));
check(
'programmatic/preset-style restoration does not autoplay and leaves idle Radio collapsed',
restored.state.enabled && restored.state.audioState === 'stopped'
&& restored.plays === playsBeforeRestore && restored.radioCollapsed,
JSON.stringify(restored),
);
await page.evaluate(() => window.__godsEyeView.styleManager.setPanelCollapsed('radio-panel', false));
await sleep(350);
await page.screenshot({ path: path.join(SHOTS_DIR, 'desktop.png') });
const desktop = await page.$eval('#radio-panel', (element) => {
const rect = element.getBoundingClientRect();
const contextRect = element.closest('#global-context-panel').getBoundingClientRect();
const tunerRect = element.querySelector('.radio-tuner').getBoundingClientRect();
const innerRect = element.querySelector('.radio-panel-inner').getBoundingClientRect();
return {
left: contextRect.left,
right: contextRect.right,
top: contextRect.top,
bottom: contextRect.bottom,
width: contextRect.width,
radioInside: rect.left >= contextRect.left && rect.right <= contextRect.right,
tunerInside: tunerRect.left >= innerRect.left && tunerRect.right <= innerRect.right,
};
});
check('desktop Context host keeps Radio and its tuner contained', desktop.left >= 0 && desktop.right <= 1440 && desktop.top >= 0 && desktop.bottom <= 900 && desktop.radioInside && desktop.tunerInside, JSON.stringify(desktop));
await page.click('#radio-play-btn');
await page.waitForFunction(() => window.__godsEyeView.dataManager.layers.get('radio').module.getUIState().audioState === 'playing');
await page.setViewport({ width: 390, height: 844, deviceScaleFactor: 1 });
await sleep(500);
await page.$eval('#radio-panel', (element) => element.scrollIntoView({ block: 'nearest' }));
await page.screenshot({ path: path.join(SHOTS_DIR, 'mobile.png') });
const mobile = await page.$eval('#radio-panel', (element) => {
const rect = element.getBoundingClientRect();
const context = element.closest('#global-context-panel');
const contextRect = context.getBoundingClientRect();
const scroller = context.querySelector('.global-context-panel-inner');
const logo = document.querySelector('#title-bar .title-logo');
const logoRect = logo.getBoundingClientRect();
const tunerRect = element.querySelector('.radio-tuner').getBoundingClientRect();
const innerRect = element.querySelector('.radio-panel-inner').getBoundingClientRect();
return {
left: contextRect.left,
right: contextRect.right,
top: contextRect.top,
bottom: contextRect.bottom,
width: contextRect.width,
radioWidth: rect.width,
radioVisible: rect.bottom > contextRect.top && rect.top < contextRect.bottom,
scrollable: scroller.scrollHeight >= scroller.clientHeight,
broadcastVisible: Number(getComputedStyle(logo, '::before').opacity) > 0
&& Number(getComputedStyle(logo, '::after').opacity) > 0,
broadcastInsideViewport: logoRect.left >= 0 && logoRect.right <= innerWidth && logoRect.top - 15 >= 0,
tunerInside: tunerRect.left >= innerRect.left && tunerRect.right <= innerRect.right
&& tunerRect.left >= 0 && tunerRect.right <= innerWidth,
};
});
check('mobile Context host is full-width and keeps the tuner contained', mobile.left >= 0 && mobile.right <= 390 && mobile.width >= 350 && mobile.radioWidth < mobile.width && mobile.radioVisible && mobile.scrollable && mobile.tunerInside, JSON.stringify(mobile));
check('mobile playing state keeps both broadcast waves visible and on-screen', mobile.broadcastVisible && mobile.broadcastInsideViewport, JSON.stringify(mobile));
await page.click('#radio-stop-btn');
const actionableConsoleErrors = [...consoleErrors];
const externalFontFailures = [];
for (let i = actionableConsoleErrors.length - 1; i >= 0; i -= 1) {
const entry = actionableConsoleErrors[i];
if (entry.includes('Failed to load resource: the server responded with a status of 404')
&& entry.includes('[https://fonts.gstatic.com/')) {
externalFontFailures.push(...actionableConsoleErrors.splice(i, 1));
}
}
if (externalFontFailures.length > 0) {
console.log(`INFO external Google Fonts resource unavailable (${externalFontFailures.length}); Radio assertions continued with the local fallback font`);
}
if (externalCesiumEndpointFailures.length > 0) {
const requestEventIndex = actionableConsoleErrors.findIndex((entry) => (
entry.startsWith('[object RequestErrorEvent]')
&& entry.includes('/node_modules/.vite/deps/cesium.js')
));
if (requestEventIndex >= 0) actionableConsoleErrors.splice(requestEventIndex, 1);
for (let i = actionableConsoleErrors.length - 1; i >= 0; i -= 1) {
if (actionableConsoleErrors[i].includes('api.cesium.com/v1/assets/1/endpoint')
&& actionableConsoleErrors[i].includes('net::ERR_FAILED')) {
actionableConsoleErrors.splice(i, 1);
}
}
console.log(`INFO external Cesium ion asset endpoint unavailable (${externalCesiumEndpointFailures.length}); Radio assertions continued against the loaded app`);
}
check(
'runtime console remains clean',
actionableConsoleErrors.length === 0,
actionableConsoleErrors.slice(0, 3).join(' | '),
);
check(
'runtime has no HTTP 5xx responses',
failedResponses.length === 0,
failedResponses.slice(0, 3).join(' | '),
);
} finally {
await browser.close();
}
const failures = results.filter((result) => !result.ok).length;
console.log(`\nRadio QA: ${results.length - failures}/${results.length} passed`);
console.log(`RESULT: ${results.length - failures} passed, ${failures} failed, 0 skipped`);
process.exitCode = failures ? 1 : 0;
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});