from __future__ import annotations import os from types import SimpleNamespace from typing import Any os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") import pytest from PySide6.QtCore import QEvent, QObject, QSettings from PySide6.QtWidgets import QApplication, QWidget from doctor_workstation.services import DemoDoctorRepository from doctor_workstation.ui.login import LoginWindow from doctor_workstation.ui.shell import ShellWindow from doctor_workstation.ui.widgets import BusyOverlay @pytest.fixture(scope="module") def application() -> QApplication: return QApplication.instance() or QApplication([]) def _visible_windows(application: QApplication) -> list[QWidget]: return [ widget for widget in application.topLevelWidgets() if widget.isWindow() and widget.isVisible() ] def test_busy_overlay_children_are_not_windows(application: QApplication) -> None: host = QWidget() host.resize(360, 240) host.show() application.processEvents() before = {id(widget) for widget in _visible_windows(application)} overlay = BusyOverlay(host, "正在验证账号…") overlay.setVisible(True) application.processEvents() assert overlay.parentWidget() is host assert not overlay.isWindow() assert overlay.progress.objectName() == "BusyOverlayProgress" assert overlay.progress.parentWidget() is overlay assert overlay.label.parentWidget() is overlay assert not overlay.progress.isWindow() assert not overlay.label.isWindow() extra = [ widget for widget in _visible_windows(application) if id(widget) not in before and widget is not host ] assert extra == [] overlay.setVisible(False) host.close() application.processEvents() def test_login_loading_does_not_spawn_extra_windows( application: QApplication, tmp_path: Any ) -> None: settings = QSettings(str(tmp_path / "login.ini"), QSettings.Format.IniFormat) window = LoginWindow( object(), config=SimpleNamespace( api_base_url="https://127.0.0.1:9", request_timeout=30, demo_mode=False, remembered_account="admin", ), settings=settings, ) window.show() application.processEvents() before = {id(widget) for widget in _visible_windows(application)} window._set_loading(True) application.processEvents() extra = [widget for widget in _visible_windows(application) if id(widget) not in before] assert extra == [] assert window.busy_overlay.isVisible() assert not window.busy_overlay.isWindow() assert not window.busy_overlay.progress.isWindow() assert window.busy_overlay.label.text() == "正在验证账号…" window.close() application.processEvents() def test_shell_construction_never_shows_orphan_business_controls( application: QApplication, ) -> None: shown: list[tuple[str, str]] = [] class OrphanShowRecorder(QObject): def eventFilter(self, watched: QObject, event: QEvent) -> bool: # noqa: N802 if ( event.type() == QEvent.Type.Show and isinstance(watched, QWidget) and watched.parentWidget() is None ): text = getattr(watched, "text", lambda: "")() shown.append((type(watched).__name__, str(text))) return False repository = DemoDoctorRepository() session = repository.login(repository.DEMO_ACCOUNT, repository.DEMO_PASSWORD) recorder = OrphanShowRecorder(application) application.installEventFilter(recorder) try: shell = ShellWindow( repository, { "session": session, "user": session.user, "demo_mode": True, }, permissions=session.permissions, ) finally: application.removeEventFilter(recorder) assert shown == [] shell.close() application.processEvents()