"""Construct desktop fixtures without touching a running application's resources.""" from pathlib import Path from typing import TypeVar from PyQt6.QtWidgets import QWidget from python_app.gui.controllers.sections.views import SectionView _Widget = TypeVar("_Widget", bound=QWidget) import json import os import tempfile from unittest import mock from PyQt6.QtCore import QCoreApplication, QEvent from python_app.gui.app_window import AppWindow from python_app.gui.controllers.web_controller import WebBridge def isolated_window(add_cleanup) -> AppWindow: temporary = tempfile.TemporaryDirectory(prefix="radar_gui_test_") add_cleanup(temporary.cleanup) root = Path(temporary.name) source = Path(__file__).resolve().parents[2] payload = json.loads((source / "run_config_examples/run_config_simulator.example.json").read_text(encoding="utf-8")) for name, ring in payload["rings"].items(): ring["name"] = f"/{root.name}_{name}" payload["run"]["processing_live_config_path"] = str(root / "python_app/runtime/processing_live.json") payload["control_button"]["enabled"] = False profile = root / "run_config.json" profile.write_text(json.dumps(payload), encoding="utf-8") with mock.patch.dict(os.environ, { "RADAR_SYSTEM_PROFILE": str(profile), "RADAR_SYSTEM_AUTO_START": "0", "RADAR_SYSTEM_AUTO_APPLY_RADAR": "0", }), mock.patch.object(WebBridge, "start_server"): window = AppWindow(root) def destroy_window() -> None: window.close() # Close stops services; Qt must destroy the widget tree before Python GC # can collect the controller callbacks and their graphics objects. window.deleteLater() QCoreApplication.sendPostedEvents(window, QEvent.Type.DeferredDelete) add_cleanup(destroy_window) return window def widget(view: SectionView, name: str, kind: type[_Widget]) -> _Widget: value = view.fields[name] assert isinstance(value, kind), name return value