mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
A panel's collapsed, dock-pinned, dock-pinned-top, layout-auto-collapsed
and accordion-collapsed classes are drawn by its root's v-panel-state from
the gev-panel-chrome store. PanelChrome, its restore, the layout
controller, both rails and the side accordion present each panel's state
there instead of writing its classes; the rails call onCollapse(panel,
{collapsed, auto}). The directive applies a change in a synchronous
watcher, since the rails measure a panel in the task that opens or closes
it.
Destroying the chrome inside Cockpit now puts back the panels Cockpit's
entry closed while it can still present them. Without a shell, the voice
tools' set_panel_open reports that there is no panel to open. QA scripts
present the states they set instead of writing the classes.
286 lines
9.7 KiB
JavaScript
286 lines
9.7 KiB
JavaScript
#!/usr/bin/env node
|
|
/** Browser acceptance for application shortcuts and generated parameter controls. */
|
|
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, passed) => {
|
|
console.log(`[${passed ? 'PASS' : 'FAIL'}] ${name}`);
|
|
if (!passed) failures += 1;
|
|
};
|
|
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(
|
|
() => window.__godsEyeView?.styleManager?._applicationShortcuts,
|
|
{ timeout: 60_000 },
|
|
);
|
|
await page.waitForFunction(
|
|
() => {
|
|
const cover = document.getElementById('loading-screen');
|
|
return (
|
|
cover?.classList.contains('hidden') &&
|
|
Number(getComputedStyle(cover).opacity) === 0
|
|
);
|
|
},
|
|
{ timeout: 60_000 },
|
|
);
|
|
await page.evaluate(() => {
|
|
const manager = window.__godsEyeView.styleManager;
|
|
manager.setStyle('normal');
|
|
document.activeElement?.blur();
|
|
});
|
|
await page.keyboard.press('2');
|
|
check(
|
|
'native number shortcut selects CRT',
|
|
await page.evaluate(
|
|
() => window.__godsEyeView.styleManager.activeStyle === 'retro',
|
|
),
|
|
);
|
|
const initial = await page.evaluate(() => {
|
|
const manager = window.__godsEyeView.styleManager;
|
|
manager.setPanelCollapsed('pp-toggles', false, {
|
|
persist: false,
|
|
syncShare: false,
|
|
});
|
|
manager._presentPanel(manager._sliderPanel, { collapsed: false });
|
|
const slider = manager._sliderContainer.querySelector('input');
|
|
window.__qaOldSlider = slider;
|
|
const name = 'pixelation';
|
|
window.__qaParameter = {
|
|
name,
|
|
before: manager.stages.retro.uniforms[name],
|
|
};
|
|
slider.focus();
|
|
return {
|
|
before: Number(slider.value),
|
|
step: Number(slider.step),
|
|
label: slider.getAttribute('aria-label'),
|
|
};
|
|
});
|
|
check(
|
|
'generated range has an accessible label and a numeric step',
|
|
initial.label === 'Pixelation' && initial.step > 0,
|
|
);
|
|
await page.keyboard.press('ArrowRight');
|
|
const changed = await page.evaluate(() => {
|
|
const slider = window.__qaOldSlider;
|
|
const manager = window.__godsEyeView.styleManager;
|
|
return {
|
|
value: Number(slider.value),
|
|
readout: slider.nextElementSibling.textContent,
|
|
uniform: manager.stages.retro.uniforms[window.__qaParameter.name],
|
|
};
|
|
});
|
|
check(
|
|
'native range input changes the shader and formatted readout',
|
|
changed.value > initial.before &&
|
|
changed.uniform === changed.value &&
|
|
Number(changed.readout) === changed.value,
|
|
);
|
|
await page.keyboard.press('3');
|
|
check(
|
|
'focused range retains native input instead of changing style',
|
|
await page.evaluate(
|
|
() => window.__godsEyeView.styleManager.activeStyle === 'retro',
|
|
),
|
|
);
|
|
check(
|
|
'replacing a style revokes its detached slider',
|
|
await page.evaluate(() => {
|
|
const manager = window.__godsEyeView.styleManager;
|
|
const before = manager.stages.retro.uniforms[window.__qaParameter.name];
|
|
manager.setStyle('thermal');
|
|
window.__qaOldSlider.value = '0';
|
|
window.__qaOldSlider.dispatchEvent(new Event('input', { bubbles: true }));
|
|
return (
|
|
manager.stages.retro.uniforms[window.__qaParameter.name] === before
|
|
);
|
|
}),
|
|
);
|
|
check(
|
|
'normal style clears rows; reopening restores current shader values',
|
|
await page.evaluate(() => {
|
|
const manager = window.__godsEyeView.styleManager;
|
|
manager.setStyle('normal');
|
|
const cleared = manager._sliderContainer.children.length === 0;
|
|
manager.setStyle('retro');
|
|
return (
|
|
cleared &&
|
|
Number(manager._sliderContainer.querySelector('input').value) ===
|
|
manager.stages.retro.uniforms[window.__qaParameter.name]
|
|
);
|
|
}),
|
|
);
|
|
const hudBefore = await page.evaluate(() => {
|
|
document.getElementById('hud-layout-select').focus();
|
|
return window.__godsEyeView.styleManager.hud.visible;
|
|
});
|
|
await page.keyboard.press('h');
|
|
check(
|
|
'HUD select keeps its native typing and selection',
|
|
await page.evaluate(
|
|
(visible) =>
|
|
document.activeElement?.id === 'hud-layout-select' &&
|
|
window.__godsEyeView.styleManager.hud.visible === visible,
|
|
hudBefore,
|
|
),
|
|
);
|
|
const displayBefore = await page.evaluate(async () => {
|
|
const manager = window.__godsEyeView.styleManager;
|
|
manager._setBloomEnabled(true);
|
|
// The Display view shows the slider row on its next render.
|
|
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
manager._bloomSlider.value = '80';
|
|
manager._bloomSlider.dispatchEvent(new Event('input', { bubbles: true }));
|
|
manager._bloomSlider.focus();
|
|
return {
|
|
value: Number(manager._bloomSlider.value),
|
|
contrast: manager._bloomStage.uniforms.contrast,
|
|
};
|
|
});
|
|
await page.keyboard.press('ArrowRight');
|
|
check(
|
|
'native Display slider updates effect state',
|
|
await page.evaluate((before) => {
|
|
const manager = window.__godsEyeView.styleManager;
|
|
return (
|
|
Number(manager._bloomSlider.value) > before.value &&
|
|
manager._bloomStage.uniforms.contrast < before.contrast
|
|
);
|
|
}, displayBefore),
|
|
);
|
|
check(
|
|
'Display buttons toggle current state once',
|
|
await page.evaluate(() => {
|
|
const manager = window.__godsEyeView.styleManager;
|
|
const before = manager.bloomEnabled;
|
|
manager._bloomBtn.click();
|
|
const changed = manager.bloomEnabled !== before;
|
|
manager._bloomBtn.click();
|
|
return changed && manager.bloomEnabled === before;
|
|
}),
|
|
);
|
|
check(
|
|
'Display rebind removes old listeners',
|
|
await page.evaluate(() => {
|
|
const manager = window.__godsEyeView.styleManager;
|
|
manager._initUI();
|
|
const before = manager.bloomEnabled;
|
|
manager._bloomBtn.click();
|
|
return manager.bloomEnabled !== before;
|
|
}),
|
|
);
|
|
const sliders = await page.evaluate(() => {
|
|
const ordinary = [
|
|
...document.querySelectorAll('input[type="range"]'),
|
|
].filter(
|
|
(input) => !input.matches('#radio-tuner-slider, .bhote-event-range'),
|
|
);
|
|
return {
|
|
count: ordinary.length,
|
|
shared: ordinary.every((input) =>
|
|
input.classList.contains('gev-quantitative-slider'),
|
|
),
|
|
surface: ordinary.every((input) => {
|
|
const style = getComputedStyle(input);
|
|
return (
|
|
style.height === '18px' &&
|
|
style.appearance === 'none' &&
|
|
style.padding === '0px' &&
|
|
style.borderWidth === '0px' &&
|
|
(!input.disabled ||
|
|
(style.opacity === '0.42' && style.cursor === 'not-allowed'))
|
|
);
|
|
}),
|
|
tunerSeparate: !document
|
|
.getElementById('radio-tuner-slider')
|
|
.classList.contains('gev-quantitative-slider'),
|
|
};
|
|
});
|
|
check(
|
|
'ordinary sliders share one native surface',
|
|
sliders.count > 10 && sliders.shared && sliders.surface,
|
|
);
|
|
check('radio tuning keeps its specialized control', sliders.tunerSeparate);
|
|
fs.mkdirSync('qa-shots/visual-input', { recursive: true });
|
|
await page.screenshot({ path: 'qa-shots/visual-input/desktop.png' });
|
|
await page.setViewport({ width: 620, height: 900 });
|
|
await page.waitForFunction(
|
|
() =>
|
|
document.getElementById('left-panel-stack')?.dataset.layoutMode ===
|
|
'mobile',
|
|
);
|
|
await page.evaluate(() => {
|
|
const slider =
|
|
window.__godsEyeView.styleManager._sliderContainer.querySelector('input');
|
|
slider.focus();
|
|
slider.scrollIntoView({ block: 'center', behavior: 'instant' });
|
|
});
|
|
await page.screenshot({ path: 'qa-shots/visual-input/narrow.png' });
|
|
await page.evaluate(() => {
|
|
const manager = window.__godsEyeView.styleManager;
|
|
window.__qaCurrentSlider = manager._sliderContainer.querySelector('input');
|
|
manager._applicationShortcuts.destroy();
|
|
manager._styleParameters.destroy();
|
|
document.activeElement?.blur();
|
|
});
|
|
await page.keyboard.press('3');
|
|
check(
|
|
'destroyed application shortcuts cannot change the selected style',
|
|
await page.evaluate(
|
|
() => window.__godsEyeView.styleManager.activeStyle === 'retro',
|
|
),
|
|
);
|
|
check(
|
|
'destroyed parameter controls cannot write through a retained element',
|
|
await page.evaluate(() => {
|
|
const manager = window.__godsEyeView.styleManager;
|
|
const before = manager.stages.retro.uniforms[window.__qaParameter.name];
|
|
window.__qaCurrentSlider.value = '0';
|
|
window.__qaCurrentSlider.dispatchEvent(
|
|
new Event('input', { bubbles: true }),
|
|
);
|
|
return (
|
|
manager._sliderContainer.children.length === 0 &&
|
|
manager.stages.retro.uniforms[window.__qaParameter.name] === before
|
|
);
|
|
}),
|
|
);
|
|
check(
|
|
'destroyed Display controls cannot change settings',
|
|
await page.evaluate(() => {
|
|
const manager = window.__godsEyeView.styleManager;
|
|
manager._displayControls.destroy();
|
|
const before = manager.bloomEnabled;
|
|
const contrast = manager._bloomStage.uniforms.contrast;
|
|
manager._bloomBtn.click();
|
|
manager._bloomSlider.value = '15';
|
|
manager._bloomSlider.dispatchEvent(new Event('input', { bubbles: true }));
|
|
return (
|
|
manager.bloomEnabled === before &&
|
|
manager._bloomStage.uniforms.contrast === contrast
|
|
);
|
|
}),
|
|
);
|
|
check('runtime has no uncaught browser errors', errors.length === 0);
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
console.log(`RESULT: ${failures} failures`);
|
|
process.exitCode = failures ? 1 : 0;
|