const { test } = require('node:test') const assert = require('node:assert/strict') const fs = require('node:fs') const vm = require('node:vm') const ts = require('typescript') function harness() { const exports = {}, requests = [], revoked = [], pending = new Map() let now = 10000, state = null, receivedAt = now, visible = false, enabledFetch = true let sequence = 0 function element() { return { children: [], dataset: {}, src: '', append(...children) { this.children.push(...children); children.forEach(child => { child.parent = this }) }, remove() { this.parent.children = this.parent.children.filter(child => child !== this) }, removeAttribute() { this.src = '' }, addEventListener() {} } } const root = element(), document = { hidden: false, createElement: element, addEventListener() {}, removeEventListener() {} } const globals = { exports, document, AbortController, Date: { now: () => now }, window: { setTimeout: () => 1, clearTimeout() {}, setInterval: () => 1, clearInterval() {} }, URL: { createObjectURL: () => `blob:${++sequence}`, revokeObjectURL: url => revoked.push(url) }, fetch: async (url, options) => { requests.push({ url, signal: options.signal }) if (pending.has(url)) await pending.get(url) return { ok: enabledFetch, blob: async () => ({}) } } } vm.runInNewContext(ts.transpileModule(fs.readFileSync(require.resolve('../src/renderer/public/slam-viewer/camera-preview.js'), 'utf8'), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 } }).outputText, globals) const controller = exports.createCameraPreview({ root, baseUrl: 'http://camera.test', getState: () => ({ state, receivedAt }), onAvailable: value => { visible = value } }) return { root, requests, revoked, pending, document, controller, state(value) { state = value; receivedAt = now }, advance(ms) { now += ms }, fail(value) { enabledFetch = !value }, visible: () => visible, settle: () => new Promise(resolve => setImmediate(resolve)) } } test('legacy camera polling stays compatible and skips absent, hidden and stale cameras', async () => { const h = harness() h.controller.setEnabled(true) assert.equal(h.requests.length, 0) h.state({ topic_status: { camera_image: { available: true } } }) h.controller.refresh(); await h.settle() assert.equal(h.requests[0].url, 'http://camera.test/camera-frame') assert.equal(h.root.children[0].children[1].src, 'blob:1') h.document.hidden = true h.controller.refresh(); await h.settle() assert.equal(h.requests.length, 1) h.advance(3001) h.controller.refresh() assert.equal(h.visible(), false) assert.equal(h.root.children.length, 0) assert.deepEqual(h.revoked, ['blob:1']) }) test('multiple camera frames are isolated and one unavailable camera cannot hide another', async () => { const h = harness() h.state({ cameras: [{ id: 'video0', name: '/dev/video0', available: true }, { id: 'video2', name: '/dev/video2', available: true }] }) h.controller.setEnabled(true); await h.settle() assert.equal(h.root.children.length, 2) assert.equal(h.requests.length, 2) assert.deepEqual(h.requests.map(request => request.url), ['http://camera.test/camera-frame?camera_id=video0', 'http://camera.test/camera-frame?camera_id=video2']) const first = h.root.children[0].children[1].src h.state({ cameras: [{ id: 'video2', name: '/dev/video2', available: true }] }) h.controller.refresh(); await h.settle() assert.equal(h.root.children.length, 1) assert.equal(h.root.children[0].dataset.cameraId, 'video2') assert.ok(h.revoked.includes(first)) assert.equal(h.visible(), true) h.controller.dispose() assert.equal(h.root.children.length, 0) assert.equal(h.visible(), false) }) test('in-flight requests are bounded and late frames cannot resurrect a removed or disabled camera', async () => { const h = harness() let release h.pending.set('http://camera.test/camera-frame?camera_id=video0', new Promise(resolve => { release = resolve })) h.state({ cameras: [{ id: 'video0', available: true }] }) h.controller.setEnabled(true) h.controller.refresh() assert.equal(h.requests.length, 1) h.controller.setEnabled(false) assert.equal(h.requests[0].signal.aborted, true) release(); await h.settle() assert.equal(h.root.children.length, 0) assert.equal(h.visible(), false) }) test('HTTP failures back off per camera and reconnecting restores frames', async () => { const h = harness() h.state({ cameras: [{ id: 'video0', available: true }] }) h.fail(true) h.controller.setEnabled(true); await h.settle() h.controller.refresh(); await h.settle() assert.equal(h.requests.length, 1) h.advance(5000); h.state({ cameras: [{ id: 'video0', available: true }] }); h.fail(false) h.controller.refresh(); await h.settle() assert.equal(h.requests.length, 2) assert.ok(h.root.children[0].children[1].src.startsWith('blob:')) }) test('camera identifiers are validated, deduplicated and limited without executing names as markup', async () => { const h = harness() h.state({ cameras: [{ id: '../etc', available: true }, ...Array.from({ length: 12 }, (_, i) => ({ id: `video${i}`, available: true, name: '' })), { id: 'video0', available: true }] }) h.controller.setEnabled(true); await h.settle() assert.equal(h.requests.length, 8) assert.equal(h.root.children.length, 8) assert.equal(h.root.children[0].children[0].textContent, '') })