"""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() == []