Files
gods-eye-view/scripts/qa-map-source-controls.mjs
Sameh Khamis 4966877859 feat(ui): render the map source chips and status badge from the store
createMapSourceControls() (./ui/maps) no longer builds the chip row or
writes the status badge: it presents both and hands them to its publish
option, so the entry stays Vue-free. The shell publishes them to the
gev-display store and connects the controls, and the dock renders the
chips and calls select() for an available one. renderMapStackChips and
syncMapStackChips are deprecated. The map source QA harnesses wait for
the dock to render and no longer read the removed element refs.
2026-10-31 12:11:35 -07:00

213 lines
7.0 KiB
JavaScript

#!/usr/bin/env node
/** Browser acceptance for Map Source control lifecycle and truthful selection. */
import fs from 'node:fs';
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({
headless: true,
args: [
'--no-sandbox',
...(process.platform === 'darwin'
? ['--use-angle=metal', '--enable-gpu']
: ['--use-gl=angle', '--use-angle=swiftshader']),
],
});
const page = await browser.newPage();
const errors = [];
page.on('pageerror', (error) => errors.push(error.message));
let failures = 0;
const check = (name, ok) => {
console.log(`[${ok ? 'PASS' : 'FAIL'}] ${name}`);
if (!ok) failures++;
};
try {
await page.setViewport({ width: 1440, height: 900 });
const url = new URL(process.env.QA_BASE_URL || 'http://127.0.0.1:4173');
url.searchParams.set('welcome', '0');
await page.goto(url.href, { waitUntil: 'domcontentloaded' });
await page.waitForFunction(
() => {
const cover = document.getElementById('loading-screen');
return (
window.__godsEyeView?.styleManager?._mapSourceControls &&
cover?.classList.contains('hidden') &&
Number(getComputedStyle(cover).opacity) === 0
);
},
{ timeout: 60_000 },
);
await page.evaluate(() => {
const manager = window.__godsEyeView.styleManager;
const controller = manager.mapStackController;
const original = controller.setStack;
window.__qaMapSelections = [];
controller.setStack = function (id) {
window.__qaMapSelections.push(id);
return original.call(this, id);
};
manager.setPanelCollapsed('control-panel', false, {
persist: false,
syncShare: false,
});
});
await page.click('[data-stack-id="osm"]');
await page.waitForFunction(
() =>
window.__godsEyeView.styleManager.mapStackController.getActiveId() ===
'osm',
);
check(
'a native chip click requests OSM exactly once',
await page.evaluate(
() =>
window.__qaMapSelections.length === 1 &&
window.__qaMapSelections[0] === 'osm',
),
);
check(
'active chip and status follow the displayed OSM source',
await page.evaluate(() => {
const manager = window.__godsEyeView.styleManager;
return (
document
.getElementById('map-stack-chips')
.querySelector('[aria-pressed="true"]')?.dataset.stackId === 'osm' &&
document.getElementById('map-stack-status').textContent ===
manager.mapStackController.getState().activeStack.shortLabel
);
}),
);
check(
'the public map action uses the same component and truthful result',
await page.evaluate(async () => {
const manager = window.__godsEyeView.styleManager;
const result = await manager.setMapStack('esri-imagery');
// The dock renders the published row on Vue's next flush.
await new Promise((resolve) => setTimeout(resolve, 0));
const active = manager.mapStackController.getActiveId();
return (
window.__qaMapSelections.at(-1) === 'esri-imagery' &&
document
.getElementById('map-stack-chips')
.querySelector('[aria-pressed="true"]')?.dataset.stackId === active &&
(result.ok || !!manager.mapStackController.getState().lastError)
);
}),
);
check(
'refresh re-presents the chips without duplicating them',
await page.evaluate(async () => {
const manager = window.__godsEyeView.styleManager;
manager._mapSourceControls.refresh();
await new Promise((resolve) => setTimeout(resolve, 0));
return (
document.querySelectorAll('#map-stack-chips [data-stack-id="osm"]')
.length === 1
);
}),
);
check(
'refreshed chips still select once',
await page.evaluate(async () => {
const before = window.__qaMapSelections.length;
document
.getElementById('map-stack-chips')
.querySelector('[data-stack-id="osm"]')
.click();
await Promise.resolve();
return window.__qaMapSelections.length === before + 1;
}),
);
await page.waitForFunction(
() =>
window.__godsEyeView.styleManager.mapStackController.getActiveId() ===
'osm',
);
await page.evaluate(async () => {
const manager = window.__godsEyeView.styleManager;
await manager.setMapStack('photoreal');
manager.setPanelCollapsed('control-panel', false, {
persist: false,
syncShare: false,
});
document.querySelector('.map-stack-chip.active')?.focus();
});
await page.evaluate(() => {
const viewer = window.__godsEyeView.viewer;
viewer.camera.cancelFlight?.();
viewer.scene.tweens?.removeAll?.();
viewer.camera.setView({
destination: viewer.scene.globe.ellipsoid.cartographicToCartesian({
longitude: (-97.7431 * Math.PI) / 180,
latitude: (30.2568 * Math.PI) / 180,
height: 1200,
}),
orientation: { heading: 0, pitch: -0.85, roll: 0 },
});
viewer.scene.requestRender();
});
await page.evaluate(
() =>
new Promise((resolve) =>
requestAnimationFrame(() => requestAnimationFrame(resolve)),
),
);
const settled = await page
.waitForFunction(
() => {
const primitives = window.__godsEyeView.viewer.scene.primitives;
for (let i = 0; i < primitives.length; i++) {
const primitive = primitives.get(i);
if (
typeof primitive.tilesLoaded === 'boolean' &&
!primitive.tilesLoaded
)
return false;
}
return true;
},
{ timeout: 60_000 },
)
.then(
() => true,
() => false,
);
check('visible tile content settles before visual captures', settled);
fs.mkdirSync('qa-shots/map-source-controls', { recursive: true });
await page.screenshot({ path: 'qa-shots/map-source-controls/desktop.png' });
await page.setViewport({ width: 620, height: 900 });
await page.waitForFunction(
() =>
document.getElementById('left-panel-stack')?.dataset.layoutMode ===
'mobile',
);
await page.evaluate(() =>
window.__godsEyeView.styleManager.setPanelCollapsed(
'control-panel',
false,
{ persist: false, syncShare: false },
),
);
await page.screenshot({ path: 'qa-shots/map-source-controls/narrow.png' });
check(
'destroyed controls cannot issue requests from retained chips',
await page.evaluate(async () => {
const manager = window.__godsEyeView.styleManager;
const before = window.__qaMapSelections.length;
manager._mapSourceControls.destroy();
document
.getElementById('map-stack-chips')
.querySelector('[data-stack-id="osm"]')
.click();
const result = await manager._mapSourceControls.select('esri-imagery');
return result === null && window.__qaMapSelections.length === before;
}),
);
check('no uncaught browser errors', errors.length === 0);
if (errors.length) console.log(JSON.stringify(errors));
} finally {
await browser.close();
}
console.log(`RESULT: ${failures} failures`);
process.exitCode = failures ? 1 : 0;