'use strict' const assert = require('node:assert/strict') const Module = require('node:module') const test = require('node:test') const { EventEmitter } = require('node:events') // displays.js 顶层 require('electron'),而开发机上的 electron 包没有下载 dist,直接 require 会抛。 // 这里在加载它之前把 electron 换成桩:本文件只验证「窗口模式 ↔ URL 里的 windowed」这条不变式, // 不需要真窗口,也跑不了真的窗口级全屏(那只能在 Windows 实机上验)。 class FakeWebContents extends EventEmitter { constructor() { super() this.url = '' this.sent = [] this.loadCount = 0 } send(channel, payload) { this.sent.push([channel, payload]) } loadURL(url) { this.url = url this.loadCount += 1 this.emit('did-finish-load') return Promise.resolve() } getURL() { return this.url } reload() { this.loadCount += 1 this.emit('did-finish-load') } } let created = [] class FakeBrowserWindow extends EventEmitter { constructor(options) { super() this.options = options this.webContents = new FakeWebContents() this.destroyed = false this.kiosk = options.kiosk === true this.fullScreen = false // 模拟真实 Electron 在切换瞬间回读到的旧值:设了它 isFullScreen() 就返回这个 this.staleFullScreen = undefined this.bounds = { x: options.x, y: options.y, width: options.width, height: options.height } this.shown = false created.push(this) } // Electron 的 BrowserWindow.loadURL 是 webContents.loadURL 的快捷方式,displays.js 用的是前者 loadURL(url) { return this.webContents.loadURL(url) } isDestroyed() { return this.destroyed } destroy() { this.destroyed = true this.emit('closed') } isFullScreen() { return this.staleFullScreen === undefined ? this.fullScreen : this.staleFullScreen } setFullScreen(value) { this.fullScreen = value } isKiosk() { return this.kiosk } setKiosk(value) { this.kiosk = value } getBounds() { return this.bounds } setBounds(bounds) { this.bounds = bounds } setIgnoreMouseEvents(value) { this.ignoreMouse = value } show() { this.shown = true } focus() {} isMinimized() { return false } restore() {} } const fakeScreen = { displays: [], primaryId: 1, getAllDisplays() { return this.displays }, getPrimaryDisplay() { return this.displays.find((item) => item.id === this.primaryId) || this.displays[0] }, on() {}, removeListener() {} } const electronStub = { BrowserWindow: FakeBrowserWindow, screen: fakeScreen } const originalLoad = Module._load Module._load = function (request, parent, isMain) { if (request === 'electron') return electronStub return originalLoad.call(this, request, parent, isMain) } const { DisplayManager } = require('./displays') Module._load = originalLoad const WIDTH = 1920 const HEIGHT = 1080 function fakeDisplay(id, { internal = false } = {}) { return { id, label: `显示器 ${id}`, internal, bounds: { x: 0, y: 0, width: WIDTH, height: HEIGHT }, scaleFactor: 1 } } function makeManager(t, { selection = 'all', kiosk = true } = {}) { fakeScreen.displays = [] fakeScreen.primaryId = 1 created = [] const log = { lines: [], push(_scope, text) { this.lines.push(text) } } const manager = new DisplayManager({ values: { display: { selection, displays: [], pages: ['standings', 'medals'], pageParam: 'page', kiosk, ignoreMouse: true, reloadEveryHours: 0, backgroundColor: '#000000' } }, server: { host: '0.0.0.0', port: 19540 }, log }) t.after(() => manager.closeAll()) return manager } /** * 唯一不变式:窗口的几何/模式、页面 URL 里的 windowed 参数必须一致。 * 这条断言在每个用例里都跑,任何一种脱钩都会在这里炸出来。 */ function assertModeConsistent(manager, displayId, expectedSingle) { const entry = manager.windows.get(displayId) assert.ok(entry, `display=${displayId} 上应该有窗口`) assert.equal(entry.single, expectedSingle, `entry.single 应为 ${expectedSingle}`) assert.equal(entry.win.isKiosk(), expectedSingle === false, 'kiosk 标记应与 entry.single 一致') const url = new URL(entry.win.webContents.getURL()) assert.equal(url.pathname, '/display') assert.equal( url.searchParams.get('windowed'), expectedSingle ? '1' : null, `URL 的 windowed 应与 entry.single 一致:${url.href}` ) const bounds = entry.win.getBounds() assert.equal(bounds.width, expectedSingle ? Math.round(WIDTH * 0.8) : WIDTH, '窗口宽度应与模式一致') assert.equal(bounds.height, expectedSingle ? Math.round(HEIGHT * 0.8) : HEIGHT, '窗口高度应与模式一致') return entry } test('单屏排布:窗口化窗口的 URL 带 windowed=1', (t) => { const manager = makeManager(t) fakeScreen.displays = [fakeDisplay(1, { internal: true })] manager.enable() const entry = assertModeConsistent(manager, 1, true) assert.equal(entry.manual, false) }) test('多屏排布:kiosk 窗口的 URL 不带 windowed', (t) => { const manager = makeManager(t) fakeScreen.displays = [fakeDisplay(1, { internal: true }), fakeDisplay(2)] manager.enable() assertModeConsistent(manager, 1, false) assertModeConsistent(manager, 2, false) }) test('回归:窗口化窗口被排布改成 kiosk 时,URL 一并换成不带 windowed 并重载', (t) => { const manager = makeManager(t) fakeScreen.displays = [fakeDisplay(1, { internal: true })] manager.enable() const before = assertModeConsistent(manager, 1, true) const loadsBefore = before.win.webContents.loadCount assert.match(before.win.webContents.getURL(), /windowed=1/) // 插上第二块屏,整机从「单屏窗口化」变成「多屏 kiosk」 fakeScreen.displays = [fakeDisplay(1, { internal: true }), fakeDisplay(2)] manager.layout() const after = assertModeConsistent(manager, 1, false) assert.equal(after.win, before.win, '同一个窗口应当就地改模式,而不是重建') assert.equal(after.win.webContents.loadCount, loadsBefore + 1, '模式变了必须重载一次 URL') assert.doesNotMatch(after.win.webContents.getURL(), /windowed=1/) }) test('回归反向:kiosk 窗口被排布改成窗口化时,URL 补上 windowed=1', (t) => { const manager = makeManager(t) fakeScreen.displays = [fakeDisplay(1, { internal: true }), fakeDisplay(2)] manager.enable() const before = assertModeConsistent(manager, 1, false) const loadsBefore = before.win.webContents.loadCount // 拔掉扩展屏,只剩主屏 → 退化成窗口化 fakeScreen.displays = [fakeDisplay(1, { internal: true })] manager.layout() const after = assertModeConsistent(manager, 1, true) assert.equal(after.win, before.win, '同一个窗口应当就地改模式,而不是重建') assert.equal(after.win.webContents.loadCount, loadsBefore + 1, '模式变了必须重载一次 URL') assert.match(after.win.webContents.getURL(), /windowed=1/) }) test('管理面板手动开的窗口保持自己的模式,不被自动排布改成 kiosk', (t) => { const manager = makeManager(t) const primary = fakeDisplay(1, { internal: true }) fakeScreen.displays = [primary] const opened = manager.openOn(primary, { page: 'standings', single: true }) assert.deepEqual(opened, { created: true, mode: 'windowed' }) assertModeConsistent(manager, 1, true) // 之后整机变成多屏,自动排布也不许把这个手动开的小窗顶成 kiosk fakeScreen.displays = [primary, fakeDisplay(2)] manager.layout() assertModeConsistent(manager, 1, true) assertModeConsistent(manager, 2, false) }) test('模式上报以 entry 为准:窗口化窗口被 F11 全屏后仍报 windowed', (t) => { const manager = makeManager(t) const primary = fakeDisplay(1, { internal: true }) fakeScreen.displays = [primary] manager.openOn(primary, { single: true }) const win = manager.windows.get(1).win win.setFullScreen(true) // 只聚焦不重建时,模式必须还说窗口化:拿 isFullScreen() 反推会误报成 kiosk assert.deepEqual(manager.openOn(primary, { single: true }), { created: false, mode: 'windowed' }) assert.equal(manager.listDisplays().displays.find((item) => item.id === 1).mode, 'windowed') assert.equal(manager.modeOf(manager.windows.get(1)), 'windowed') // kiosk 窗口不受 F11 影响,仍报 kiosk fakeScreen.displays = [primary, fakeDisplay(2)] manager.layout() assert.equal(manager.listDisplays().displays.find((item) => item.id === 2).mode, 'kiosk') }) test('F11 下发的是算出来的确定值,不回读窗口', (t) => { const manager = makeManager(t) fakeScreen.displays = [fakeDisplay(1, { internal: true })] manager.enable() const win = manager.windows.get(1).win const [handler] = win.webContents.listeners('before-input-event') assert.ok(handler, '窗口化窗口必须挂 F11 处理') let prevented = 0 const pressF11 = () => handler({ preventDefault: () => { prevented += 1 } }, { type: 'keyDown', key: 'F11' }) win.webContents.sent.length = 0 pressF11() assert.equal(prevented, 1) assert.equal(win.isFullScreen(), true) assert.deepEqual(win.webContents.sent, [['window:fullscreen-changed', true]]) win.webContents.sent.length = 0 pressF11() assert.equal(win.isFullScreen(), false) assert.deepEqual(win.webContents.sent, [['window:fullscreen-changed', false]]) // 切换瞬间回读可能拿到旧值:enter/leave 必须下发事件本身的确定值 win.webContents.sent.length = 0 win.staleFullScreen = false win.emit('enter-full-screen') assert.deepEqual(win.webContents.sent, [['window:fullscreen-changed', true]]) win.webContents.sent.length = 0 win.staleFullScreen = true win.emit('leave-full-screen') assert.deepEqual(win.webContents.sent, [['window:fullscreen-changed', false]]) win.staleFullScreen = undefined // 加载完成那次窗口状态已经稳定,继续用「读一次」 win.webContents.sent.length = 0 win.fullScreen = true win.webContents.emit('did-finish-load') assert.deepEqual(win.webContents.sent, [['window:fullscreen-changed', true]]) }) test('kiosk 窗口不挂 F11 处理', (t) => { const manager = makeManager(t) fakeScreen.displays = [fakeDisplay(1, { internal: true }), fakeDisplay(2)] manager.enable() assert.equal(manager.windows.get(2).win.webContents.listeners('before-input-event').length, 0) })