// Render-Probe: laesst ALLE render*-Funktionen gegen ein Minimal-DOM laufen. // // ⚠⚠ WOZU. Ein `ReferenceError` mitten im Render bricht den GESAMTEN Durchlauf // ab: das Dashboard friert auf Altwerten ein, Server und Snapshot sind dabei // korrekt. `node --check` sieht das NICHT — die Datei ist syntaktisch einwandfrei. // Real passiert: // · 01.08. — ein direkter Zugriff auf ein noch nicht ausgeliefertes Element // · 20.08. — die RSI-Zeile griff in `renderM15(s)` auf `d.market` zu // · 20.08. — der Trailing-Block landete in `renderKontext(snap)` und las `d.…` // Der dritte Fall waere durch die ALTE Fassung dieser Probe geschluepft: sie // schnitt per Regex genau ZWEI Funktionen heraus (`renderM15`, `renderBrk`). // `renderKontext` und 13 weitere waren nie abgedeckt. // // ⚠ Jetzt wird die KOMPLETTE `app.js` in einem VM-Kontext geladen und jede // gefundene `render*`-Funktion aufgerufen — mit dem Fixture-Snapshot UND mit // einem leeren Objekt (der Kaltstart-Fall, in dem jedes Feld fehlt). // // ⚠ Kein npm, kein jsdom: das DOM ist eine Attrappe von 20 Zeilen. Damit bleibt // die Pruefumgebung dieselbe Laufzeit wie die Zielumgebung (V8) — genau die // Divergenz, die beim `.ps1`-BOM und beim pre-commit-Framework zugeschlagen hat. // // Aufruf: node tools/render_check.mjs import fs from "node:fs"; import vm from "node:vm"; const pfad = process.argv[2] || "tests/fixtures/snapshot.json"; const snap = JSON.parse(fs.readFileSync(pfad, "utf-8")); const src = fs.readFileSync("web/app.js", "utf-8"); function el() { const o = { textContent: "", innerHTML: "", className: "", title: "", value: "", disabled: false, checked: false, style: {}, dataset: {}, classList: { add() {}, remove() {}, toggle() {}, contains() { return false; } }, appendChild() {}, removeChild() {}, setAttribute() {}, removeAttribute() {}, addEventListener() {}, focus() {}, blur() {}, remove() {}, getBoundingClientRect() { return { top: 0, left: 0, width: 0, height: 0 }; }, querySelector() { return el(); }, querySelectorAll() { return []; }, children: [], parentNode: null, firstChild: null, }; return o; } const ctx = { console, document: { getElementById: () => el(), querySelector: () => el(), querySelectorAll: () => [], createElement: () => el(), addEventListener() {}, body: el(), documentElement: el(), activeElement: null, hidden: false, visibilityState: "visible", }, window: { addEventListener() {}, setTimeout() {}, clearTimeout() {}, location: { href: "", protocol: "http:", host: "x" }, localStorage: { getItem: () => null, setItem() {}, removeItem() {} }, matchMedia: () => ({ matches: false, addEventListener() {} }), }, navigator: { userAgent: "node" }, location: { href: "", protocol: "http:", host: "x" }, localStorage: { getItem: () => null, setItem() {}, removeItem() {} }, setInterval: () => 0, clearInterval() {}, setTimeout: () => 0, clearTimeout() {}, requestAnimationFrame: () => 0, fetch: () => new Promise(() => {}), WebSocket: function () { return { close() {}, send() {} }; }, AudioContext: function () { return { createOscillator: () => ({ connect() {}, start() {}, stop() {}, frequency: { setValueAtTime() {} }, type: "" }), createGain: () => ({ connect() {}, gain: { setValueAtTime() {}, exponentialRampToValueAtTime() {} } }), currentTime: 0, destination: {}, resume: () => Promise.resolve(), state: "running" }; }, Notification: function () {}, alert() {}, confirm: () => true, prompt: () => null, }; ctx.window.document = ctx.document; ctx.globalThis = ctx; ctx.self = ctx; vm.createContext(ctx); try { vm.runInContext(src, ctx, { filename: "app.js" }); } catch (e) { console.log(` ❌ app.js liess sich nicht laden: ${e.constructor.name}: ${e.message}`); process.exit(1); } // Jede render*-Funktion einsammeln (die im Kontext wirklich existiert). const namen = Object.keys(ctx).filter( (k) => /^render/.test(k) && typeof ctx[k] === "function"); if (namen.length < 5) { console.log(` ❌ nur ${namen.length} render*-Funktionen gefunden — Probe greift nicht`); process.exit(1); } // ⚠ Zwei Durchlaeufe. Der LEERE Snapshot ist der wichtigere: er stellt den // Kaltstart nach, in dem noch kein Feld befuellt ist — dort schlagen fehlende // Null-Pruefungen zu, nicht bei vollen Daten. const faelle = [["Fixture", snap], ["leerer Snapshot", {}]]; let fehler = 0; for (const [lbl, daten] of faelle) { for (const n of namen) { try { // `renderM15` nimmt (setup, snapshot), alle uebrigen nur (snapshot). if (n === "renderM15") ctx[n](daten.m15_setup || {}, daten); else ctx[n](daten); } catch (e) { fehler++; console.log(` ❌ ${n}(${lbl}): ${e.constructor.name}: ${e.message}`); } } } if (fehler) { console.log(` ${fehler} Fehler in ${namen.length} render*-Funktionen`); process.exit(1); } console.log(` ✅ ${namen.length} render*-Funktionen laufen durch ` + `(Fixture UND leerer Snapshot)`);