更新
This commit is contained in:
@@ -0,0 +1,285 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user