import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; const read = name => readFileSync(new URL(name, import.meta.url), "utf8"); const host = read("PopoutHost.qml"); for (const [name, panel, dependencies] of [ ["clock", "ClockPanel", { theme: "Theme", timeService: "Time", caldavService: "CalDav" }], ["system", "SystemPanel", { theme: "Theme", resourceService: "Resources" }], ]) { const wiring = host.match(new RegExp(`Component \\{\\s*id: ${name}Content\\s*${panel} \\{([^}]+)\\}\\s*\\}`)); assert(wiring, `${name} delegates to ${panel}`); assert.match(host, new RegExp(`case "${name}":\\s*return ${name}Content;`)); const source = read(`${panel}.qml`); assert.match(source, name === "system" ? /Item\s*\{\s*id: root/ : /Column\s*\{\s*id: root/, `${panel} keeps its host-compatible root`); for (const [property, type] of Object.entries(dependencies)) { assert.match(source, new RegExp(`required property ${type} ${property}\\b`)); assert.match(wiring[1], new RegExp(`${property}: root\\.${property}\\b`)); } assert.match(wiring[1], /width: parent \? parent\.width : 0/, "host retains width binding"); assert.doesNotMatch(source, /childrenRect|\bsurfaces\b|\bbarWindow\b/, "panel has no host geometry dependency"); } assert.doesNotMatch(host, /id: clockCol|id: systemPanel|id: setupForm|id: cardRow/); assert.match(host, /id: removingLoader[\s\S]*sourceComponent: active \? root\.contentComponent\(root\.removingName\) : null/); assert.match(host, /id: activeLoader[\s\S]*sourceComponent: root\.activeName\.length > 0 \? root\.contentComponent\(root\.activeName\) : null/); assert.equal((host.match(/Behavior on opacity/g) || []).length, 3, "both loader fades and the card fade remain"); const clock = read("ClockPanel.qml"); assert.match(clock, /Util\.parseJson\(root\.timeService\.stateJson\(\), \{\}\)/); assert.match(clock, /root\.timeService\.calendar\(localNow\.getFullYear\(\), localNow\.getMonth\(\) \+ 1, localNow\.getDate\(\)\)/); assert.match(clock, /Component\.onCompleted: if \(root\.caldavService\)\s*root\.caldavService\.refresh\(\)/); assert.match(clock, /root\.caldavService\.events/); assert.match(clock, /root\.caldavService\.gridStartDate\(\)/); assert.match(clock, /Ical\.coveredDates/); assert.match(clock, /Ical\.buildSegments\(caldavEvents \|\| \[\], gs\)/); for (const property of ["setupUrlDraft", "setupUsernameDraft", "setupPasswordDraft"]) { assert.match(clock, new RegExp(`text: root\\.caldavService \\? root\\.caldavService\\.${property} : ""`)); assert.match(clock, new RegExp(`root\\.caldavService\\.${property} = text;`)); } assert.match(clock, /root\.caldavService\.saveSetup\(urlField\.text, userField\.text, pwField\.text\)/); assert.match(clock, /secret: true/); assert.match(clock, /root\.caldavService\.secretError/); assert.match(clock, /root\.caldavService\.stale/); assert.equal((clock.match(/Qt\.callLater\(urlField\.field\.forceActiveFocus\)/g) || []).length, 2); assert.match(clock, /height: Math\.max\(70, parent \? parent\.height - y : agendaCol\.height\)/); const system = read("SystemPanel.qml"); assert.match(system, /readonly property var rs: root\.resourceService/); assert.match(system, /readonly property var identity: rs \? rs\.identity/); assert.match(system, /return rs\.issues\(\);/); for (const input of ["failedUnits", "incidents", "cpuTemperatureC", "diskUsage", "updateCount", "memoryUsage", "swapGrowing", "loadOne", "healthPercentage"]) assert.match(system, new RegExp(`rs\\.${input};`), `issues tracks ${input}`); assert.match(system, /Component\.onCompleted: \{\s*if \(rs\)\s*rs\.popoutOpen = true;/); assert.match(system, /Component\.onDestruction: \{\s*if \(rs\)\s*rs\.popoutOpen = false;/); assert.match(system, /root\.rs\.runIssueAction\(modelData\.id\)/); assert.match(system, /root\.rs\.runIssueAgent\(modelData\.id\)/); assert.match(system, /height: parent \? parent\.height : implicitHeight/, "system panel consumes the actual loader height"); assert.match(system, /Flickable\s*\{\s*id: contentScroll[\s\S]*anchors\.fill: parent[\s\S]*contentHeight: contentColumn\.implicitHeight[\s\S]*clip: true/, "long system content scrolls inside the host"); assert.match(system, /onFocusEntered: Qt\.callLater\(contentScroll\.revealItem, issueRow\)/, "keyboard-focused issue actions are revealed"); const issueRow = read("SystemIssueRow.qml"); assert.match(issueRow, /readonly property bool historical: !!issue\.context/); assert.match(issueRow, /attention: !historical && issue\.severity === "warning"/); assert.match(issueRow, /destructive: !historical && issue\.severity === "error"/); assert.match(issueRow, /text: root\.historical \? ""/); assert.match(issueRow, /color: root\.historical \? root\.theme\.ui\.minor/); assert.match(system, /"incident history" : "needs attention"/); assert.match(issueRow, /icon: ""[\s\S]*text: root\.issue\.events \? "open latest crash for " \+ Res\.issueHeading\(root\.issue\) : "open " \+ String\(root\.issue\.label \|\| "issue"\)[\s\S]*showText: false/, "open actions name the group identity and latest-crash scope"); assert.match(issueRow, /icon: "󰨈"[\s\S]*"arrrgh"[\s\S]*accessibleName: root\.issue\.events \? "assess crashes: " \+ Res\.issueHeading\(root\.issue\) : "fix with agent: "/); assert.match(issueRow, /textFormat: Text\.PlainText/, "incident metadata is never interpreted as markup"); assert.match(host, /onAgentDispatched\(\)[\s\S]*closePopout\("agent-dispatched"\)/); assert.match(system, /model: root\.rs \? root\.rs\.processes : \[\]/); assert(system.indexOf('model: root.rs ? root.rs.processes : []') < system.indexOf('model: [false, true]'), 'process chips precede both issue groups'); assert.match(system, /machineW: avail \* 0\.382/); assert.match(system, /liveW: avail \* 0\.618/); assert.equal((system.match(/MetricRow \{/g) || []).length, 6); assert.match(system, /Sys\.formatBytes/); assert.match(system, /Sys\.formatRate/); console.log("panels.test.mjs: ALL PASS");