from __future__ import annotations import os from typing import Any os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") import pytest from PySide6.QtCore import Qt from PySide6.QtWidgets import QApplication, QWidget from doctor_workstation.ui import shell as shell_module from doctor_workstation.ui.shell import NavigationItem, ShellWindow class _ShellPageDouble(QWidget): def __init__( self, _repository: Any, *, permissions: Any, current_user: Any, parent: QWidget | None = None, ) -> None: super().__init__(parent) self.permissions = permissions self.current_user = current_user self.refresh_count = 0 def refresh(self) -> None: self.refresh_count += 1 @pytest.fixture(scope="module") def application() -> QApplication: return QApplication.instance() or QApplication([]) def test_patients_navigation_keeps_the_product_menu_title() -> None: resolved = shell_module._resolve_navigation( [ { "name": "我的患者", "component": "first_visit/my_patients", "perms": "firstvisit.myPatient/lists", } ], {"firstvisit.myPatient/lists"}, demo_mode=False, ) assert [(item.key, title) for item, title in resolved] == [("patients", "我的患者")] def test_appointments_navigation_is_named_reception_and_always_first() -> None: resolved = shell_module._resolve_navigation( [ { "name": "我的患者", "component": "first_visit/my_patients", "perms": "firstvisit.myPatient/lists", "sort": 99, }, { "name": "挂号列表", "component": "tcm/appointment/list", "perms": "doctor.appointment/lists", "sort": 1, }, ], {"doctor.appointment/lists", "firstvisit.myPatient/lists"}, demo_mode=False, ) assert [(item.key, title) for item, title in resolved] == [ ("appointments", "接诊台"), ("patients", "我的患者"), ] @pytest.fixture def shell_window( application: QApplication, monkeypatch: pytest.MonkeyPatch, ) -> ShellWindow: navigation = [ NavigationItem(key, title, glyph, _ShellPageDouble, (permission,)) for key, title, glyph, permission in ( ("appointments", "接诊台", "号", "doctor.appointment/lists"), ("reception", "接诊台", "◎", "doctor.appointment/lists"), ( "prescription_library", "我的处方库", "方", "tcm.prescriptionLibrary/lists", ), ("prescriptions", "已开处方", "笺", "tcm.prescription/lists"), ("patients", "我的患者", "患", "firstvisit.myPatient/lists"), ("consultations", "问诊列表", "询", "tcm.diagnosis/lists"), ) ] monkeypatch.setattr( shell_module, "_resolve_navigation", lambda _menu, _permissions, *, demo_mode: [ (item, item.title) for item in navigation ], ) window = ShellWindow( object(), { "user": {"name": "陈医生", "department_name": "中医门诊", "role_ids": [1]}, "demo_mode": True, }, permissions={item.permissions[0] for item in navigation}, ) window.show() application.processEvents() yield window window.close() application.processEvents() def test_shell_matches_reference_geometry_at_both_acceptance_sizes( application: QApplication, shell_window: ShellWindow, ) -> None: for width, height in ((1024, 640), (1440, 900)): shell_window.resize(width, height) application.processEvents() assert shell_window.sidebar.width() == 179 assert shell_window.topbar.height() == 62 assert shell_window.tabs_host.height() == 0 assert shell_window.workspace.width() == width - 26 - 179 assert shell_window.stack.width() == width - 26 - 179 assert shell_window.stack.height() == height - 26 - 62 assert shell_window.stack.geometry().right() < shell_window.workspace.width() assert shell_window.stack.geometry().bottom() < shell_window.workspace.height() image = shell_window.grab().toImage() assert image.pixelColor(20, 300).name().lower() in { "#f2f5fd", "#f3f6fd", "#f2f6fe", "#f3f6fe", } assert image.pixelColor(610, 20).name().lower() == "#ffffff" assert image.pixelColor(220, 90).name().lower() == "#fcfdfe" def test_registered_pages_are_not_top_level_windows(shell_window: ShellWindow) -> None: for page in shell_window.pages.values(): assert page.parentWidget() is shell_window.stack assert not page.isWindow() def test_reference_shell_has_integrated_search_ai_card_and_window_controls( shell_window: ShellWindow, ) -> None: assert shell_window.windowFlags() & Qt.WindowType.FramelessWindowHint assert ( shell_window.global_search.placeholderText() == "搜索患者姓名、手机号、病历号" ) assert shell_window.assistant_card.isVisible() assert shell_window.assistant_button.text() == "开始对话" assert "服务端自动匹配" in shell_window.model_label.text() assert shell_window.minimize_button.text() == "" assert shell_window.close_button.text() == "" shell_window.set_connection_state(False) assert "离线" in shell_window.assistant_status.text() shell_window.set_connection_state(True) assert "在线" in shell_window.assistant_status.text() def test_every_visible_page_navigates_and_visited_tabs_track_active_page( shell_window: ShellWindow, ) -> None: changed: list[str] = [] shell_window.page_changed.connect(changed.append) for key in ( "reception", "appointments", "prescription_library", "prescriptions", "patients", "consultations", ): assert shell_window.navigate(key) assert shell_window.stack.currentWidget() is shell_window.pages[key] assert shell_window.nav_buttons[key].isChecked() assert shell_window.tab_bar.tabData(shell_window.tab_bar.currentIndex()) == key assert shell_window.visited_tab_keys() == ( "appointments", "reception", "prescription_library", "prescriptions", "patients", "consultations", ) assert changed[-6:] == [ "reception", "appointments", "prescription_library", "prescriptions", "patients", "consultations", ] def test_non_fixed_tabs_close_and_active_close_renavigates( shell_window: ShellWindow, ) -> None: for key in ("patients", "consultations"): assert shell_window.navigate(key) assert not shell_window.close_tab("appointments") assert shell_window.close_tab("patients") assert "patients" not in shell_window.visited_tab_keys() assert ( shell_window.tab_bar.tabData(shell_window.tab_bar.currentIndex()) == "consultations" ) assert shell_window.close_current_tab() assert ( shell_window.tab_bar.tabData(shell_window.tab_bar.currentIndex()) == "appointments" ) assert shell_window.stack.currentWidget() is shell_window.pages["appointments"] assert shell_window.nav_buttons["appointments"].isChecked() shell_window.close_all_tabs() assert shell_window.visited_tab_keys() == ("appointments",) assert shell_window.stack.currentWidget() is shell_window.pages["appointments"] def test_sidebar_collapse_preserves_active_navigation( shell_window: ShellWindow, ) -> None: assert shell_window.navigate("consultations") shell_window.toggle_sidebar() assert shell_window.sidebar.width() == 68 assert shell_window.nav_buttons["consultations"].text() == "" assert shell_window.nav_buttons["consultations"].isChecked() shell_window.toggle_sidebar() assert shell_window.sidebar.width() == 195 assert shell_window.nav_buttons["consultations"].text().endswith("问诊列表") assert shell_window.nav_buttons["consultations"].isChecked() def test_shell_directional_controls_have_no_unicode_arrow_text( shell_window: ShellWindow, ) -> None: assert shell_window.fold_button.text() == "" assert shell_window.refresh_button.text() == "" assert shell_window.fullscreen_button.text() == "" assert shell_window.tabs_menu_button.text() == "" assert all( arrow not in button.text() for button in shell_window.findChildren(QWidget) if hasattr(button, "text") and callable(button.text) for arrow in ("←", "→", "↑", "↓", "▲", "▼", "▴", "▾") )