Files
zyt/app/tests/test_prescriptions_tech_blue.py
2026-09-10 15:19:17 +08:00

256 lines
11 KiB
Python

"""Native interactions and reachability unique to the issued-prescription blue UI."""
from __future__ import annotations
import os
import socket
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
import pytest
from PySide6.QtCore import QPoint, QRect, Qt, QTimer
from PySide6.QtGui import QIcon
from PySide6.QtTest import QTest
from PySide6.QtWidgets import QAbstractItemView, QApplication, QPushButton
from doctor_workstation.core import PermissionSet
from doctor_workstation.services import DemoDoctorRepository
from doctor_workstation.ui import icons
from doctor_workstation.ui.pages import prescriptions as prescriptions_module
from doctor_workstation.ui.shell import ShellWindow
from doctor_workstation.ui.theme import apply_theme
def _settle(application: QApplication) -> None:
for _ in range(5):
application.processEvents()
def _rect_in(widget, parent) -> QRect:
return QRect(widget.mapTo(parent, QPoint()), widget.size())
def _assert_visible_within(widget, viewport) -> None:
assert widget.isVisible()
rect = _rect_in(widget, viewport)
assert viewport.rect().contains(rect), (widget.objectName(), rect, viewport.rect())
def _record_id(table, row: int) -> int:
return int(table.item(row, 0).data(Qt.ItemDataRole.UserRole).id)
@pytest.fixture(scope="module")
def application() -> QApplication:
app = QApplication.instance() or QApplication([])
apply_theme(app)
return app
@pytest.fixture(autouse=True)
def shell_factory(application: QApplication, monkeypatch: pytest.MonkeyPatch):
network_attempts = []
def reject_network(*_args, **_kwargs):
network_attempts.append("socket")
pytest.fail("Prescription blue interaction tests must use local Demo data only")
def immediate(function, *, on_success=None, on_error=None, on_finished=None):
try:
result = function()
except Exception as error:
if on_error is not None:
on_error(error)
raise
else:
if on_success is not None:
on_success(result)
finally:
if on_finished is not None:
on_finished()
monkeypatch.setattr(socket.socket, "connect", reject_network)
monkeypatch.setattr(socket.socket, "connect_ex", reject_network)
monkeypatch.setattr(socket, "create_connection", reject_network)
monkeypatch.setattr(prescriptions_module, "run_async", immediate)
monkeypatch.setenv("DOCTOR_SMOKE_TEST", "1")
opened = []
def create(width=1536, height=960):
repository = DemoDoctorRepository()
session = repository.login(repository.DEMO_ACCOUNT, repository.DEMO_PASSWORD)
permissions = PermissionSet(["*"])
shell = ShellWindow(repository, {
"user": session.user,
"permissions": permissions,
"demo_mode": True,
"menu": [{"perms": "tcm.prescription/lists"}],
}, permissions=permissions)
opened.append(shell)
shell.setAttribute(Qt.WidgetAttribute.WA_DontShowOnScreen, True)
shell.resize(width, height)
shell.show()
assert shell.navigate("prescriptions")
_settle(application)
page = shell.pages["prescriptions"]
assert {_record_id(page.table, row) for row in range(page.table.rowCount())} == {801, 802}
assert (shell.width(), shell.height()) == (width, height)
return shell, page
yield create
for shell in opened:
for timer in shell.findChildren(QTimer):
timer.stop()
shell.close()
shell.deleteLater()
_settle(application)
assert network_attempts == []
@pytest.mark.parametrize(
("width", "height", "filter_height"),
[(1536, 960, 144), (1366, 768, 144), (1024, 768, 200), (1024, 640, 200)],
ids=["full-desktop", "compact-desktop", "small-window", "minimum-window"],
)
def test_shell_preserves_all_columns_filters_and_reachable_pager(
shell_factory, application, width, height, filter_height
) -> None:
shell, page = shell_factory(width, height)
page.filter_disclosure.set_expanded(True)
_settle(application)
table = page.table
assert [column.key for column in table.columns] == [
"__selected__", "sn", "__actions__", "prescription_type", "is_system_auto",
"patient_name", "audit_status", "void_status", "doctor_name", "assistant_name", "create_time",
"__ai_status__", "__ai_agreement__",
]
assert [table.horizontalHeader().logicalIndex(index) for index in range(11)] == [
0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 2,
]
assert table.horizontalHeaderItem(2).text() == "操作"
assert page.filter_card.height() == filter_height
for name in ("quick_date", "start_time", "end_time", "audit_filter", "source_filter",
"sn_filter", "patient_filter", "doctor_filter", "query_button", "reset_button"):
_assert_visible_within(getattr(page, name), page.filter_card)
outer_scroll = page.scroll.verticalScrollBar()
if width >= 1366:
assert outer_scroll.maximum() == 0
_assert_visible_within(page.filter_card, page.scroll.viewport())
_assert_visible_within(page.pager, page.scroll.viewport())
else:
# The compact footer allows 1024x768 to fit without outer scrolling.
if height <= 640:
assert outer_scroll.maximum() > 0
assert table.horizontalScrollBar().maximum() > 0
if width == 1536:
assert table.horizontalScrollBar().maximum() == 0
for row in range(table.rowCount()):
for column in range(11):
assert table.viewport().rect().contains(table.visualItemRect(table.item(row, column))), (row, column)
_assert_visible_within(page.pager, shell)
# Every logical column remains reachable in the native table at every width.
outer_scroll.setValue(outer_scroll.maximum())
for column in range(11):
table.scrollToItem(table.item(0, column), QAbstractItemView.ScrollHint.EnsureVisible)
_settle(application)
assert table.viewport().rect().contains(table.visualItemRect(table.item(0, column))), column
table.horizontalScrollBar().setValue(table.horizontalScrollBar().maximum())
_settle(application)
action_host = table.cellWidget(0, 2)
assert action_host is not None
_assert_visible_within(action_host, table.viewport())
_assert_visible_within(action_host, page.scroll.viewport())
_assert_visible_within(page.pager, page.scroll.viewport())
_assert_visible_within(page.pager.summary_label, shell)
assert page.pager.height() == 24
assert not hasattr(page.pager, "next")
for button in page.pager.findChildren(QPushButton):
if button.isVisible():
_assert_visible_within(button, page.scroll.viewport())
for button in action_host.findChildren(QPushButton):
_assert_visible_within(button, table.viewport())
def test_native_checkbox_center_clicks_and_space_keep_row_binding(shell_factory, application) -> None:
_shell, page = shell_factory()
table = page.table
row = next(index for index in range(table.rowCount()) if _record_id(table, index) == 802)
selector = table.item(row, 0)
table.setCurrentItem(selector)
table.setFocus()
_settle(application)
center = table.visualItemRect(selector).center()
assert table.viewport().rect().contains(center)
assert selector.checkState() == Qt.CheckState.Unchecked
QTest.mouseClick(table.viewport(), Qt.MouseButton.LeftButton, pos=center)
assert selector.checkState() == Qt.CheckState.Checked
QTest.mouseClick(table.viewport(), Qt.MouseButton.LeftButton, pos=center)
assert selector.checkState() == Qt.CheckState.Unchecked
QTest.keyClick(table, Qt.Key.Key_Space)
assert selector.checkState() == Qt.CheckState.Checked
assert int(table.current_data().id) == 802
assert selector.data(Qt.ItemDataRole.UserRole).id == 802
@pytest.mark.parametrize("order", [Qt.SortOrder.AscendingOrder, Qt.SortOrder.DescendingOrder])
def test_sorted_native_row_view_button_targets_visible_record(
shell_factory, application, monkeypatch, order
) -> None:
_shell, page = shell_factory()
table = page.table
visited = []
monkeypatch.setattr(page, "_view_selected", lambda: visited.append(int(table.current_data().id)))
table.sortItems(1, order)
_settle(application)
expected_order = [801, 802] if order == Qt.SortOrder.AscendingOrder else [802, 801]
assert [_record_id(table, index) for index in range(2)] == expected_order
for row in range(2):
# Start on the other record, so a stale action that only uses selection fails.
table.selectRow(1 - row)
host = table.cellWidget(row, 2)
button = next(button for button in host.findChildren(QPushButton)
if button.accessibleName() == "查看处方")
_assert_visible_within(button, table.viewport())
QTest.mouseClick(button, Qt.MouseButton.LeftButton, pos=button.rect().center())
assert visited[-1] == expected_order[row]
assert int(table.current_data().id) == expected_order[row]
assert visited == expected_order
def test_blue_icon_disabled_variant_does_not_modify_shared_icon_cache(application) -> None:
kind, color, size = "pencil", "#1769E8", 19
shared = icons.icon(kind, color, size)
original_key = shared.cacheKey()
original_normal = shared.pixmap(size, size, QIcon.Mode.Normal).toImage()
original_disabled = shared.pixmap(size, size, QIcon.Mode.Disabled).toImage()
blue = prescriptions_module._blue_prescription_icon(kind, color, size)
assert icons.icon(kind, color, size) is shared
assert shared.cacheKey() == original_key
assert shared.pixmap(size, size, QIcon.Mode.Normal).toImage() == original_normal
assert shared.pixmap(size, size, QIcon.Mode.Disabled).toImage() == original_disabled
assert blue.pixmap(size, size, QIcon.Mode.Normal).toImage() == original_normal
assert blue.pixmap(size, size, QIcon.Mode.Disabled).toImage() == icons.pixmap(kind, "#A4ADBA", size).toImage()
assert blue.pixmap(size, size, QIcon.Mode.Disabled).toImage() != original_disabled
def test_long_doctor_button_paints_compactly_without_truncating_real_value(shell_factory, application) -> None:
_shell, page = shell_factory(1024, 768)
page.filter_disclosure.set_expanded(True)
_settle(application)
button = page.doctor_filter.button
full_text = "联合会诊专家门诊陈医生、疑难病联合门诊林医生 等3人"
button.setText(full_text)
_settle(application)
assert button.fontMetrics().horizontalAdvance(full_text) > button.width() - 48
assert button.text() == full_text
assert button.toolTip() == full_text
assert not button.grab().isNull() # Execute the actual compact paint path.
assert button.text() == full_text
assert page.doctor_filter.values() == []