Files
Halloween/tests/test_ui_status.js
2026-10-14 17:40:31 +02:00

58 lines
3.2 KiB
JavaScript

// Exercise the real status renderer without a browser, network or camera.
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
const html = fs.readFileSync(path.join(__dirname, '../halloween/index.html'), 'utf8');
const detectionRefresh = html.slice(html.indexOf(' async function refreshDetection() {'), html.indexOf(' function updateDetectionHelp()'));
const refresh = html.slice(html.indexOf(' async function refresh() {'), html.indexOf(' async function action('));
async function render(controller, detection) {
const nodes = {};
const context = vm.createContext({
document: {activeElement: null},
$: id => nodes[id] || (nodes[id] = {}),
names: {sanft: 'Sanft'},
fetch: async () => ({json: async () => ({controller, detection, camera: {ready:true, source:'Test'}})})
});
vm.runInContext(refresh, context);
await vm.runInContext('refresh()', context);
return nodes;
}
(async () => {
const controller = {mode:'sanft', phase:'Bereich frei abwarten', cooldown_seconds:0};
const ready = {enabled:true, ready:true, armed:true, method:'motion_test'};
let nodes = await render(controller, ready);
assert.equal(nodes.phase.textContent, 'Bereit (Automatik)', 'Automatic ready does not need manual clear');
nodes = await render({...controller, phase:'Bereit'}, {...ready, armed:false});
assert.equal(nodes.phase.textContent, 'Ruhiges Bild abwarten (Automatik)', 'Manual clear cannot claim automatic ready');
for (const phase of ['Stopp', 'Sperrzeit', 'Sanft (simuliert)']) {
nodes = await render({...controller, phase}, ready);
assert.equal(nodes.phase.textContent, phase, 'Preserve actual sequence/stop/cooldown');
}
nodes = await render({...controller, mode:'aus'}, ready);
assert.equal(nodes.phase.textContent, 'Modus ohne Schreckauslösung');
nodes = await render(controller, {...ready, reset_required:true});
assert.equal(nodes.phase.textContent, 'Erkennung gesperrt; zurücksetzen');
nodes = await render(controller, {...ready, enabled:false});
assert.equal(nodes.phase.textContent, controller.phase, 'Manual mode retains its own clear requirement');
const motionNodes = {};
const motionContext = vm.createContext({
$: id => motionNodes[id] || (motionNodes[id] = {}),
detectionEpoch:0, performance:{now:() => 0}, people:[], processed:false,
setTimeout:() => {}, drawDetection:() => {},
fetch:async () => ({ok:true, json:async () => ({
status:{...ready, fps:5}, image_base64:null,
motion:{changed_fraction:.0014, note:'Bewegung in Pfeilrichtung', confirmed_frames:2, moving:true, directed:true}
})})
});
vm.runInContext(detectionRefresh, motionContext);
await vm.runInContext('refreshDetection()', motionContext);
assert.equal(motionNodes['motion-details'].hidden, false);
assert.match(motionNodes['motion-details'].textContent, /0,14 %/);
assert.match(motionNodes['motion-details'].textContent, /Bewegung in Pfeilrichtung/);
assert.match(motionNodes['motion-details'].textContent, /Bestätigung 2\/2/);
console.log('UI status tests passed');
})().catch(error => {console.error(error); process.exitCode = 1;});