Files
Git-Manager-Gui/scripts/ui-smoke-splash.js
T
2026-10-22 09:13:21 +00:00

111 lines
5.1 KiB
JavaScript

// 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(
'<body style="margin:0;background:#123"><div id="explorerGrid"><div>Repo</div></div></body>');
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);
});