feat(gui): system-tray applet (M11) + GUI smoke tests — 0.19.0
QSystemTrayIcon applet (gui/tray.py, D13): menu with live CPU/GPU temp + memory used/total, a status line, a Run Diagnostic submenu per detected game, plus Open dashboard / Start-Stop recording / Snapshot-copy / Quit. Reuses the dashboard's sample stream; drives existing MainWindow flows. - MainWindow creates the tray when one is available; closing the window hides to tray (Quit exits); setQuitOnLastWindowClosed(False) so dialogs don't quit it. - app: `--tray` starts hidden for autostart. - tests/test_gui_smoke.py: construct MainWindow headless + exercise the tray, so a startup crash (like the 0.18.0 import bug) fails the build. Skips if no PySide6. - docs: M10/M11 marked done in MODULES/ROADMAP. Completes the Desktop UI bundle (M10 + M11). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
"""GUI smoke tests: construct the real widgets so a startup crash fails the build.
|
||||
|
||||
These run headless (offscreen) and skip cleanly if PySide6 isn't installed (the core/CLI
|
||||
test suite stays Qt-free). Constructing MainWindow is the check that would have caught the
|
||||
0.18.0 bad-import regression that broke launch.
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
import unittest
|
||||
|
||||
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||
|
||||
try:
|
||||
from PySide6.QtGui import QIcon
|
||||
from PySide6.QtWidgets import QApplication, QWidget
|
||||
HAVE_QT = True
|
||||
except ImportError:
|
||||
HAVE_QT = False
|
||||
|
||||
|
||||
@unittest.skipUnless(HAVE_QT, "PySide6 not installed")
|
||||
class GuiSmokeTests(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.app = QApplication.instance() or QApplication([])
|
||||
|
||||
def test_main_window_constructs(self):
|
||||
from unittest import mock
|
||||
|
||||
from rigdoctor.core import updates
|
||||
from rigdoctor.gui import main_window as mw
|
||||
|
||||
# Avoid construction side effects: no pkexec elevation, no network update check.
|
||||
with mock.patch("rigdoctor.core.elevation.available", return_value=False), \
|
||||
mock.patch.object(updates, "update_state", return_value=(updates.UP_TO_DATE, None, "")):
|
||||
window = mw.MainWindow()
|
||||
try:
|
||||
self.assertEqual(len(window._nav_buttons), len(mw._NAV_ITEMS))
|
||||
finally:
|
||||
window._worker.stop()
|
||||
|
||||
def test_tray_readouts_update(self):
|
||||
from rigdoctor.core.sample import Reading, Sample
|
||||
from rigdoctor.gui.tray import TrayIcon
|
||||
|
||||
class StubWindow(QWidget):
|
||||
def show_dashboard(self): ...
|
||||
def show_page(self, name): ...
|
||||
def run_diagnostic(self, name, appid): ...
|
||||
def quit_app(self): ...
|
||||
|
||||
tray = TrayIcon(StubWindow(), QIcon())
|
||||
tray.update_sample(Sample(time.time(), [
|
||||
Reading("gpu", "temp", 72.0, "°C", ""),
|
||||
Reading("cpu", "temp", 65.0, "°C", "Package id 0"),
|
||||
Reading("memory", "used", 14.2, "GB"),
|
||||
Reading("memory", "total", 31.0, "GB"),
|
||||
Reading("memory", "used_pct", 46.0, "%"),
|
||||
]))
|
||||
self.assertIn("72", tray._gpu_act.text())
|
||||
self.assertIn("65", tray._cpu_act.text())
|
||||
self.assertIn("14.2 / 31.0 GB", tray._mem_act.text())
|
||||
self.assertEqual(tray._status_act.text(), "● Normal")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user