'use strict'; const assert = require('assert'); const fs = require('fs'); const controls = ['PopupSurface', 'PopupSection', 'PopupCard', 'PopupButton', 'PopupBadge', 'PopupDivider', 'PopupSlider', 'PopupTextField', 'PopupToolTip']; for (const control of controls) assert(fs.existsSync(`${control}.qml`), `${control}.qml must exist`); const host = fs.readFileSync('PopoutHost.qml', 'utf8'); assert.strictEqual((host.match(/PopupSurface\s*\{/g) || []).length, 1, 'host has exactly one popup surface'); for (const control of ['PopupSurface', 'PopupSection', 'PopupCard']) assert(!/childrenRect/.test(fs.readFileSync(`${control}.qml`, 'utf8')), `${control} avoids container binding loops`); const popupCard = fs.readFileSync('PopupCard.qml', 'utf8'); const popupButton = fs.readFileSync('PopupButton.qml', 'utf8'); assert(/PopupButton\s*\{/.test(popupCard), 'cards share the button activation route'); assert(/enabled:\s*root\.interactive && root\.enabled/.test(popupButton), 'disabled cards reject pointer activation through the shared native button'); assert(/activeFocusOnTab:\s*interactive && enabled/.test(popupButton), 'disabled cards reject keyboard traversal through the shared button'); assert(/Accessible\.onPressAction:\s*if \(interactive && enabled\)/.test(popupButton), 'disabled cards reject accessible activation'); const popupTextField = fs.readFileSync('PopupTextField.qml', 'utf8'); assert(/T\.TextField\s*\{[\s\S]*?clip:\s*true/.test(popupTextField), 'shared native text input clips long text'); assert(/anchors\.fill:\s*parent[\s\S]*?leftPadding:/.test(popupTextField), 'field hit target includes icon and padding'); assert(/visible:\s*input\.text\.length === 0/.test(popupTextField) && !/&& !input\.activeFocus/.test(popupTextField), 'empty focused fields retain their hint'); assert(/T\.Button\s*\{/.test(popupButton) && /autoRepeat:\s*false/.test(popupButton), 'buttons use native activation without command repeat'); assert(/event\.isAutoRepeat/.test(popupButton), 'supplemental Enter activation also guards repeat events'); assert(/root\.visualFocus \|\| \(root\.hovered && !root\.activeFocus\)/.test(popupButton), 'tooltip focus route excludes persistent pointer-only focus'); const popupSlider = fs.readFileSync('PopupSlider.qml', 'utf8'); assert(/T\.Slider\s*\{/.test(popupSlider), 'slider delegates input and handle dragging to Qt'); assert(/signal moved\(real value\)/.test(popupSlider), 'slider retains the moved(real) caller contract'); assert(/value:\s*root\.boundedValue/.test(popupSlider) && /onMoved:\s*root\.moved\(value\)/.test(popupSlider), 'slider binds external state and only forwards user movement'); assert(!/onValueChanged/.test(popupSlider), 'external slider updates do not dispatch commands'); const popupToolTip = fs.readFileSync('PopupToolTip.qml', 'utf8'); assert(/required property Theme theme/.test(popupToolTip) && /wrapMode:\s*Text\.Wrap/.test(popupToolTip), 'tooltips use a typed Nugu theme and wrap'); assert(/textFormat:\s*Text\.PlainText/.test(popupToolTip), 'tooltips never interpret service text as markup'); // These are source contracts, not interaction evidence. ui-test.qml exposes // native pressed/focus/geometry and write counts for the nested input driver. const fixture = fs.readFileSync('ui-test.qml', 'utf8'); for (const probe of ['buttonPressed', 'sliderPressed', 'sliderThumb', 'fieldHintVisible', 'cardWrites', 'nestedWrites', 'iconTooltipVisible', 'disabledWrites']) assert(fixture.includes(probe), `nested fixture exposes ${probe}`); const notificationCard = fs.readFileSync('NotificationCard.qml', 'utf8'); assert(/iconSource:\s*actionBtn\.actIcon/.test(notificationCard), 'notification actions retain advertised icons'); assert(!/height:\s*3\s*\n\s*color:.*ui\.accent/.test(fs.readFileSync('LauncherPanel.qml', 'utf8')), 'launcher accent removed'); assert(!/left accent bar/.test(fs.readFileSync('AudioPanel.qml', 'utf8')), 'audio accent strips removed'); const keybinds = fs.readFileSync('../../niri/cfg/keybinds.kdl', 'utf8'); assert(/Mod\+Escape[^}]*notificationsDismissToasts/s.test(keybinds), 'super+escape dismisses notification toasts'); const bluetoothService = fs.readFileSync('Bluetooth.qml', 'utf8'); const connectivityPanel = fs.readFileSync('ConnectivityPanel.qml', 'utf8'); assert(/signal refreshed/.test(bluetoothService) && /refreshTimer\.restart\(\)/.test(bluetoothService), 'bluetooth actions schedule async refreshes'); assert(/target:\s*root\.bluetoothService[\s\S]*onRefreshed/.test(connectivityPanel), 'open connectivity popup follows bluetooth refreshes'); const trayService = fs.readFileSync('Tray.qml', 'utf8'); assert(/item\.onlyMenu[\s\S]*displayMenu/.test(trayService), 'only-menu tray items open their menu'); assert(/item\.display\(/.test(trayService), 'tray service exposes native menu display'); assert(/item\.secondaryActivate\(\)/.test(trayService), 'tray service exposes secondary activation'); const trayPanel = fs.readFileSync('TrayPanel.qml', 'utf8'); assert(/interactive:\s*true/.test(trayPanel) && /onClicked:\s*activate\(\)/.test(trayPanel) && /root\.primary\(rowData\.id/.test(trayPanel), 'tray cards retain primary pointer and keyboard activation through the shared card'); assert(/acceptedButtons:\s*Qt\.MiddleButton \| Qt\.RightButton/.test(trayPanel) && /event\.button === Qt\.RightButton/.test(trayPanel) && /root\.secondary\(itemCell\.rowData\.id/.test(trayPanel), 'tray cards retain both documented secondary pointer routes'); assert(/QsMenuAnchor\s*\{[\s\S]*root\.trayService\.menuFor/.test(trayPanel), 'tray right click uses a native menu anchor'); const panels = ['ConnectivityPanel', 'AudioPanel', 'TrayPanel', 'NotificationGroup', 'NotificationCard', 'ClipboardPanel', 'PowerPanel', 'BrightnessPanel', 'MediaPanel', 'NokoPanel', 'LauncherPanel']; for (const panel of panels) { const source = fs.readFileSync(`${panel}.qml`, 'utf8'); assert(/Popup(?:Section|Card|Button|Badge|Divider|Slider|TextField)\s*\{/.test(source), `${panel} uses shared popup controls`); assert(!/text:\s*"[A-Z]/.test(source), `${panel} authored labels are lowercase`); } for (const panel of ['AudioPanel', 'BrightnessPanel', 'MediaPanel']) assert(/PopupSlider\s*\{/.test(fs.readFileSync(`${panel}.qml`, 'utf8')), `${panel} uses the shared slider`); for (const panel of ['ConnectivityPanel', 'MediaPanel', 'NokoPanel']) assert(/PopupBadge\s*\{/.test(fs.readFileSync(`${panel}.qml`, 'utf8')), `${panel} uses shared status badges`); const nokoPanel = fs.readFileSync('NokoPanel.qml', 'utf8'); assert(/updateBadge\.width - parent\.spacing \* 2/.test(nokoPanel), 'noko update badge reserves both row gaps'); for (const panel of ['ClipboardPanel', 'ConnectivityPanel', 'LauncherPanel']) assert(/PopupTextField\s*\{/.test(fs.readFileSync(`${panel}.qml`, 'utf8')), `${panel} uses the shared text field`); console.log('popup-controls.test.js: ALL PASS');