import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import vm from 'node:vm'; const read = name => readFileSync(new URL(name, import.meta.url), 'utf8'); const catalog = vm.createContext({}); vm.runInContext(read('popoutCatalog.js').replace(/^\.pragma library\s*/, ''), catalog); const data = vm.createContext({ PopoutCatalog: catalog }); vm.runInContext(read('diagnostics.js').replace(/^\.(?:pragma|import).*$/gm, ''), data); const plain = value => JSON.parse(JSON.stringify(value)); const sentinel = 'SENSITIVE-title-clipboard-vault-account-SSID-prompt-url'; const sensitive = { title: sentinel, text: sentinel, records: [sentinel], account: sentinel, ssid: sentinel, prompt: sentinel, url: sentinel, error: sentinel, stateJson() { throw new Error('diagnostics must not dump service JSON'); }, }; const surfaces = { ...sensitive, operationId: 17, popoutOpen: true, popoutClosing: false, popoutPhase: 'opening', popoutName: 'clock', popoutOutput: sentinel, lastInteraction: sentinel, lastCloseReason: sentinel, activeLoaderName: sentinel, popoutProgress: 0, screenWidth: 1366, screenHeight: 768, barHeight: 34, orientation: 'horizontal', popoutAnchor: { ...sensitive, x: 10, y: 0, width: 40, height: 34 }, popupCardFrame() { return { ...sensitive, currentRect: { x: 269, y: 16, width: 828, height: 564 } }; }, }; const screens = [{ ...sensitive, name: sentinel, x: -1366, y: 0, width: 1366, height: 768, devicePixelRatio: 1.25 }]; const services = { niri: { ...sensitive, available: true, ready: true, windows: [sensitive], workspaces: [sensitive, sensitive] }, audio: { ...sensitive, pipewireReady: true }, network: { ...sensitive, wifiEnabled: true, scanning: false }, vault: { ...sensitive, status: 'locked', lockPending: false }, noko: { ...sensitive, ready: true, overlayState: '', installing: false }, }; const snapshot = plain(data.snapshot(surfaces, screens, services)); assert.deepEqual(snapshot, { version: 1, popup: { operationId: 17, name: 'clock', phase: 'opening', open: true, closing: false, orientation: 'horizontal', progress: 0, anchor: { x: 10, y: 0, width: 40, height: 34 }, card: { x: 269, y: 16, width: 828, height: 564 }, viewport: { width: 1366, height: 768, barHeight: 34 }, }, displays: [{ x: -1366, y: 0, width: 1366, height: 768, scale: 1.25 }], services: { niri: { available: true, ready: true, windowCount: 1, workspaceCount: 2 }, audio: { ready: true }, network: { enabled: true, scanning: false }, vault: { status: 'locked', lockPending: false }, noko: { ready: true, phase: '', installing: false }, }, }); assert(!JSON.stringify(snapshot).includes(sentinel)); for (const name of Object.keys(catalog.entries)) { surfaces.popoutName = name; assert.equal(data.popup(surfaces).name, name); } for (const name of [sentinel, '__proto__', 'constructor', 'toString']) { surfaces.popoutName = name; surfaces.popoutPhase = sentinel; surfaces.orientation = sentinel; services.vault.status = sentinel; services.noko.overlayState = sentinel; const safe = plain(data.snapshot(surfaces, screens, services)); assert.equal(safe.popup.name, 'unknown'); assert.equal(safe.popup.phase, 'unknown'); assert.equal(safe.popup.orientation, 'unknown'); assert.equal(safe.services.vault.status, 'unknown'); assert.equal(safe.services.noko.phase, 'unknown'); assert(!JSON.stringify(safe).includes(sentinel)); } // Strings in numeric/boolean fields must not become another path for free text. const malformed = plain(data.snapshot({ ...surfaces, operationId: sentinel, popoutOpen: sentinel, popoutProgress: Infinity, screenWidth: sentinel, popoutAnchor: { x: sentinel, y: NaN, width: Infinity, height: -Infinity }, }, [{ x: sentinel, y: NaN, width: Infinity, height: -Infinity, devicePixelRatio: sentinel }], services)); assert.deepEqual(malformed.popup.anchor, { x: 0, y: 0, width: 0, height: 0 }); assert.deepEqual(malformed.displays, [{ x: 0, y: 0, width: 0, height: 0, scale: 0 }]); assert.equal(malformed.popup.operationId, 0); assert.equal(malformed.popup.open, false); assert.equal(malformed.popup.progress, 0); assert(!JSON.stringify(malformed).includes(sentinel)); assert.equal(data.nextOperationId(0), 1); assert.equal(data.nextOperationId(2147483647), 1); assert.equal(data.nextOperationId(41), 42); // Exercise the actual shared lifecycle functions and timer completion, not a model of them. const qml = read('SurfaceManager.qml'); const logs = []; const context = vm.createContext({ ...surfaces, operationId: 0, popoutOpen: false, popoutClosing: false, popoutPhase: 'closed', popoutName: '', orientation: 'horizontal', vertical: false, goldenRatio: 1.61803, powerService: null, dismissing() {}, niriService: null, returnWindowId: null, PopoutCatalog: catalog, DiagnosticData: data, console: { info: line => logs.push(JSON.parse(line)) }, Qt: { rect: (x, y, width, height) => ({ x, y, width, height }) }, openTimer: { stop() {}, restart() {} }, unloadTimer: { stop() {} }, }); context.root = context; const start = qml.indexOf(' function parseRect('); vm.runInContext(qml.slice(start, qml.indexOf(' Timer {', start)), context); context.openPopout('clock', '10,0,40,34', sentinel); context.completeOpen(); context.openPopout('system', '10,0,40,34', sentinel); context.closePopout(sentinel); context.completeOpen(); // A stopped/deferred completion cannot emit another record. context.closePopout(sentinel); // No-op closes allocate no operation. assert.deepEqual(logs.map(({ operationId, event }) => [operationId, event]), [ [1, 'opening'], [1, 'opened'], [2, 'switched'], [3, 'closed'], ]); assert.equal(data.popup(context).operationId, 3); context.openPopout(sentinel, '10,0,40,34', sentinel); context.closePopout(sentinel); context.completeOpen(); assert.equal(logs.length, 6); assert.equal(logs[4].name, 'unknown'); context.openPopout('clock', '10,0,40,34', sentinel); context.openPopout('system', '10,0,40,34', sentinel); context.completeOpen(); // Switching already completed the opening and stopped its timer. assert.deepEqual(logs.slice(6).map(({ operationId, event }) => [operationId, event]), [ [6, 'opening'], [7, 'switched'], ]); for (const log of logs) { assert.deepEqual(Object.keys(log), ['version', 'kind', 'operationId', 'event', 'name', 'phase']); assert.equal(log.version, 1); assert.equal(log.kind, 'popup-transition'); assert(!JSON.stringify(log).includes(sentinel)); } assert.equal(data.transition(sentinel, surfaces).event, 'unknown'); assert.match(qml, /onTriggered: root\.completeOpen\(\)/); const composition = read('Diagnostics.qml'); assert.doesNotMatch(composition, /stateJson\(|Quickshell\.exec|Process\s*\{/); for (const type of ['SurfaceManager', 'Niri', 'Audio', 'Network', 'Vault', 'Noko']) assert.match(composition, new RegExp(`required property ${type} `)); assert.match(read('shell.qml'), /function diagnostics\(\): string\s*\{\s*return diagnosticsService\.snapshot\(\);/); console.log('diagnostics.test.mjs: ALL PASS');