173 lines
7.3 KiB
JavaScript
173 lines
7.3 KiB
JavaScript
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
|
|
}
|
|
}
|