更新
This commit is contained in:
@@ -9,7 +9,7 @@ from math import ceil
|
||||
from typing import Any
|
||||
|
||||
from PySide6.QtCore import QDateTime, QModelIndex, QRectF, QSize, Qt
|
||||
from PySide6.QtGui import QColor, QFont, QIcon, QPainter, QPen, QPixmap, QTextDocument, QTextOption
|
||||
from PySide6.QtGui import QColor, QFont, QIcon, QPainter, QPen, QTextDocument, QTextOption
|
||||
from PySide6.QtWidgets import (
|
||||
QComboBox,
|
||||
QDateTimeEdit,
|
||||
@@ -398,7 +398,10 @@ def _order_warnings(row: Any) -> list[str]:
|
||||
return []
|
||||
herbs = get_value(source, "herbs", None)
|
||||
herbs = herbs if isinstance(herbs, (list, tuple)) else []
|
||||
names = [str(get_value(herb, "name", "") or "").strip() for herb in herbs]
|
||||
names = []
|
||||
for herb in herbs:
|
||||
raw_name = get_value(herb, "name", "")
|
||||
names.append(str("" if raw_name is None else raw_name).strip())
|
||||
names = [name for name in names if name]
|
||||
if not names:
|
||||
return ["请开方,当前处方药材为空白"]
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
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) >= 250
|
||||
|
||||
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()
|
||||
Reference in New Issue
Block a user