// Executes web/index.html's script against a stub DOM and fails on anything thrown. // // This exists because a ReferenceError at load once blanked the whole UI: a patch // anchored on a function that no longer existed, so `prefsModal` was referenced but // never defined. `node --check` passes that happily -- it is a parse, not a run -- // and every server-side test passed too, because the server was fine. // // node tests/page-smoke.js const fs = require('fs'); const path = require('path'); const vm = require('vm'); const html = fs.readFileSync(path.join(__dirname, '..', 'web', 'index.html'), 'utf8'); const script = html.split('')[0]; const ids = new Set([...html.matchAll(/id="([^"]+)"/g)].map(m => m[1])); const missing = []; const el = (name) => new Proxy({ style: {}, dataset: {}, classList: { add(){}, remove(){}, toggle(){}, contains(){ return false; } }, value: '', textContent: '', innerHTML: '', hidden: false, children: [], firstElementChild: null, appendChild(){}, removeChild(){}, remove(){}, insertAdjacentHTML(){}, addEventListener(){}, setAttribute(){}, getAttribute(){ return null; }, select(){}, setSelectionRange(){}, focus(){}, replaceWith(){}, querySelector(){ return el('nested'); }, querySelectorAll(){ return []; }, play(){ return Promise.resolve(); }, pause(){}, closest(){ return null; } }, { get: (t, k) => k in t ? t[k] : undefined, set: (t, k, v) => (t[k] = v, true) }); const document = { querySelector(sel) { if (sel.startsWith('#') && !ids.has(sel.slice(1))) { missing.push(sel); return null; } return el(sel); }, querySelectorAll: () => [], createElement: () => el('created'), addEventListener(){}, body: el('body'), documentElement: { dataset: {} }, }; const ctx = { document, console, window: { isSecureContext: false, addEventListener(){} }, localStorage: { getItem: () => null, setItem(){}, removeItem(){} }, navigator: { clipboard: undefined, sendBeacon(){}, mediaSession: undefined }, fetch: (url) => Promise.resolve({ ok: true, status: 200, text: () => Promise.resolve(''), json: () => Promise.resolve( String(url).includes('/api/settings') ? { schedule: 'every 60m', every_mins: 60, download_dir: '/tmp', max_total_gb: 0, max_age_days: 0 } : []), }), EventSource: function () { this.close = () => {}; }, MediaMetadata: function () {}, Blob: function () {}, setTimeout, clearTimeout, setInterval, clearInterval, confirm: () => false, prompt: () => null, alert(){}, Date, Math, JSON, Object, Array, String, Number, Promise, Error, FormData: function(){}, URLSearchParams, encodeURIComponent, decodeURIComponent, parseInt, parseFloat, isNaN, }; ctx.globalThis = ctx; ctx.window.location = { href: '' }; try { vm.createContext(ctx); vm.runInContext(script, ctx, { filename: 'index.html