from __future__ import annotations import os from pathlib import Path from types import SimpleNamespace from typing import Any os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") import pytest from PySide6.QtCore import QPoint from PySide6.QtGui import QImage from PySide6.QtWidgets import ( QAbstractItemView, QApplication, QComboBox, QFrame, QPushButton, QWidget, ) from doctor_workstation.ui.pages import prescription_library as library_module from doctor_workstation.ui.pages import prescriptions as prescriptions_module from doctor_workstation.ui.pages.prescription_library import PrescriptionLibraryPage from doctor_workstation.ui.pages.prescriptions import PrescriptionsPage from doctor_workstation.ui.widgets import BusinessPager @pytest.fixture(scope="module") def application() -> QApplication: return QApplication.instance() or QApplication([]) @pytest.fixture(autouse=True) def immediate_async(monkeypatch: pytest.MonkeyPatch) -> None: def run_immediately( function: Any, *args: Any, on_success: Any = None, on_error: Any = None, on_finished: Any = None, **kwargs: Any, ) -> object: try: result = function(*args, **kwargs) except Exception as error: if on_error is not None: on_error(error) else: if on_success is not None: on_success(result) finally: if on_finished is not None: on_finished() return object() monkeypatch.setattr(prescriptions_module, "run_async", run_immediately) monkeypatch.setattr(library_module, "run_async", run_immediately) def _issued_row(index: int) -> dict[str, Any]: return { "id": 1000 + index, "sn": f"CF-202608-{1000 + index}", "prescription_type": "汤剂", "is_system_auto": index % 2, "patient_name": ("林晓岚", "周明远", "许安然")[index % 3], "gender": 2 if index % 2 else 1, "age": 29 + index, "audit_status": index % 3, "void_status": 0, "has_prescription_order": index % 2, "creator_id": 7, "doctor_name": "陈医生", "assistant_name": "赵医助", "create_time": f"2026-08-{(index % 9) + 10:02d} 09:30:00", "herbs": [{"name": "黄芪", "dosage": 15}], } def _library_row(index: int) -> dict[str, Any]: return { "id": 2000 + index, "prescription_name": ("益气养阴方", "清热祛湿方", "滋阴调和方")[index % 3], "formula_type": "主方" if index % 3 else "辅方", "herbs": [ {"name": "黄芪", "dosage": 15}, {"name": "党参", "dosage": 12}, ], "efficacy": ("益气养阴", "清热祛湿", "滋阴补肾")[index % 3], "is_public": index % 2, "disable_edit": 0, "creator_id": 7, "creator_name": "陈医生", "create_time": f"2026-08-{(index % 9) + 10:02d} 08:20:00", } class DensityRepository: def __init__(self) -> None: self.issued_rows = [_issued_row(index) for index in range(15)] self.library_rows = [_library_row(index) for index in range(15)] def list_diagnosis_doctors(self) -> list[dict[str, Any]]: return [{"id": 7, "name": "陈医生"}, {"id": 8, "name": "孙医生"}] def list_prescriptions(self, **_filters: Any) -> dict[str, Any]: return {"lists": self.issued_rows, "count": 44} def list_prescription_templates(self, **_filters: Any) -> dict[str, Any]: return {"lists": self.library_rows, "count": 41} def _new_page(kind: str) -> PrescriptionsPage | PrescriptionLibraryPage: repository = DensityRepository() user = SimpleNamespace(id=7, name="陈医生", root=1, role_ids=[0]) permissions = {"*"} if kind == "issued": page: PrescriptionsPage | PrescriptionLibraryPage = PrescriptionsPage( repository, permissions, user ) else: page = PrescriptionLibraryPage(repository, permissions, user) page.refresh() return page def _settle(application: QApplication) -> None: for _ in range(5): application.processEvents() def _fully_visible_rows(page: PrescriptionsPage | PrescriptionLibraryPage) -> int: viewport = page.table.viewport() return sum( 1 for row in range(page.table.rowCount()) if ( (item := page.table.item(row, 0)) is not None and (rect := page.table.visualItemRect(item)).isValid() and rect.top() >= 0 and rect.bottom() < viewport.height() ) ) def test_business_pager_is_shared_fixed_and_not_a_fake_dropdown( application: QApplication, ) -> None: pager = BusinessPager(15) pager.update_state(2, 44) pager.show() _settle(application) assert prescriptions_module.BusinessPager is BusinessPager assert 40 <= pager.height() <= 44 assert pager.minimumHeight() == pager.maximumHeight() == 42 assert pager.findChildren(QComboBox) == [] assert pager.page_size_label.text() == "15 条/页" margins = pager.layout().contentsMargins() assert (margins.left(), margins.top(), margins.right(), margins.bottom()) == (16, 4, 16, 4) assert pager.page_label is not None and pager.page_label.text() == "2" pager.close() @pytest.mark.parametrize("kind", ["issued", "library"]) @pytest.mark.parametrize( ("size", "minimum_visible_rows"), [((1366, 768), 6), ((1710, 920), 9)], ) def test_desktop_sizes_keep_rows_and_pager_visible_and_aligned( application: QApplication, kind: str, size: tuple[int, int], minimum_visible_rows: int, ) -> None: page = _new_page(kind) page.resize(*size) page.show() _settle(application) header = page.findChild(QWidget, "PageHeader") toolbar_name = "PrescriptionToolbar" if kind == "issued" else "PrescriptionLibraryToolbar" toolbar = page.findChild(QFrame, toolbar_name) assert header is not None and 60 <= header.height() <= 64 assert toolbar is not None and 44 <= toolbar.height() <= 48 assert 40 <= page.pager.height() <= 44 assert page.pager.minimumHeight() == page.pager.maximumHeight() assert page.table.minimumHeight() == 0 assert page.table.horizontalScrollMode() == QAbstractItemView.ScrollMode.ScrollPerPixel assert page.table.verticalScrollMode() == QAbstractItemView.ScrollMode.ScrollPerPixel assert _fully_visible_rows(page) >= minimum_visible_rows pager_position = page.pager.mapTo(page, QPoint()) assert pager_position.x() >= 0 assert pager_position.x() + page.pager.width() <= page.width() assert pager_position.y() >= 0 assert pager_position.y() + page.pager.height() <= page.height() page_size_right = page.pager.page_size_label.mapTo(page, QPoint()).x() + ( page.pager.page_size_label.width() ) assert page_size_right <= page.width() pager_margins = page.pager.layout().contentsMargins() toolbar_margins = toolbar.layout().contentsMargins() assert pager_margins.left() == toolbar_margins.left() == 16 assert pager_margins.right() == toolbar_margins.right() == 16 if kind == "issued": filters = page.findChild(QFrame, "PrescriptionFilterBar") assert filters is not None and 84 <= filters.height() <= 92 actions_host = page.table.cellWidget(0, 2) assert actions_host is not None row_edit = next( button for button in actions_host.findChildren(QPushButton) if button.accessibleName() == "编辑处方" ) edit_top_left = row_edit.mapTo(page.table.viewport(), row_edit.rect().topLeft()) edit_bottom_right = row_edit.mapTo( page.table.viewport(), row_edit.rect().bottomRight() ) assert row_edit.text() == "编辑" assert page.table.viewport().rect().contains(edit_top_left) assert page.table.viewport().rect().contains(edit_bottom_right) else: filters = page.findChild(QFrame, "PrescriptionLibraryFilterBar") assert filters is not None assert page.name_filter.minimumWidth() < 500 filter_right = filters.contentsRect().right() for control in ( page.name_filter, page.formula_filter, page.visibility_filter, page.effect_filter, page.query_button, page.reset_button, ): right = control.mapTo(filters, QPoint()).x() + control.width() assert right <= filter_right page.close() _settle(application) def test_density_reference_artifacts_exist() -> None: root = Path(__file__).resolve().parents[1] expected = { root / "artifacts" / "prescription_list_density" / "prescriptions_1366x768.png": ( 1366, 768, ), root / "artifacts" / "prescription_list_density" / "prescriptions_1710x920.png": ( 1710, 920, ), root / "artifacts" / "prescription_list_density" / "prescription_library_1366x768.png": (1366, 768), root / "artifacts" / "prescription_list_density" / "prescription_library_1710x920.png": (1710, 920), } for path, dimensions in expected.items(): image = QImage(str(path)) assert not image.isNull(), path assert (image.width(), image.height()) == dimensions