198 lines
6.7 KiB
Python
198 lines
6.7 KiB
Python
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 Qt
|
|
from PySide6.QtGui import QImage
|
|
from PySide6.QtWidgets import QApplication, QStyleOptionViewItem
|
|
|
|
from doctor_workstation.ui.pages import prescriptions as prescriptions_module
|
|
from doctor_workstation.ui.pages.prescriptions import PrescriptionsPage, _order_warnings
|
|
|
|
|
|
@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)
|
|
|
|
|
|
def row(
|
|
record_id: int,
|
|
number: str,
|
|
*,
|
|
has_order: Any,
|
|
herbs: Any,
|
|
) -> dict[str, Any]:
|
|
return {
|
|
"id": record_id,
|
|
"sn": number,
|
|
"patient_name": "列表提示测试患者",
|
|
"gender": 1,
|
|
"age": 45,
|
|
"prescription_type": "浓缩水丸",
|
|
"is_system_auto": 1,
|
|
"audit_status": 0,
|
|
"void_status": 0,
|
|
"has_prescription_order": has_order,
|
|
"herbs": herbs,
|
|
"doctor_name": "测试医生",
|
|
"assistant_name": "测试医助",
|
|
"create_time": "2026-09-01 09:30:00",
|
|
}
|
|
|
|
|
|
class Repository:
|
|
def __init__(self, rows: list[dict[str, Any]]) -> None:
|
|
self.rows = rows
|
|
|
|
def list_diagnosis_doctors(self) -> list[Any]:
|
|
return []
|
|
|
|
def list_prescriptions(self, **_filters: Any) -> dict[str, Any]:
|
|
return {"lists": self.rows, "count": len(self.rows)}
|
|
|
|
|
|
def test_order_warnings_exactly_match_pc_rules() -> None:
|
|
assert _order_warnings(row(1, "RX-1", has_order=0, herbs=[])) == []
|
|
assert _order_warnings(row(2, "RX-2", has_order=1, herbs=[])) == ["请开方,当前处方药材为空白"]
|
|
assert _order_warnings(
|
|
row(3, "RX-3", has_order="1.0", herbs=[{}, {"name": ""}, {"name": " "}])
|
|
) == ["请开方,当前处方药材为空白"]
|
|
assert _order_warnings(
|
|
row(
|
|
4,
|
|
"RX-4",
|
|
has_order=1,
|
|
herbs=[{"name": "黄芪"}, {"name": " 黄 芪 "}, {"name": "党参"}],
|
|
)
|
|
) == ["已有关联业务订单,当前处方存在重复药材:黄 芪"]
|
|
# The PC implementation only reads the canonical `name` field.
|
|
assert _order_warnings(row(5, "RX-5", has_order=1, herbs=[{"medicine_name": "黄芪"}])) == [
|
|
"请开方,当前处方药材为空白"
|
|
]
|
|
|
|
|
|
def test_number_column_renders_sn_id_and_visible_warning_with_dynamic_height(
|
|
application: QApplication,
|
|
) -> None:
|
|
rows = [
|
|
row(11, "RX-NORMAL", has_order=0, herbs=[]),
|
|
row(22, "RX-BLANK", has_order=1, herbs=[{"name": ""}]),
|
|
row(
|
|
33,
|
|
"RX-DUPLICATE",
|
|
has_order=1,
|
|
herbs=[{"name": "黄芪"}, {"name": "黄 芪"}],
|
|
),
|
|
]
|
|
page = PrescriptionsPage(Repository(rows), {"*"}, SimpleNamespace(id=7, name="测试医生"))
|
|
page.resize(1366, 768)
|
|
page.show()
|
|
page.refresh()
|
|
for _ in range(6):
|
|
application.processEvents()
|
|
|
|
rendered: dict[int, tuple[int, str, str]] = {}
|
|
for visual_row in range(page.table.rowCount()):
|
|
item = page.table.item(visual_row, 1)
|
|
source = item.data(Qt.ItemDataRole.UserRole)
|
|
rendered[source["id"]] = (
|
|
page.table.rowHeight(visual_row),
|
|
item.toolTip(),
|
|
item.data(Qt.ItemDataRole.AccessibleTextRole),
|
|
)
|
|
|
|
normal_height, normal_tip, normal_accessible = rendered[11]
|
|
blank_height, blank_tip, blank_accessible = rendered[22]
|
|
duplicate_height, duplicate_tip, _duplicate_accessible = rendered[33]
|
|
assert normal_tip == normal_accessible == "RX-NORMAL\nID: 11"
|
|
assert "请开方,当前处方药材为空白" in blank_tip == blank_accessible
|
|
assert "已有关联业务订单,当前处方存在重复药材:黄 芪" in duplicate_tip
|
|
assert blank_height > normal_height
|
|
assert duplicate_height > normal_height
|
|
assert page.table.columnWidth(1) == 192
|
|
|
|
image = page.table.viewport().grab().toImage().convertToFormat(QImage.Format.Format_RGB32)
|
|
red_pixels = 0
|
|
for y in range(image.height()):
|
|
for x in range(image.width()):
|
|
color = image.pixelColor(x, y)
|
|
if color.red() > 170 and color.green() < 90 and color.blue() < 100:
|
|
red_pixels += 1
|
|
assert red_pixels > 40
|
|
page.close()
|
|
application.processEvents()
|
|
|
|
|
|
def test_number_warning_survives_sort_and_column_resize(application: QApplication) -> None:
|
|
rows = [
|
|
row(41, "RX-Z", has_order=1, herbs=[]),
|
|
row(42, "RX-A", has_order=0, herbs=[{"name": "黄芪"}]),
|
|
]
|
|
page = PrescriptionsPage(Repository(rows), {"*"}, SimpleNamespace(id=7, name="测试医生"))
|
|
page.resize(1200, 700)
|
|
page.show()
|
|
page.refresh()
|
|
page.table.sortItems(1, Qt.SortOrder.AscendingOrder)
|
|
page.table.setColumnWidth(1, 190)
|
|
for _ in range(5):
|
|
application.processEvents()
|
|
|
|
assert page.table.item(0, 1).text() == "RX-A"
|
|
assert page.table.item(1, 1).text() == "RX-Z"
|
|
assert page.table.item(1, 1).data(Qt.ItemDataRole.UserRole)["id"] == 41
|
|
assert "请开方,当前处方药材为空白" in page.table.item(1, 1).toolTip()
|
|
assert page.table.rowHeight(1) > page.table.rowHeight(0)
|
|
page.close()
|
|
application.processEvents()
|
|
|
|
|
|
def test_number_html_is_escaped_but_remains_readable(application: QApplication) -> None:
|
|
unsafe_number = "RX-<b>NOT HTML</b>"
|
|
page = PrescriptionsPage(
|
|
Repository([row(51, unsafe_number, has_order=1, herbs=[])]),
|
|
{"*"},
|
|
SimpleNamespace(id=7, name="测试医生"),
|
|
)
|
|
page.refresh()
|
|
delegate = page.table.itemDelegateForColumn(1)
|
|
option = QStyleOptionViewItem()
|
|
option.initFrom(page.table)
|
|
index = page.table.model().index(0, 1)
|
|
plain_text = delegate.document(option, index, page.table.columnWidth(1)).toPlainText()
|
|
assert unsafe_number in plain_text
|
|
assert "ID: 51" in plain_text
|
|
assert "请开方,当前处方药材为空白" in plain_text
|
|
page.close()
|
|
application.processEvents()
|