const assert = require('node:assert/strict') const { app, BrowserWindow, screen, powerMonitor } = require('electron') const { readFileSync } = require('node:fs') const { join } = require('node:path') module.exports = async function venueSmoke({ control, evaluate, state, command, poll, targetDisplay, offlinePackage, powerBlocks, }) { const getDisplays = screen.getAllDisplays.bind(screen) const getPrimary = screen.getPrimaryDisplay.bind(screen) let removed = false screen.getAllDisplays = () => getDisplays().filter((d) => !removed || d.id !== targetDisplay.id) const host = () => evaluate('window.screenBridge.host()') const currentOutput = () => BrowserWindow.getAllWindows().find((w) => w !== control) async function open() { await evaluate(`window.screenBridge.show(${targetDisplay.id})`) await poll(async () => { const s = await state() return s.outputOpen && s.scene.status === 'ready' && s.channels.main.status === 'stopped' }, 'venue open prepared') assert.equal((await host()).sleepBlocked, true) assert.equal(powerBlocks.active.size, 1) assert.equal((await state()).timer.status, 'paused') return currentOutput() } async function start() { await command({ action: 'timer-set', label: 'Аварийная проверка', durationSeconds: 120 }) await command({ action: 'timer-start' }) await command({ action: 'play', channel: 'main' }) await command({ action: 'play', channel: 'music' }) } async function stopped(label) { await poll(async () => !(await state()).outputOpen, label) const s = await state() assert.equal(s.timer.status, 'paused') assert.ok(s.timer.remainingMs > 0 && s.timer.remainingMs <= 120000) assert.equal(s.channels.main.desired, 'stopped') assert.equal(s.channels.music.desired, 'stopped') assert.equal((await host()).sleepBlocked, false) assert.equal(powerBlocks.active.size, 0) assert.ok((await host()).notice.length > 0) assert.ok(!control.isDestroyed() && control.isVisible()) return s } try { await evaluate('window.screenBridge.closeOutput()') await evaluate(`window.screenBridge.selectPackage(${JSON.stringify(offlinePackage.key)})`) // Первый пуск начинается с остановленного таймера; остальные — с аварийной паузы. await evaluate(`window.screenBridge.show(${targetDisplay.id})`) await poll(async () => (await state()).scene.status === 'ready', 'venue first open') await start() const old = currentOutput() removed = true screen.emit('display-removed', {}, targetDisplay) const closed = await stopped('display removed') screen.emit('display-removed', {}, targetDisplay) assert.equal((await state()).session, closed.session) removed = false screen.emit('display-added', {}, targetDisplay) assert.equal((await state()).outputOpen, false) old.emit('ready-to-show') old.emit('leave-full-screen') assert.equal((await state()).outputOpen, false) const remaining = closed.timer.remainingMs let output = await open() assert.equal((await state()).timer.remainingMs, remaining) // Запоздавшие события старого окна не должны закрывать новое. old.emit('closed') assert.equal((await state()).outputOpen, true) await start() screen.emit('display-metrics-changed', {}, targetDisplay, ['workArea']) assert.equal((await state()).outputOpen, true) screen.emit('display-metrics-changed', {}, targetDisplay, ['bounds']) await stopped('resolution changed') for (const metric of ['scaleFactor', 'rotation']) { await open() await start() screen.emit('display-metrics-changed', {}, targetDisplay, [metric]) await stopped(metric) } await open() await start() screen.getPrimaryDisplay = () => ({ ...targetDisplay, workArea: targetDisplay.bounds }) screen.emit('display-metrics-changed', {}, targetDisplay, ['workArea']) await stopped('projector became primary') assert.equal((await host()).preferredDisplayId, null) screen.getPrimaryDisplay = getPrimary screen.emit('display-added', {}, getPrimary()) const bounds = control.getNormalBounds() const work = getPrimary().workArea assert.ok(bounds.x >= work.x && bounds.y >= work.y) assert.ok(bounds.x + bounds.width <= work.x + work.width) assert.ok(bounds.y + bounds.height <= work.y + work.height) output = await open() await start() powerMonitor.emit('suspend') await stopped('system suspend') await assert.rejects(evaluate(`window.screenBridge.show(${targetDisplay.id})`), /пробуждения/) powerMonitor.emit('resume') assert.equal((await state()).outputOpen, false) output = await open() await start() // События fullscreen синтетические; физический переход проверяется отдельно. output.emit('enter-full-screen') output.emit('leave-full-screen') await stopped('fullscreen exited') output = await open() await start() output.webContents.emit('did-fail-load', {}, -2, 'test subresource', '', false) assert.equal((await state()).outputOpen, true) assert.notEqual( output.webContents.getOSProcessId(), control.webContents.getOSProcessId(), 'output renderer must be isolated from control', ) const rendererPid = output.webContents.getOSProcessId() assert.ok(rendererPid > 0 && rendererPid !== process.pid) // SIGKILL даёт настоящий render-process-gone без зависания сборщика crash dump. process.kill(rendererPid, 'SIGKILL') await stopped('renderer crashed') output = await open() await start() output.webContents.emit('did-fail-load', {}, -2, 'test main document', '', true) await stopped('main document failed') output = await open() await start() output.emit('unresponsive') await stopped('window hung') // Обычная кнопка закрытия не меняет прежнюю политику таймера. await open() await start() await evaluate('window.screenBridge.closeOutput()') assert.equal((await state()).timer.status, 'running') powerMonitor.emit('suspend') assert.equal((await state()).timer.status, 'paused') powerMonitor.emit('resume') await open() control.setBounds({ x: work.x + 30, y: work.y + 30, width: Math.min(900, work.width - 30), height: Math.min(650, work.height - 30), }) app.emit('before-quit', {}) assert.equal(powerBlocks.active.size, 0) assert.equal((await host()).sleepBlocked, false) const saved = JSON.parse(readFileSync(join(app.getPath('userData'), 'windows.json'), 'utf8')) assert.equal(saved.display.id, targetDisplay.id) assert.equal(saved.display.label, targetDisplay.label) assert.deepEqual(saved.control.bounds, control.getNormalBounds()) assert.equal(powerBlocks.starts, powerBlocks.stops) console.log( 'SCREEN_VENUE_SMOKE_PASS', JSON.stringify({ blockerStarts: powerBlocks.starts, blockerStops: powerBlocks.stops, rendererCrash: true, incidents: 10, }), ) } finally { screen.getAllDisplays = getDisplays screen.getPrimaryDisplay = getPrimary } }