import { test } from 'node:test' import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import vm from 'node:vm' import { dauer, einstellungen, frameAt, WEBKLAR } from '../public/neu/glas-logo.js' const lese = (p) => readFileSync(new URL(`../public/neu/${p}`, import.meta.url), 'utf8') const ohneKommentare = (q) => q.replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|[^:'"`\\])\/\/[^\n]*/g, '$1') test('Glas-Logo: WEBklar-Vorgaben (weiß, Oval #111111) und eigene Werte gehen vor', () => { const P = einstellungen({ finalScale: 0.9 }) assert.equal(P.background, '#ffffff'); assert.equal(P.logoColor, '#111111'); assert.equal(P.finalScale, 0.9) assert.equal(P.logoScale, WEBKLAR.logoScale); assert.equal(P.present, 2.4) assert.equal(einstellungen().finalScale, WEBKLAR.finalScale) }) test('Glas-Logo: Zeitleiste beginnt leer, endet mit dem blanken Logo ohne Glas (3,5 s)', () => { const P = einstellungen({ finalScale: 0.9 }) assert.equal(dauer(P), 3.5) assert.equal(frameAt(0, P).uPlateAlpha, 0) const mitte = frameAt(1.5, P) assert.equal(mitte.uPlateAlpha, 1); assert.equal(mitte.uOptics, 1); assert.equal(mitte.uBare, 0) const ende = frameAt(dauer(P), P) assert.ok(ende.uPlateAlpha < 0.01, `Glas noch sichtbar: ${ende.uPlateAlpha}`); assert.ok(ende.uOptics < 0.01); assert.equal(ende.uBare, 1) assert.deepEqual(ende.uBg, [1, 1, 1]); assert.deepEqual(ende.uLogoCol, [17 / 255, 17 / 255, 17 / 255]) assert.ok(Math.abs(ende.uLogoHalf - 0.6 * 0.9 * 0.955) < 0.01, `Logogröße am Ende: ${ende.uLogoHalf}`) }) test('Glas-Logo: das Glas wird nie breiter als 1,3 Kacheln (die Anmeldung rechnet mit rand 1,4)', () => { const P = einstellungen({ finalScale: 0.9 }) let max = 0 for (let t = 0; t <= dauer(P) + 1e-9; t += 0.005) max = Math.max(max, frameAt(t, P).uPlateScale) assert.ok(max < 1.3, `größte Glasbreite ${max}`) assert.match(lese('login-logo.js'), /rand: 1\.4/) }) test('Glas-Logo: MIT-Hinweis des Originals bleibt im Quelltext', () => { const q = lese('glas-logo.js') assert.match(q, /Copyright \(c\) 2026 Zanwei Guo/); assert.match(q, /Permission is hereby granted, free of charge/) assert.match(q, /THE SOFTWARE IS PROVIDED "AS IS"/); assert.match(q, /github\.com\/zanwei\/glass-icon-reveal/) }) test('Glas-Logo, Anmeldung, Startbild: kein HTML-Einsetzen, nur relative Importe, kein console.log', () => { for (const datei of ['glas-logo.js', 'login-logo.js', 'startbild.js']) { const q = ohneKommentare(lese(datei)) assert.doesNotMatch(q, /innerHTML|insertAdjacentHTML|outerHTML|document\.write|console\.log|\beval\(/, datei) for (const m of q.matchAll(/from '([^']+)'/g)) assert.match(m[1], /^\.\//, `${datei}: nur relative Importe`) } // Das Startbild ist ein klassisches Skript (läuft im Kopf vor dem ersten Bild) und lädt das Modul selbst assert.doesNotMatch(ohneKommentare(lese('startbild.js')), /^\s*(import|export)\s/m) assert.match(lese('startbild.js'), /import\('\/neu\/glas-logo\.js'\)/) }) /* ------------------------------------------------------------------ Startbild in einer VM --- */ const STARTBILD = lese('startbild.js') function element(tag) { const el = { tag, id: '', className: '', textContent: '', kinder: [], attrs: {}, klassen: new Set(), hoerer: {}, entfernt: false, setAttribute(k, v) { el.attrs[k] = v }, appendChild(kind) { el.kinder.push(kind); return kind }, addEventListener(typ, fn) { el.hoerer[typ] = fn }, remove() { el.entfernt = true }, classList: { add: (c) => el.klassen.add(c) }, } return el } function starte({ standalone = false, ios = false, gemerkt = false, speicherWirft = false } = {}) { const attr = new Map() const kopf = [] const hoerer = {} const timer = [] const speicher = new Map(gemerkt ? [['kn-startbild', '1']] : []) const wirf = () => { if (speicherWirft) throw new Error('Speicher gesperrt') } const document = { documentElement: { setAttribute: (k, v) => attr.set(k, v), removeAttribute: (k) => attr.delete(k) }, head: { appendChild: (el) => kopf.push(el) }, body: null, createElement: (tag) => element(tag), addEventListener: (typ, fn) => { hoerer[typ] = fn }, } const window = { matchMedia: (q) => ({ matches: standalone && /display-mode:\s*standalone/.test(q) }), navigator: { standalone: ios }, sessionStorage: { getItem: (k) => { wirf(); return speicher.get(k) ?? null }, setItem: (k, v) => { wirf(); speicher.set(k, v) } }, } // import() ohne Modul-Lader schlägt in der VM fehl: wie ein Startbild, dessen Modul nicht lädt vm.runInNewContext(STARTBILD, { window, document, setTimeout: (fn, ms) => timer.push({ fn, ms }) }) return { attr, kopf, hoerer, timer, speicher, document } } const ruhe = () => new Promise((r) => setImmediate(r)) test('Startbild: im Browser (nicht installiert) passiert nichts', () => { const s = starte() assert.equal(s.attr.size, 0); assert.equal(s.kopf.length, 0); assert.equal(s.speicher.size, 0); assert.equal(s.hoerer.DOMContentLoaded, undefined) }) test('Startbild: nur einmal je App-Start (sessionStorage), ohne Speicher gar nicht', () => { for (const s of [starte({ standalone: true, gemerkt: true }), starte({ standalone: true, speicherWirft: true })]) { assert.equal(s.attr.size, 0); assert.equal(s.kopf.length, 0) } }) test('Startbild: installierte App deckt sofort weiß ab, zeigt die Fläche und blendet sie aus, auch wenn das Modul nicht lädt', async () => { for (const art of [{ standalone: true }, { ios: true }]) { const s = starte(art) assert.equal(s.attr.get('data-kn-startbild'), 'deckt'); assert.equal(s.speicher.get('kn-startbild'), '1') assert.equal(s.kopf.length, 1); assert.match(s.kopf[0].textContent, /html\[data-kn-startbild="deckt"\]::before\{[^}]*position:fixed[^}]*background:#fff/) assert.match(s.kopf[0].textContent, /#kn-startbild\.kn-startbild-weg\{opacity:0/) assert.equal(typeof s.hoerer.DOMContentLoaded, 'function') s.document.body = element('body') s.hoerer.DOMContentLoaded() const flaeche = s.document.body.kinder[0] assert.equal(flaeche.id, 'kn-startbild'); assert.equal(flaeche.attrs['aria-hidden'], 'true') assert.equal(flaeche.kinder[0].className, 'kn-startbild-logo') assert.equal(s.attr.get('data-kn-startbild'), 'spielt', 'die Fläche liegt jetzt selbst über der Seite') assert.ok(s.timer.some((t) => t.ms === 6500), 'Sicherheitszeit') await ruhe() const halten = s.timer.find((t) => t.ms === 120) assert.ok(halten, 'ohne Modul blendet die Fläche nach kurzer Pause aus') halten.fn() assert.ok(flaeche.klassen.has('kn-startbild-weg')) flaeche.hoerer.transitionend() assert.equal(flaeche.entfernt, true); assert.equal(s.attr.has('data-kn-startbild'), false) } }) test('Startbild: ein Tipp überspringt es', () => { const s = starte({ standalone: true }) s.document.body = element('body') s.hoerer.DOMContentLoaded() const flaeche = s.document.body.kinder[0] flaeche.hoerer.click() assert.ok(flaeche.klassen.has('kn-startbild-weg')) })