// Teil des UI-Testlaufs (scripts/ui-smoke.js): prüft das Tray-Menüfenster in echtem Electron, ohne das // Tray-Symbol selbst. Zeigt das Fenster, fährt mit der Maus darüber, klickt, drückt Esc. // Gibt „✓ …“ und „✗ …“ Zeilen aus; Aufruf durch ui-smoke.js. const { app } = require('electron'); const path = require('path'); const ROOT = path.resolve(__dirname, '..'); const { createTrayPopup } = require(path.join(ROOT, 'src', 'features', 'trayPopup.js')); const trayMenu = require(path.join(ROOT, 'src', 'features', 'trayMenu.js')); let failed = 0; function check(name, ok, detail = '') { if (!ok) failed++; console.log(`${ok ? '✓' : '✗'} ${name}${detail ? ` — ${detail}` : ''}`); } app.disableHardwareAcceleration(); app.on('window-all-closed', (e) => e.preventDefault()); app.whenReady().then(async () => { const wait = (ms) => new Promise((r) => setTimeout(r, ms)); const log = []; let active = 1; const template = () => trayMenu.buildTrayTemplate({ version: '0.0.0', active, queued: 2, paused: false, autostart: true, onOpen: () => log.push('open'), onHistory: () => log.push('history'), onPause: () => log.push('pause'), onCancel: () => log.push('cancel'), onAutostart: () => log.push('autostart'), onQuit: () => log.push('quit') }); const popup = createTrayPopup({ htmlPath: path.join(ROOT, 'renderer', 'tray-menu.html'), preloadPath: path.join(ROOT, 'tray-menu-preload.js'), getModel: () => trayMenu.serializeTemplate(template()), onAction: (id) => trayMenu.runAction(template(), id) }); const js = (code) => popup.getWindow().webContents.executeJavaScript(code); const items = async () => JSON.parse(await js(`JSON.stringify([...document.querySelectorAll('.item')].map(b => { const r = b.getBoundingClientRect(); return { text: b.innerText.trim(), disabled: b.disabled, x: r.x, y: r.y, h: r.height }; }))`)); const mid = (i) => ({ x: Math.round(i.x + 40), y: Math.round(i.y + i.h / 2) }); const bg = (label) => js(`(() => { const b = [...document.querySelectorAll('.item')].find(x => x.innerText.includes(${JSON.stringify(label)})); return getComputedStyle(b).backgroundColor; })()`); const mouse = (type, p, extra = {}) => popup.getWindow().webContents.sendInputEvent({ type, x: p.x, y: p.y, ...extra }); try { popup.warmUp(); await wait(6000); // erster Renderer-Start dauert mehrere Sekunden popup.show(); await wait(800); check('Menüfenster wird sichtbar', popup.isVisible()); let list = await items(); check('Menü zeigt Einträge mit Pausieren und Abbrechen', ['Fenster öffnen', 'Upload-Verlauf', 'Übertragung pausieren', 'Laufende Übertragung abbrechen', 'Beenden'].every((t) => list.some((i) => i.text.includes(t)))); // Hover folgt der Maus const history = list.find((i) => i.text.includes('Upload-Verlauf')); // Das frisch geöffnete Fenster braucht einen Moment, bis es sein erstes Bild gezeichnet hat und Hover annimmt. let hovered = 'rgba(0, 0, 0, 0)'; for (let i = 0; i < 12 && hovered === 'rgba(0, 0, 0, 0)'; i++) { mouse('mouseMove', { x: mid(history).x + (i % 2), y: mid(history).y }); await wait(250); hovered = await bg('Upload-Verlauf'); } const other = await bg('Fenster öffnen'); check('Hover markiert den Eintrag unter der Maus', hovered !== 'rgba(0, 0, 0, 0)' && other === 'rgba(0, 0, 0, 0)', `${hovered} / ${other}`); // Gesperrter Eintrag bleibt unmarkiert und löst nichts aus active = 0; popup.refresh(); await wait(400); list = await items(); const cancel = list.find((i) => i.text.includes('abbrechen')); mouse('mouseMove', mid(cancel)); await wait(300); check('Gesperrter Eintrag wird nicht markiert', cancel.disabled && (await bg('abbrechen')) === 'rgba(0, 0, 0, 0)'); mouse('mouseDown', mid(cancel), { button: 'left', clickCount: 1 }); mouse('mouseUp', mid(cancel), { button: 'left', clickCount: 1 }); await wait(400); check('Klick auf gesperrten Eintrag löst keine Aktion aus', log.length === 0 && popup.isVisible()); // Klick führt die Aktion aus und schließt das Fenster const open = list.find((i) => i.text.includes('Fenster öffnen')); mouse('mouseMove', mid(open)); mouse('mouseDown', mid(open), { button: 'left', clickCount: 1 }); mouse('mouseUp', mid(open), { button: 'left', clickCount: 1 }); await wait(600); check('Klick führt die Aktion aus und schließt das Menü', log.join() === 'open' && !popup.isVisible()); // Esc schließt popup.show(); await wait(900); const reopened = popup.isVisible(); const win = popup.getWindow(); win.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Escape' }); win.webContents.sendInputEvent({ type: 'keyUp', keyCode: 'Escape' }); await wait(500); check('Esc schließt das Menü', reopened && !popup.isVisible()); } catch (e) { check('Menüfenster-Test ohne Ausnahme', false, e.message); } popup.destroy(); app.exit(failed ? 1 : 0); });