269 lines
9.3 KiB
Python
269 lines
9.3 KiB
Python
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
|