111 lines
5.1 KiB
JavaScript
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);
|
|
});
|