Files
zyt/app/tests/test_patients_infinite.py
T
2026-09-07 12:30:42 +08:00

286 lines
10 KiB
Python

"""Patient workspaces keep resource identity while appending server pages."""
from __future__ import annotations
import os
from typing import Any
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
import pytest
from PySide6.QtCore import QDate, Qt
from PySide6.QtTest import QTest
from PySide6.QtWidgets import QApplication, QCheckBox, QPushButton
from doctor_workstation.core import PermissionSet
from doctor_workstation.ui.pages import patients as patients_module
from doctor_workstation.ui.pages.patients import (
PatientListWorkspace,
PatientOrdersWorkspace,
PatientProgressWorkspace,
PatientsPage,
)
class Repository:
def __init__(self) -> None:
self.calls: list[dict[str, Any]] = []
self.failed_pages: set[int] = set()
self.version = 0
def _list(self, **query: Any) -> dict[str, Any]:
self.calls.append(dict(query))
page = query["page_no"]
if page in self.failed_pages:
raise RuntimeError("连接失败,请重试。")
start = (page - 1) * query["page_size"]
rows = [
{
"id": index + 100,
"diagnosis_id": index + 1000,
"patient_id": index + 2000,
"appointment_id": index + 3000,
"order_id": index + 4000,
"patient_name": f"患者 {index:02d} · {query.get('keyword', '')}{self.version}",
"appointment_status": 1,
"prescription_audit_status": 0,
"payment_slip_audit_status": 0,
"fulfillment_status": 2,
"queue_no": index + 1,
"queue_status": "waiting",
"queue_status_text": "等待中",
}
for index in range(start, min(start + query["page_size"], 37))
]
return {
"lists": rows,
"count": 37,
"extend": {
"scope": {"label": "本人患者"},
"summary": {"orders": 37, "amount": 3700, "today": 37, "waiting": 37},
"schedule_mode": "ownership",
},
}
list_patients = _list
patient_orders = _list
patient_progress = _list
def run_immediately(function: Any, **callbacks: Any) -> object:
try:
result = function()
except Exception as error:
callbacks["on_error"](error)
else:
callbacks["on_success"](result)
finally:
if callbacks.get("on_finished"):
callbacks["on_finished"]()
return object()
@pytest.fixture(scope="module")
def application() -> QApplication:
return QApplication.instance() or QApplication([])
@pytest.fixture(params=["patients", "orders", "progress"])
def workspace(request: Any, application: QApplication, monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr(patients_module, "run_async", run_immediately)
repository = Repository()
permissions = PermissionSet(["*"])
constructors = {
"patients": lambda: PatientListWorkspace(repository, permissions),
"orders": lambda: PatientOrdersWorkspace(repository, permissions),
"progress": lambda: PatientProgressWorkspace(repository),
}
widget = constructors[request.param]()
widget.resize(1280, 900)
widget.show()
application.processEvents()
yield request.param, widget, repository
widget.close()
widget.deleteLater()
application.processEvents()
def table_for(workspace: Any) -> Any:
return getattr(workspace, "table", None) or workspace.queue_table
def settle(application: QApplication) -> None:
application.processEvents()
QTest.qWait(60)
application.processEvents()
def scroll_down(workspace: Any, application: QApplication) -> None:
table = table_for(workspace)
table.verticalScrollBar().setValue(table.verticalScrollBar().maximum())
settle(application)
def test_scrolling_appends_without_duplicates_or_selection_loss(workspace, application):
kind, widget, repository = workspace
widget.refresh()
settle(application)
table = table_for(widget)
assert table.rowCount() == 15
assert widget.pager.height() == 24
assert not hasattr(widget.pager, "page_changed")
assert not any(button.text().isdigit() for button in widget.pager.findChildren(QPushButton))
table.sortItems(0, Qt.SortOrder.DescendingOrder)
table.selectRow(5)
chosen = table.current_data()["id"]
if kind == "patients":
table.cellWidget(5, 0).findChild(QCheckBox).setChecked(True)
scroll_down(widget, application)
assert table.rowCount() == 30
assert table.current_data()["id"] == chosen
if kind == "patients":
selected_row = next(index for index in range(table.rowCount())
if table.item(index, 0).data(Qt.ItemDataRole.UserRole)["id"] == chosen)
assert table.cellWidget(selected_row, 0).findChild(QCheckBox).isChecked()
scroll_down(widget, application)
assert table.rowCount() == 37
assert len({table.item(index, 0).data(Qt.ItemDataRole.UserRole)["id"]
for index in range(table.rowCount())}) == 37
assert [query["page_no"] for query in repository.calls] == [1, 2, 3]
assert all(query["page_size"] == 15 and "page" not in query for query in repository.calls)
assert widget.pager.page == widget._page == 3
assert widget.pager.total == 37
assert not widget.pager.has_more
assert widget.scope == "本人患者"
scroll_down(widget, application)
assert len(repository.calls) == 3
def test_refresh_retains_loaded_prefix_scroll_and_distinct_action_ids(workspace, application):
kind, widget, repository = workspace
widget.refresh()
settle(application)
scroll_down(widget, application)
table = table_for(widget)
table.selectRow(20)
chosen = dict(table.current_data())
scroll = table.verticalScrollBar().value()
repository.version = 1
widget.refresh(silent=True)
assert [query["page_no"] for query in repository.calls] == [1, 2, 1, 2]
assert table.rowCount() == 30
assert table.current_data()["id"] == chosen["id"]
assert table.current_data()["patient_name"].endswith("1")
assert table.verticalScrollBar().value() == scroll
selected = []
if kind == "patients":
widget.diagnosis_requested.connect(lambda row, _edit: selected.append(row))
widget._open_selected_diagnosis()
elif kind == "orders":
widget.detail_requested.connect(selected.append)
widget._request_detail()
else:
widget.diagnosis_requested.connect(selected.append)
widget._open_selected()
assert selected[0]["id"] == chosen["id"]
assert selected[0]["diagnosis_id"] == chosen["diagnosis_id"]
assert selected[0]["diagnosis_id"] != selected[0]["patient_id"]
def test_failed_append_keeps_rows_and_retry_continues_same_page(workspace, application):
_kind, widget, repository = workspace
repository.failed_pages.add(2)
widget.refresh()
settle(application)
scroll_down(widget, application)
table = table_for(widget)
assert table.rowCount() == 15
assert widget.pager.page == 1
assert widget.pager.retry_button.isVisible()
settle(application)
assert [query["page_no"] for query in repository.calls] == [1, 2]
repository.failed_pages.clear()
widget.pager.retry_button.click()
assert table.rowCount() == 30
assert [query["page_no"] for query in repository.calls] == [1, 2, 2]
assert not widget.pager.retry_button.isVisible()
def test_refresh_preserves_horizontal_scroll_at_narrow_width(workspace, application):
_kind, widget, _repository = workspace
widget.resize(760, 600)
widget.refresh()
settle(application)
table = table_for(widget)
horizontal = table.horizontalScrollBar()
assert horizontal.maximum() > 0
horizontal.setValue(horizontal.maximum())
offset = horizontal.value()
widget.refresh(silent=True)
assert horizontal.value() == offset
def test_first_page_failure_keeps_compact_retry_accessible(workspace, application):
_kind, widget, repository = workspace
repository.failed_pages.add(1)
widget.refresh()
settle(application)
widget.scroll.ensureWidgetVisible(widget.pager)
settle(application)
assert table_for(widget).rowCount() == 0
assert widget.pager.isVisible()
assert widget.pager.retry_button.isVisible()
assert widget.pager.retry_button.height() <= widget.pager.height()
repository.failed_pages.clear()
widget.pager.retry_button.click()
assert table_for(widget).rowCount() == 15
def test_filter_change_restarts_and_ignores_old_append(workspace, application, monkeypatch):
kind, widget, repository = workspace
widget.refresh()
settle(application)
pending = []
monkeypatch.setattr(patients_module, "run_async",
lambda function, **callbacks: pending.append((function, callbacks)))
# Capture the new runner for the next scroll request without changing query.
widget.refresh(silent=True)
pending.pop()[1]["on_success"](repository._list(page_no=1, page_size=15))
scroll_down(widget, application)
assert len(pending) == 1
if kind == "progress":
class Tomorrow:
@staticmethod
def currentDate():
return QDate.currentDate().addDays(1)
monkeypatch.setattr(patients_module, "QDate", Tomorrow)
else:
widget.keyword_edit.setText("新条件")
widget.refresh()
assert len(pending) == 2
stale_function, stale_callbacks = pending[0]
current_function, current_callbacks = pending[1]
current_callbacks["on_success"](current_function())
stale_callbacks["on_success"](stale_function())
stale_callbacks["on_error"](RuntimeError("旧请求错误"))
assert table_for(widget).rowCount() == 15
assert widget.pager.page == 1
assert not widget.banner.isVisible()
latest = repository.calls[-2]
assert latest["page_no"] == 1
if kind == "progress":
assert latest["start_date"] == QDate.currentDate().addDays(1).toString("yyyy-MM-dd")
else:
assert latest["keyword"] == "新条件"
def test_patient_route_uses_eight_pixel_bottom_margin(application, monkeypatch):
monkeypatch.setattr(patients_module, "run_async", run_immediately)
page = PatientsPage(Repository(), PermissionSet(["*"]))
assert page.layout().contentsMargins().bottom() == 8
assert page.order_workspace.action_bar.height() == 54
page.close()
page.deleteLater()
application.processEvents()