From 4a3f53bfcd9a084291f77f6c8fd2ef74c872c885 Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Sat, 12 Sep 2026 22:36:48 -0700 Subject: [PATCH] test: verify Layers panel replacement in the browser --- scripts/format-scope.json | 3 +- scripts/qa-layer-panel.mjs | 115 +++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 scripts/qa-layer-panel.mjs diff --git a/scripts/format-scope.json b/scripts/format-scope.json index ea5240d82..16dbe251a 100644 --- a/scripts/format-scope.json +++ b/scripts/format-scope.json @@ -156,5 +156,6 @@ "scripts/qa-map-source-controls.mjs", "src/ui/layers.js", "src/ui/layerPanel.js", - "src/ui/clearLayersControl.js" + "src/ui/clearLayersControl.js", + "scripts/qa-layer-panel.mjs" ] diff --git a/scripts/qa-layer-panel.mjs b/scripts/qa-layer-panel.mjs new file mode 100644 index 000000000..ef458bee7 --- /dev/null +++ b/scripts/qa-layer-panel.mjs @@ -0,0 +1,115 @@ +#!/usr/bin/env node +/** Browser proof of Layers panel replacement and subscription ownership. */ +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(); +let failures = 0; +const errors = []; +page.on('pageerror', (error) => errors.push(error.message)); +const check = (name, passed) => { + console.log(`[${passed ? 'PASS' : 'FAIL'}] ${name}`); + if (!passed) failures++; +}; +try { + await page.goto( + `${process.env.QA_BASE_URL || 'http://localhost:4173'}/?welcome=0`, + { waitUntil: 'domcontentloaded' }, + ); + await page.waitForFunction( + () => window.__godsEyeView?.dataManager?._layerPanel, + { timeout: 60000 }, + ); + const results = await page.evaluate(async () => { + const manager = window.__godsEyeView.dataManager; + const container = manager._toggleContainer; + const id = 'qa-panel-lifecycle'; + let listener = null; + let enabled = 0; + window.__gevQaRegisterLayer(manager, { + id, + name: 'Literal layer', + icon: '◌', + source: 'Local fixture', + updateInterval: -1, + init() {}, + enable() { + enabled++; + }, + disable() {}, + update() {}, + destroy() {}, + getStats: () => ({ count: 1250, lastUpdate: Date.now(), stale: true }), + getRowControls: () => ({ chips: [] }), + setRowControlsListener: (callback) => { + listener = callback; + }, + }); + const result = []; + try { + manager.buildTogglePanel(container); + const row = () => container.querySelector(`[data-layer-id="${id}"]`); + const first = row().querySelector('.data-toggle-btn'); + result.push([ + 'descriptor text is literal content', + row().querySelector('.data-name').textContent === + 'Literal layer' && !row().querySelector('b'), + ]); + result.push([ + 'count is presented from the supplied snapshot', + row().querySelector('.data-count').textContent === '1.3K', + ]); + result.push([ + 'row subscription is installed', + typeof listener === 'function', + ]); + manager.buildTogglePanel(container); + first.click(); + await Promise.resolve(); + result.push([ + 'a detached row cannot issue an enable action', + enabled === 0 && !manager.isEnabled(id), + ]); + await manager.setEnabled(id, true); + result.push([ + 'feed state reflects the settled layer snapshot', + row().querySelector('.data-toggle-btn').dataset.feedState === 'stale', + ]); + const final = row().querySelector('.data-toggle-btn'); + manager._layerPanel.destroy(); + final.click(); + await Promise.resolve(); + result.push([ + 'destroyed panel releases row subscriptions', + listener === null, + ]); + result.push([ + 'destroyed controls cannot change the layer', + manager.isEnabled(id), + ]); + } finally { + manager._layerPanel?.destroy(); + manager._layerPanel = null; + await window.__gevQaUnregisterLayer(manager, id); + manager.buildTogglePanel(container); + } + result.push([ + 'ordinary layer rows return after reassembly', + container.querySelectorAll('.data-toggle-row').length > 10, + ]); + return result; + }); + for (const [name, passed] of results) check(name, passed); + check('no uncaught browser errors', errors.length === 0); +} finally { + await browser.close(); +} +console.log(`RESULT: ${failures} failures`); +process.exitCode = failures ? 1 : 0;