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.QtTest import QTest from PySide6.QtWidgets import QApplication, QPushButton from doctor_workstation.core import PermissionSet from doctor_workstation.ui.dialogs import ai_consult_picker, diagnosis, prescription @pytest.fixture(scope="module") def application() -> QApplication: return QApplication.instance() or QApplication([]) @pytest.fixture def immediate_async(monkeypatch: pytest.MonkeyPatch) -> None: def run(function: Any, *, on_success: Any, on_error: Any, on_finished: Any = None) -> None: try: result = function() except Exception as error: on_error(error) else: on_success(result) if on_finished: on_finished() for module in (ai_consult_picker, diagnosis, prescription): monkeypatch.setattr(module, "run_async", run) class ListRepository: def __init__(self) -> None: self.calls: list[dict[str, Any]] = [] self.fail_page = 0 def _list(self, **filters: Any) -> dict[str, Any]: self.calls.append(filters) page, size = filters["page_no"], filters["page_size"] if page == self.fail_page: raise RuntimeError("暂时无法加载") start = (page - 1) * size + 1 return { "lists": [ { "id": number, "diagnosis_id": number, "patient_id": 1000 + number, "patient_name": f"患者{number}", "prescription_id": 77, "prescription_name": f"处方{number}", "herbs": [{"name": "白术", "dosage": 10}], "order_no": f"ORDER{number}", } for number in range(start, min(start + size, 46)) ], "count": 45, } list_prescription_templates = _list list_prescription_orders = _list list_ai_patient_options = _list def scroll_to_bottom(table: Any) -> None: scrollbar = table.verticalScrollBar() scrollbar.setValue(scrollbar.maximum()) QTest.qWait(70) @pytest.mark.parametrize("kind", ["template", "order", "ai"]) def test_scroll_appends_and_retains_selected_business_identity( application: QApplication, immediate_async: None, kind: str ) -> None: repository = ListRepository() if kind == "template": dialog = prescription.TemplateImportDialog(repository, 42) controller = dialog.infinite_list elif kind == "order": dialog = prescription.PrescriptionOrderListDialog(repository, prescription_id=77) controller = dialog.infinite_list else: dialog = ai_consult_picker.AiConsultTargetDialog(repository, PermissionSet(["*"])) controller = dialog.pager dialog.resize(980, 520) dialog.show() application.processEvents() try: page_size = controller.page_size assert dialog.table.rowCount() == page_size assert not any( button.text() in {"上一页", "下一页"} for button in dialog.findChildren(QPushButton) ) dialog.table.selectRow(2) scroll_to_bottom(dialog.table) assert [call["page_no"] for call in repository.calls] == [1, 2] assert dialog.table.rowCount() == page_size * 2 assert dialog.table.currentRow() == 2 assert all( dialog.table.item(row, column).data(Qt.ItemDataRole.CheckStateRole) is None for row in range(dialog.table.rowCount()) for column in range(dialog.table.columnCount()) ) repository.fail_page = 3 scroll_to_bottom(dialog.table) assert controller.retry_button.isVisible() assert dialog.table.rowCount() == page_size * 2 repository.fail_page = 0 controller.retry_button.click() assert dialog.table.rowCount() == 45 assert not controller.has_more assert dialog.table.currentRow() == 2 if kind == "template": dialog.accept() assert dialog.selected_template()["id"] == 3 elif kind == "ai": dialog.accept() assert dialog.selected_target().diagnosis_id == 3 assert dialog.selected_target().patient_id == 1003 else: assert dialog.table.item(2, 0).data(Qt.ItemDataRole.UserRole)["id"] == 3 finally: dialog.close() def test_template_query_supersedes_pending_load_and_keeps_creator_scope( application: QApplication, monkeypatch: pytest.MonkeyPatch ) -> None: pending: list[tuple[Any, dict[str, Any]]] = [] monkeypatch.setattr( prescription, "run_async", lambda function, **callbacks: pending.append((function, callbacks)) ) repository = ListRepository() dialog = prescription.TemplateImportDialog(repository, 42) dialog.show() application.processEvents() try: dialog.name_edit.setText("白术") dialog.formula_combo.setCurrentIndex(1) dialog.search() assert len(pending) == 2 latest_function, latest_callbacks = pending[1] latest_callbacks["on_success"](latest_function()) old_function, old_callbacks = pending[0] old_callbacks["on_success"](old_function()) old_callbacks["on_error"](RuntimeError("过期错误")) assert dialog.table.rowCount() == 15 assert not dialog.banner.isVisible() assert repository.calls[0] == { "page_no": 1, "page_size": 15, "prescription_name": "白术", "formula_type": "主方", "prescribing_creator_id": 42, } finally: dialog.close() def test_ai_picker_continues_when_first_page_has_no_valid_diagnosis_targets( application: QApplication, immediate_async: None ) -> None: class Repository(ListRepository): def list_ai_patient_options(self, **filters: Any) -> dict[str, Any]: result = self._list(**filters) if filters["page_no"] == 1: for row in result["lists"]: row["id"] = -row["id"] row["diagnosis_id"] = row["id"] return result repository = Repository() dialog = ai_consult_picker.AiConsultTargetDialog(repository, PermissionSet(["*"])) dialog.show() application.processEvents() try: QTest.qWait(100) assert [call["page_no"] for call in repository.calls] == [1, 2] assert dialog.table.rowCount() == 20 assert dialog.table.item(0, 3).text() == "21" assert dialog.table.isVisible() assert not dialog.empty_state.isVisible() finally: dialog.close() def test_diagnosis_readonly_orders_scroll_and_switching_patient_resets_scope( application: QApplication, immediate_async: None ) -> None: repository = ListRepository() dialog = diagnosis.DiagnosisDialog( repository, permissions=PermissionSet(["tcm.diagnosis/patientOrders"]) ) dialog.open_for( 501, authoritative_detail={"id": 501, "patient_id": 301, "patient_name": "患者一"} ) application.processEvents() try: table = dialog._table_registry["orders"][0] dialog.readonly_scroll.ensureWidgetVisible(table) assert table.rowCount() == 10 assert dialog.orders_list.parentWidget() is dialog._readonly_sections["orders"] table.selectRow(2) scroll_to_bottom(table) assert table.rowCount() == 20 assert table.currentRow() == 2 assert table.item(2, 0).data(Qt.ItemDataRole.CheckStateRole) is None assert repository.calls[-1] == { "page_no": 2, "page_size": 10, "patient_id": 301, "context_diagnosis_id": 501, "scene": "diagnosis_edit", } dialog.open_for( 502, authoritative_detail={"id": 502, "patient_id": 302, "patient_name": "患者二"} ) assert table.rowCount() == 10 assert repository.calls[-1]["page_no"] == 1 assert repository.calls[-1]["patient_id"] == 302 assert repository.calls[-1]["context_diagnosis_id"] == 502 finally: dialog.close()