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

146 lines
4.7 KiB
Python

"""List pages must load without a banner that reflows the page.
Every list refresh used to raise an info banner ("正在加载…") into the page
layout and drop it again on completion. That is two relayouts per refresh - the
filter bar and the whole table jump down and back - and the appointment and
consultation pages poll every five seconds, so the jump repeated on its own with
nobody touching the keyboard.
Errors still surface on the banner; only the routine load is silent.
"""
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.QtWidgets import QApplication
from doctor_workstation.core import PermissionSet
from doctor_workstation.services import DemoDoctorRepository
from doctor_workstation.ui.pages import appointments as appointments_module
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.appointments import AppointmentsPage
from doctor_workstation.ui.pages.prescription_library import PrescriptionLibraryPage
from doctor_workstation.ui.pages.prescriptions import PrescriptionsPage
@pytest.fixture(scope="module")
def application() -> QApplication:
return QApplication.instance() or QApplication([])
@pytest.fixture(autouse=True)
def immediate_async(monkeypatch: pytest.MonkeyPatch) -> None:
"""Run the worker inline so the banner state can be inspected mid-load."""
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: # noqa: BLE001 - mirrors the real worker
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()
for module in (prescriptions_module, library_module, appointments_module):
monkeypatch.setattr(module, "run_async", run_immediately)
def _pages(application: QApplication) -> list[Any]:
del application
repository = DemoDoctorRepository()
return [
PrescriptionsPage(repository, ["*"]),
PrescriptionLibraryPage(
repository, ["*"], SimpleNamespace(id=1, root=1, role_ids=[])
),
AppointmentsPage(repository, PermissionSet(["*"])),
]
def test_a_list_refresh_never_raises_a_loading_banner(
application: QApplication,
) -> None:
seen: list[tuple[str, str]] = []
for page in _pages(application):
banner = page.banner
original = banner.show_message
def record(text: str, kind: str = "info", _page: Any = page) -> None:
seen.append((type(_page).__name__, text))
banner.show_message = record # type: ignore[method-assign]
page.refresh()
application.processEvents()
banner.show_message = original # type: ignore[method-assign]
page.close()
assert seen == [], f"a list refresh still announced itself: {seen}"
def test_a_refresh_does_not_move_anything_on_the_page(
application: QApplication,
) -> None:
"""The real symptom was reflow, so assert on geometry rather than on a flag.
A hidden widget keeps whatever size it was last given, so checking the
banner's own height proves nothing; what matters is that the table below it
does not shift when a refresh runs.
"""
for page in _pages(application):
page.resize(1200, 700)
page.show()
application.processEvents()
table = page.table
settled = (table.mapTo(page, table.rect().topLeft()), table.size())
for _ in range(3):
page.refresh()
application.processEvents()
assert not page.banner.isVisible()
assert (table.mapTo(page, table.rect().topLeft()), table.size()) == settled
page.close()
application.processEvents()
def test_a_failed_load_still_reports_on_the_banner(
application: QApplication,
) -> None:
"""Silence is only for the happy path; failures must stay visible."""
class Failing(DemoDoctorRepository):
def list_prescriptions(self, **_filters: Any) -> Any:
raise RuntimeError("上游不可用")
page = PrescriptionsPage(Failing(), ["*"])
page.resize(1200, 700)
page.show()
page.refresh()
application.processEvents()
assert page.banner.isVisible()
assert page.banner.label.text()
page.close()
application.processEvents()