mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 18:34:02 +00:00
The Display panel, the dock's style buttons and the clean-view exit call the display store's actions, and DisplayBindings connects their handlers to it, replacing the bindDisplayControls listeners. The detection allocation and 3D model coverage choices render through a new kit UiSegmented, which takes v-model, options, label, stacked and disabled, and adds a keyboard policy, item classes and per-option attributes. Shell element fields and facade accessors that only the old listeners read are removed. The package boundaries now own UiSegmented and the UiPanel kit files, and the format scope drops the deleted test.
286 lines
9.8 KiB
JavaScript
286 lines
9.8 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._displayBindings._detachDisplayControls();
|
|
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;
|