Dotfiles_ArchAP/tests/test_quickshell_update_center_ui.py
2026-11-09 19:21:49 +01:00

112 lines
6.3 KiB
Python

"""Run the production popup with fake commands; no package operation is reachable."""
import json
import os
from pathlib import Path
import subprocess
import shutil
import tempfile
import unittest
ROOT = Path(__file__).resolve().parents[1]
BASE = ROOT / 'quickshell/.config/quickshell/labfy-sway'
QS = Path.home() / '.local/opt/quickshell-qt612-popup/bin/quickshell'
class UpdateUITests(unittest.TestCase):
def test_popup_states_filters_long_list_and_preview_argv(self):
if not QS.exists() or not os.environ.get('WAYLAND_DISPLAY'):
self.skipTest('QuickShell et session Wayland requis')
with tempfile.TemporaryDirectory(prefix='labfy-updates-ui-') as folder:
p = Path(folder)
(p/'runtime').mkdir(mode=0o700)
shutil.copytree(BASE, p/'config')
# All production Process commands are intercepted, including python3.
tool = '''#!/usr/bin/python3
import os,sys,json
from pathlib import Path
p=Path(os.environ['UPDATE_UI_FIXTURE'])
name=Path(sys.argv[0]).name
with (p/'commands').open('a') as f: f.write(json.dumps([name,*sys.argv[1:]])+'\\n')
if name=='python3':
if sys.argv[-1]=='maintenance': print(json.dumps(dict(time='fixture',history=['transaction completed'],files=['/etc/test.pacnew'],failed=[],errors=[],reboot='Unknown')))
elif sys.argv[-1]=='news': print(json.dumps(dict(items=[dict(title='Manual intervention required',date='today',link='https://archlinux.org/news/test/',manual=True)])))
'''
for name in ('python3', 'kitty', 'systemctl'):
(p/name).write_text(tool)
(p/name).chmod(0o755)
qml = '''import QtQuick
import Quickshell
import "SOURCE" as Production
ShellRoot {
PanelWindow { id: bar; anchors.top: true; anchors.left: true; anchors.right: true; implicitHeight: 30; exclusiveZone: 0; focusable: true }
QtObject { id: fakeIndicator; function reload() { } }
Production.UpdateCenter {
id: center; barWindow: bar; indicator: fakeIndicator
// No input serial exists in this synthetic window; real bar retains grabFocus.
grabFocus: false
updateState: ({repoUpdates: 0, aurUpdates: 0, totalUpdates: 0, packages: [], lastCheck: '', error: ''})
}
function check(value, message) { if (!value) { console.error('FAIL ' + message); Qt.exit(1); } }
function find(item, name) {
if (item.objectName === name) return item;
for (const child of (item.children || [])) { const found = find(child, name); if (found) return found; }
return null;
}
property int step: 0
Timer {
interval: 350; running: true; repeat: true
onTriggered: {
if (step === 0) { center.visible = true; }
else if (step === 1) {
check(center.visible, 'popup opens'); check(center.filtered.length === 0, 'zero');
const rows = []; for (let i=0; i<600; i++) rows.push({name: 'pkg-' + i, installed:'1', available:'2', origin: i%2 ? 'AUR' : 'Arch', sensitive: i===0 ? 'Noyau' : ''});
center.updateState = {repoUpdates:300,aurUpdates:300,totalUpdates:600,packages:rows,lastCheck:'2026-11-08T01:00:00Z',error:''};
} else if (step === 2) {
check(center.filtered.length === 600, 'long list');
const list = find(center.contentItem, 'updatePackageList'); check(list && list.count === 600, 'production list');
list.positionViewAtEnd(); center.origin = 'AUR';
} else if (step === 3) {
check(center.filtered.length === 300, 'AUR filter'); center.query = 'pkg-599';
} else if (step === 4) {
check(center.filtered.length === 1, 'search'); center.origin = 'Arch';
} else if (step === 5) {
check(center.filtered.length === 0, 'Arch filter');
center.updateState = {repoUpdates:0,aurUpdates:0,totalUpdates:0,packages:[],lastCheck:'',error:'network failure',offline:true};
const tabs = find(center.contentItem, 'updateTabs'); check(!!tabs, 'tabs exist'); tabs.currentIndex = 1;
} else if (step === 6) {
check(center.maintenance.files.length === 1, 'pacnew'); check(center.maintenance.history.length === 1, 'history');
find(center.contentItem, 'updateTabs').currentIndex = 2;
} else if (step === 7) {
check(center.news.items.length === 1, 'news'); center.refresh(); center.refresh();
} else if (step === 8) {
center.updateState = {repoUpdates:0,aurUpdates:0,totalUpdates:0,packages:[],lastCheck:'',error:'',checking:true,checkStarted:'2000-01-01T00:00:00Z'};
check(center.staleCheck && !center.checking, 'interrupted check recoverable');
center.visible = false;
} else if (step === 9) { check(!center.visible, 'popup closes'); center.visible = true; }
else if (step === 10) { check(center.visible, 'popup reopens'); center.terminal(true); }
else { console.info('PASS update popup states'); Qt.quit(); }
step++;
}
}
}
'''.replace('SOURCE', 'config/status')
(p/'shell.qml').write_text(qml)
env = dict(os.environ, PATH=f'{p}:/usr/bin', UPDATE_UI_FIXTURE=folder,
QT_QPA_PLATFORM='wayland', WAYLAND_DISPLAY=str(Path(os.environ['XDG_RUNTIME_DIR']) / os.environ['WAYLAND_DISPLAY']), XDG_RUNTIME_DIR=str(p/'runtime'))
result = subprocess.run([str(QS), '-p', str(p), '--no-color'], env=env, capture_output=True, text=True, timeout=20)
logs = result.stdout + result.stderr
self.assertEqual(result.returncode, 0, logs)
self.assertIn('PASS update popup states', logs)
self.assertNotIn('ReferenceError', logs)
self.assertNotIn('TypeError', logs)
commands = [json.loads(line) for line in (p/'commands').read_text().splitlines()]
terminals = [cmd for cmd in commands if cmd[0] == 'kitty']
self.assertEqual(len(terminals), 1, logs)
self.assertEqual(terminals[0][-1], '--dry-run')
self.assertEqual(terminals[0][1:3], ['--class', 'labfy-update-center'])
refreshes = [cmd for cmd in commands if cmd == ['systemctl', '--user', 'start', 'labfy-quickshell-updates.service']]
self.assertEqual(len(refreshes), 1)
if __name__ == '__main__':
unittest.main()