// 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;});