Files
javis/tests/_retheme_check.py
T
2026-10-04 15:31:44 +02:00

125 lines
3.7 KiB
Python

"""Offscreen functional check for the GUI retheme registry.
Single thread (Qt widgets must be driven on their creating thread):
- Patch the modal startup dialog to return the config immediately.
- Patch ``qasync.QEventLoop.run_forever`` to grab the top-level window and raise
a sentinel BEFORE the GUI loop actually runs.
- Call ``run_gui``, catch the sentinel (window is now fully constructed), then
read a registered themed widget's style in dark, trigger the REAL light/dark
toggle (``_theme_act`` -> ``_on_theme_toggle`` -> ``_apply_theme`` ->
``retheme``), and assert the style changed and round-trips.
Run: QT_QPA_PLATFORM=offscreen .\\.venv\\Scripts\\python.exe tests\\_retheme_check.py
"""
from __future__ import annotations
import os
import sys
from pathlib import Path
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
from PySide6 import QtWidgets # noqa: E402
import qasync # noqa: E402
from javis.core.config import load_config # noqa: E402
import javis.gui.main_window as mw # noqa: E402
class _Stop(Exception):
pass
STATE: dict = {"win": None}
def _probe(win) -> None:
"""Drive the window: capture dark, toggle to light, assert it changed."""
if not getattr(win, "_themed", None):
raise AssertionError("retheme registry (_themed) is empty!")
sample = None
for w, factory in win._themed:
try:
factory()
except Exception:
continue
sample = (w, factory)
break
if sample is None:
w, factory = win._themed[0]
sample = (w, factory)
w, factory = sample
app = QtWidgets.QApplication.instance()
app.processEvents()
dark_style = w.styleSheet()
print(f"[probe] themed widget: {type(w).__name__}")
print(f"[probe] dark style = {dark_style!r}")
act = win._theme_act
act.setChecked(True) # -> light
app.processEvents()
light_style = w.styleSheet()
print(f"[probe] light style = {light_style!r}")
assert light_style != dark_style, (
"BUG: toggling theme did not change the registered widget's stylesheet. "
f"dark={dark_style!r} light={light_style!r}"
)
act.setChecked(False) # -> dark
app.processEvents()
back_dark = w.styleSheet()
print(f"[probe] dark (again) = {back_dark!r}")
assert back_dark == dark_style, (
f"BUG: toggling back did not restore the dark style. "
f"{back_dark!r} != {dark_style!r}"
)
print("\n[probe] OK — retheme registry is theme-reactive (dark->light->dark round-trip).")
# GUI-01 regression: voice-profile cards used undefined names.
card = win._make_voice_card(
{"name": "Alice", "clip_count": 3, "enrolled_at": "2026-01-01T00:00:00"})
assert card.layout() is not None and card.layout().count() == 2, "voice card layout broken"
print("[probe] OK — voice-profile card renders.")
def main() -> int:
app = QtWidgets.QApplication.instance() or QtWidgets.QApplication([])
config = load_config(Path("config.yaml"))
mw.show_startup_dialog = lambda cfg: cfg # type: ignore[assignment]
def _run_forever(self):
tops = [w for w in QtWidgets.QApplication.topLevelWidgets()
if isinstance(w, QtWidgets.QMainWindow) and getattr(w, "retheme", None)]
if tops:
STATE["win"] = tops[-1]
raise _Stop()
qasync.QEventLoop.run_forever = _run_forever # type: ignore[assignment]
try:
mw.run_gui(config)
except _Stop:
pass
if STATE["win"] is None:
print("[FAIL] could not capture the main window")
return 1
try:
_probe(STATE["win"])
except Exception as e: # noqa: BLE001
print(f"[FAIL] {e!r}")
return 1
print("\nRESULT: PASS")
return 0
if __name__ == "__main__":
sys.exit(main())