// Teil des UI-Testlaufs (scripts/ui-smoke.js): prüft die Startanimation in echtem Electron mit einem // Ersatz-Hauptfenster. Das Startfenster muss genau über dem Hauptfenster liegen, wirklich durchsichtig sein // (kein weißes Feld), das Hauptfenster bis zum Aufbau verborgen bleiben, das Bild von der Mitte aus // aufgezogen werden und am Ende das Hauptfenster sichtbar sein. Gibt „✓ …“ und „✗ …“ Zeilen aus. // // Die Durchsichtigkeit wird über eine Bildschirmaufnahme geprüft: An einer leeren Stelle des Startfensters // muss dieselbe Farbe stehen wie vorher auf dem Desktop. Dafür darf die Hardware-Beschleunigung nicht // abgeschaltet sein, sonst verhalten sich durchsichtige Fenster anders als im echten Programm. const { app, BrowserWindow, desktopCapturer, screen } = require('electron'); const path = require('path'); const ROOT = path.resolve(__dirname, '..'); const { begin, play, MIN_SPIN_MS } = require(path.join(ROOT, 'src', 'features', 'splash.js')); let failed = 0; function check(name, ok, detail = '') { if (!ok) failed++; console.log(`${ok ? '✓' : '✗'} ${name}${detail ? ` — ${detail}` : ''}`); } app.on('window-all-closed', (e) => e.preventDefault()); const wait = (ms) => new Promise((r) => setTimeout(r, ms)); // Farbe des Bildschirms an einer Stelle (Koordinaten des Hauptbildschirms) als [r, g, b] async function pixelAt(x, y) { const d = screen.getPrimaryDisplay(); const sf = d.scaleFactor; const sources = await desktopCapturer.getSources({ types: ['screen'], thumbnailSize: { width: Math.round(d.size.width * sf), height: Math.round(d.size.height * sf) } }); const src = sources.find((s) => String(s.display_id) === String(d.id)) || sources[0]; const size = src.thumbnail.getSize(); const bmp = src.thumbnail.toBitmap(); // BGRA const i = (Math.round(y * sf) * size.width + Math.round(x * sf)) * 4; return [bmp[i + 2], bmp[i + 1], bmp[i]]; } const close = (a, b) => a.every((v, k) => Math.abs(v - b[k]) <= 6); app.whenReady().then(async () => { const page = 'data:text/html;charset=utf-8,' + encodeURIComponent( '
Repo
'); const main = new BrowserWindow({ width: 900, height: 600, frame: false, show: false, webPreferences: { sandbox: true } }); main.loadURL(page); const loaded = new Promise((resolve) => main.webContents.once('did-finish-load', resolve)); await loaded; const mainBounds = main.getBounds(); // Leere Stelle nahe der linken oberen Ecke des Fensters (das Logo sitzt in der Mitte) const sx = mainBounds.x + 24; const sy = mainBounds.y + 24; const desktopColor = await pixelAt(sx, sy); let sawSplashWindow = false; let boundsMatch = false; let hiddenWhileSpinning = true; let sawGrow = false; const startedAt = Date.now(); const handle = begin({ htmlPath: path.join(ROOT, 'renderer', 'splash.html'), bounds: mainBounds }); // Die Oberfläche gilt erst später als bereit, damit das Logo lange genug dreht, um es zu messen const slowReady = wait(3500); const playing = play(main, slowReady, handle); // Durchsichtigkeit messen, solange das Logo dreht (nach dem Zeigen und der Verzögerung für die Mausereignisse) const duringColor = (async () => { await handle.shownAt; await wait(1400); return pixelAt(sx, sy); })(); // Während der Animation beobachten for (let i = 0; i < 500; i++) { const others = BrowserWindow.getAllWindows().filter((w) => w !== main); if (others.length) { sawSplashWindow = true; const b = others[0].getBounds(); if (b.x === mainBounds.x && b.y === mainBounds.y && b.width === mainBounds.width && b.height === mainBounds.height) boundsMatch = true; if (Date.now() - startedAt < MIN_SPIN_MS - 250 && main.isVisible() && main.getOpacity() > 0) hiddenWhileSpinning = false; try { if (await others[0].webContents.executeJavaScript("document.body.classList.contains('aufbau')")) sawGrow = true; } catch (_) { /* Fenster schon geschlossen */ } } if (sawSplashWindow && others.length === 0) break; await wait(40); } await playing; const duration = Date.now() - startedAt; const measured = await duringColor; check('Startfenster erscheint über dem Hauptfenster', sawSplashWindow && boundsMatch); check('Startfenster ist durchsichtig (kein weißes Feld)', close(measured, desktopColor) && !close(measured, [255, 255, 255]), `Desktop ${desktopColor.join(',')} / Startfenster ${measured.join(',')}`); check('Hauptfenster bleibt während des Drehens unsichtbar', hiddenWhileSpinning); check(`Logo dreht sich mindestens ${MIN_SPIN_MS} ms`, duration >= MIN_SPIN_MS, `${duration} ms`); check('Animation endet in angemessener Zeit', duration < 12000, `${duration} ms`); check('Fenster wird von der Mitte aus aufgezogen', sawGrow); check('Danach ist das Hauptfenster sichtbar und deckend', main.isVisible() && main.getOpacity() === 1); await wait(300); check('Startfenster ist danach geschlossen', BrowserWindow.getAllWindows().filter((w) => w !== main).length === 0); app.exit(failed ? 1 : 0); }).catch((e) => { console.log(`✗ Startanimation ohne Ausnahme — ${e.message}`); app.exit(1); });