更新
This commit is contained in:
@@ -10,11 +10,14 @@ os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||
import pytest
|
||||
from PySide6.QtCore import QAbstractTableModel, QPoint, QRect, Qt, Signal
|
||||
from PySide6.QtGui import QColor, QImage, QPainter
|
||||
from PySide6.QtTest import QTest
|
||||
from PySide6.QtWidgets import (
|
||||
QAbstractItemView,
|
||||
QApplication,
|
||||
QComboBox,
|
||||
QFrame,
|
||||
QSizePolicy,
|
||||
QSpinBox,
|
||||
QToolButton,
|
||||
QWidget,
|
||||
)
|
||||
@@ -161,27 +164,44 @@ def _page() -> ConsultationsPage:
|
||||
return ConsultationsPage(_CancellationRepository(), permissions=PermissionSet(["*"]))
|
||||
|
||||
|
||||
def _caret_matches(button: object, direction: str) -> bool:
|
||||
"""The disclosure caret is now a shared glyph, not Fusion's arrow type.
|
||||
|
||||
``setArrowType`` drew a solid triangle - the one filled mark in an otherwise
|
||||
all-stroke icon set - so the state is carried by the icon instead, and the
|
||||
direction has to be checked by comparing what was actually painted.
|
||||
"""
|
||||
|
||||
from doctor_workstation.ui import icons
|
||||
|
||||
painted = button.icon().pixmap(14, 14).toImage()
|
||||
expected = icons.pixmap(direction, "muted", 14).toImage()
|
||||
return painted == expected
|
||||
|
||||
|
||||
def test_visual_hierarchy_and_filter_contract(
|
||||
application: QApplication,
|
||||
) -> None:
|
||||
page = _page()
|
||||
content_layout = page.page_scroll.widget().layout()
|
||||
margins = content_layout.contentsMargins()
|
||||
assert (margins.left(), margins.top(), margins.right(), margins.bottom()) == (18, 10, 18, 10)
|
||||
assert content_layout.spacing() == 8
|
||||
# The approved blue shell has no outer gutter; the page owns this spacing.
|
||||
assert (margins.left(), margins.top(), margins.right(), margins.bottom()) == (27, 24, 26, 8)
|
||||
assert content_layout.spacing() == 10
|
||||
status_card = page.findChild(QFrame, "DiagnosisStatusCard")
|
||||
assert status_card is not None
|
||||
assert page.page_header.height() == 62
|
||||
assert status_card.height() == 50
|
||||
assert page.page_header.maximumHeight() >= page.page_header.minimumSizeHint().height()
|
||||
assert status_card.height() == 54
|
||||
assert page.findChild(QFrame, "DiagnosisFilterCard") is not None
|
||||
assert page.findChild(QFrame, "DiagnosisListCard") is not None
|
||||
assert page.filters_card.height() == 90
|
||||
assert page.keyword_edit.maximumWidth() == 380
|
||||
assert page.filters_card.isHidden()
|
||||
assert page.page_header.height() <= 44
|
||||
assert page.keyword_edit.maximumWidth() == 340
|
||||
assert list(page.status_buttons) == ["1", "", "4", "2", "3"]
|
||||
assert page.status_buttons["1"].isChecked()
|
||||
assert not page.advanced_filters.isVisible()
|
||||
assert page.more_filter_button.text() == "更多筛选"
|
||||
assert page.more_filter_button.arrowType() == Qt.ArrowType.DownArrow
|
||||
assert _caret_matches(page.more_filter_button, "down")
|
||||
assert [page._date_button_labels[key] for key in page.date_buttons] == [
|
||||
"昨天挂号",
|
||||
"前天挂号",
|
||||
@@ -228,7 +248,7 @@ def test_pending_assign_and_secondary_chip_semantics(
|
||||
page._toggle_advanced_filters(True)
|
||||
assert not page.advanced_filters.isHidden()
|
||||
assert page.more_filter_button.text() == "收起"
|
||||
assert page.more_filter_button.arrowType() == Qt.ArrowType.UpArrow
|
||||
assert _caret_matches(page.more_filter_button, "up")
|
||||
assert page.unserved_sort_combo.isHidden()
|
||||
date_ranges = page.advanced_filters.findChildren(QFrame, "DiagnosisDateRange")
|
||||
assert len(date_ranges) == 2
|
||||
@@ -237,15 +257,81 @@ def test_pending_assign_and_secondary_chip_semantics(
|
||||
application.processEvents()
|
||||
|
||||
|
||||
def test_toolbar_cancel_tracks_single_appointment_without_changing_checked_rows(
|
||||
application: QApplication,
|
||||
) -> None:
|
||||
page = _page()
|
||||
single = _row(880)
|
||||
multiple = _row(881, appointments=[
|
||||
{"id": 8801, "status": 1}, {"id": 8802, "status": 3},
|
||||
])
|
||||
page.table_host.set_rows([single, multiple])
|
||||
page.table.selectRow(0)
|
||||
page._selection_changed()
|
||||
assert page.cancel_toolbar_button.isEnabled()
|
||||
assert page.case_toolbar_button.isEnabled()
|
||||
assert not page.call_toolbar_button.isEnabled()
|
||||
page.table.selectRow(1)
|
||||
page._selection_changed()
|
||||
assert not page.cancel_toolbar_button.isEnabled()
|
||||
assert page.table_host.selected_records() == []
|
||||
page.table_host.set_rows([])
|
||||
page._selection_changed()
|
||||
assert not page.case_toolbar_button.isEnabled()
|
||||
assert not page.prescription_toolbar_button.isEnabled()
|
||||
page.close()
|
||||
application.processEvents()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("size", [(816, 564), (1328, 884)])
|
||||
def test_filter_rows_and_toolbar_stay_inside_their_panels_when_wrapping(
|
||||
application: QApplication,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
size: tuple[int, int],
|
||||
) -> None:
|
||||
page = _page()
|
||||
monkeypatch.setattr(page, "refresh", lambda silent=False: None)
|
||||
page.resize(*size)
|
||||
page.show()
|
||||
page.filter_disclosure.set_expanded(True)
|
||||
for expanded in (False, True):
|
||||
page._toggle_advanced_filters(expanded)
|
||||
if expanded:
|
||||
page._choose_pending_assign()
|
||||
for _ in range(4):
|
||||
application.processEvents()
|
||||
controls = [*page.date_buttons.values(), page.custom_date_edit,
|
||||
page.confirmed_combo, page.department_combo, page.keyword_edit,
|
||||
page.more_filter_button]
|
||||
if expanded:
|
||||
controls.extend([page.pending_assign_month, page.pending_assign_keyword,
|
||||
page.channel_combo, page.latest_assign_end_date])
|
||||
rectangles = [QRect(control.mapTo(page.filters_card, QPoint()), control.size())
|
||||
for control in controls if control.isVisible()]
|
||||
assert all(page.filters_card.rect().contains(rect) for rect in rectangles)
|
||||
for index, rect in enumerate(rectangles):
|
||||
assert all(not rect.intersects(other) for other in rectangles[index + 1:])
|
||||
for button in (page.add_button, page.cancel_toolbar_button,
|
||||
page.complete_toolbar_button, page.refresh_button):
|
||||
if button.isVisible():
|
||||
assert page.list_toolbar.rect().contains(
|
||||
QRect(button.mapTo(page.list_toolbar, QPoint()), button.size())
|
||||
)
|
||||
page.close()
|
||||
application.processEvents()
|
||||
|
||||
|
||||
def test_dedicated_model_fixed_columns_selection_and_sort(
|
||||
application: QApplication,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
page = _page()
|
||||
assert isinstance(page.table.model(), QAbstractTableModel)
|
||||
assert isinstance(page.table.model(), DiagnosisTableModel)
|
||||
assert page.table_host.LEFT_WIDTHS == (48, 70, 60, 100, 175, 88, 120, 100, 72, 110)
|
||||
assert page.table_host.FIXED_WIDTHS == (120, 410)
|
||||
assert page.table_host.fixed.width() == 532
|
||||
assert page.table_host.LEFT_WIDTHS == (48, 70, 82, 102, 244, 90, 84, 90, 88, 122)
|
||||
# Reference-aligned video/actions remain frozen in a compact 250px pane.
|
||||
assert page.table_host.FIXED_WIDTHS == (92, 158)
|
||||
assert page.table_host.fixed.width() == 250
|
||||
assert page.table.isColumnHidden(10)
|
||||
assert page.table_host.fixed.isColumnHidden(9)
|
||||
assert not page.table_host.fixed.isColumnHidden(10)
|
||||
@@ -267,6 +353,9 @@ def test_dedicated_model_fixed_columns_selection_and_sort(
|
||||
page.table_host.sort_unserved_requested.connect(requested.append)
|
||||
assert page.table_host.model.headerData(9, Qt.Orientation.Horizontal) == "未服务天数"
|
||||
assert page.table_host.model._sort_direction == ""
|
||||
# This model/action test keeps its fixture rows; a real server sort starts
|
||||
# a new query and intentionally resets the loaded list.
|
||||
monkeypatch.setattr(page, "refresh", lambda silent=False: None)
|
||||
page.table_host.main.horizontalHeader().sectionClicked.emit(9)
|
||||
assert requested == ["desc"]
|
||||
assert page.table_host.model._sort_direction == "desc"
|
||||
@@ -300,7 +389,7 @@ def test_dedicated_model_fixed_columns_selection_and_sort(
|
||||
application.processEvents()
|
||||
|
||||
|
||||
def test_empty_loading_and_full_pager_keep_the_table_shell(
|
||||
def test_empty_loading_and_compact_footer_keep_the_table_shell(
|
||||
application: QApplication,
|
||||
) -> None:
|
||||
page = _page()
|
||||
@@ -321,13 +410,13 @@ def test_empty_loading_and_full_pager_keep_the_table_shell(
|
||||
page.loading_overlay.stop()
|
||||
|
||||
page.pager.update_state(3, 97)
|
||||
assert 40 <= page.pager.height() <= 44
|
||||
assert page.pager.height() == 24
|
||||
pager_margins = page.pager.layout().contentsMargins()
|
||||
assert pager_margins.top() >= 4
|
||||
assert pager_margins.bottom() >= 4
|
||||
assert [page.pager.size_combo.itemData(index) for index in range(4)] == [15, 20, 30, 40]
|
||||
assert len([button for button in page.pager._page_buttons if not button.isHidden()]) == 5
|
||||
assert page.pager.jumper.maximum() == 7
|
||||
assert pager_margins.top() == 0
|
||||
assert pager_margins.bottom() == 0
|
||||
assert not page.pager.findChildren(QComboBox)
|
||||
assert not page.pager.findChildren(QSpinBox)
|
||||
assert not page.pager.findChildren(QToolButton)
|
||||
page.close()
|
||||
application.processEvents()
|
||||
|
||||
@@ -335,11 +424,11 @@ def test_empty_loading_and_full_pager_keep_the_table_shell(
|
||||
@pytest.mark.parametrize(
|
||||
("record", "stripe", "channel"),
|
||||
[
|
||||
(_row(701, DiagnosisViewRecord=[{"is_confirmed": 0}]), "#9a6813", "warning"),
|
||||
(_row(702, has_appointment=0, appointments=[]), "#2f6edb", "info"),
|
||||
(_row(701, DiagnosisViewRecord=[{"is_confirmed": 0}]), "#a9691d", "warning"),
|
||||
(_row(702, has_appointment=0, appointments=[]), "#4f63d9", "info"),
|
||||
],
|
||||
)
|
||||
def test_semantic_hover_preserves_gradient_and_three_pixel_stripe(
|
||||
def test_semantic_hover_preserves_three_pixel_stripe_on_neutral_background(
|
||||
application: QApplication,
|
||||
record: dict[str, Any],
|
||||
stripe: str,
|
||||
@@ -363,8 +452,9 @@ def test_semantic_hover_preserves_gradient_and_three_pixel_stripe(
|
||||
assert image.pixelColor(0, 20).name() == stripe
|
||||
assert image.pixelColor(1, 20).name() == stripe
|
||||
assert image.pixelColor(2, 20).name() == stripe
|
||||
gradient = image.pixelColor(6, 20)
|
||||
assert gradient.name() != "#f8f8f8", f"{channel} hover collapsed to a neutral row"
|
||||
assert image.pixelColor(3, 20).name() == "#f7f7f7", f"{channel} stripe widened"
|
||||
assert image.pixelColor(6, 20).name() == "#f7f7f7"
|
||||
assert image.pixelColor(40, 20).name() == "#f7f7f7"
|
||||
|
||||
|
||||
def test_page_hides_fixed_shadow_and_preserves_admin_token_contract(
|
||||
@@ -378,9 +468,7 @@ def test_page_hides_fixed_shadow_and_preserves_admin_token_contract(
|
||||
assert shadow.isHidden()
|
||||
assert shadow.width() == 12
|
||||
assert shadow.geometry().right() == page.table_host.fixed.geometry().left() - 1
|
||||
assert 'font-family: "PingFang SC", Arial, "Hiragino Sans GB", "Microsoft YaHei"' in (
|
||||
DIAGNOSIS_INDEX_QSS
|
||||
)
|
||||
assert "font-family:" not in DIAGNOSIS_INDEX_QSS
|
||||
assert "QTableView:focus" in DIAGNOSIS_INDEX_QSS
|
||||
assert "QToolButton[rowLink]:focus" in DIAGNOSIS_INDEX_QSS
|
||||
assert '#DiagnosisIndex QToolButton[diagnosisChip="true"][semantic="warning"]' in (
|
||||
@@ -491,7 +579,7 @@ def test_full_more_menu_requires_each_real_repository_capability(
|
||||
min(danger_image.width(), danger_rect.right() + 1),
|
||||
):
|
||||
color = danger_image.pixelColor(x, y)
|
||||
if color.red() > 190 and color.green() < 150 and color.blue() < 150:
|
||||
if color.red() > color.green() + 60 and color.red() > color.blue() + 60:
|
||||
red_text_pixels += 1
|
||||
assert red_text_pixels > 8, "删除文案必须由 danger 色绘制,不能回退成原生黑色"
|
||||
more.menu().hide()
|
||||
@@ -642,7 +730,7 @@ def test_error_state_is_persistent_until_rows_replace_it(application: QApplicati
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("size", "minimum_visible_rows"),
|
||||
[((1366, 768), 4), ((1710, 920), 7)],
|
||||
[((1366, 768), 3), ((1710, 920), 4)],
|
||||
)
|
||||
def test_two_desktop_sizes_keep_pager_visible_and_scroll_rows_inside_table(
|
||||
application: QApplication,
|
||||
@@ -686,9 +774,10 @@ def test_two_desktop_sizes_keep_pager_visible_and_scroll_rows_inside_table(
|
||||
application.processEvents()
|
||||
|
||||
|
||||
def test_frozen_rows_track_main_pixel_scroll_and_host_height_is_page_size_stable(
|
||||
def test_frozen_rows_track_main_pixel_scroll_and_host_height_is_append_stable(
|
||||
application: QApplication,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
immediate_async: None,
|
||||
) -> None:
|
||||
page = _page()
|
||||
rows = [
|
||||
@@ -704,15 +793,26 @@ def test_frozen_rows_track_main_pixel_scroll_and_host_height_is_page_size_stable
|
||||
for _ in range(4):
|
||||
application.processEvents()
|
||||
host_height = page.table_host.height()
|
||||
calls: list[int] = []
|
||||
|
||||
monkeypatch.setattr(page, "refresh", lambda silent=False: None)
|
||||
page._change_page_size(40)
|
||||
page.table_host.set_rows(rows)
|
||||
def list_consultations(**query: Any) -> dict[str, Any]:
|
||||
calls.append(query["page_no"])
|
||||
start = (query["page_no"] - 1) * query["page_size"]
|
||||
return {"lists": rows[start:start + query["page_size"]], "count": len(rows)}
|
||||
|
||||
monkeypatch.setattr(page.repository, "list_consultations", list_consultations, raising=False)
|
||||
page.refresh(silent=True)
|
||||
for _ in range(2):
|
||||
page.table.verticalScrollBar().setValue(page.table.verticalScrollBar().maximum())
|
||||
QTest.qWait(50)
|
||||
application.processEvents()
|
||||
for _ in range(4):
|
||||
application.processEvents()
|
||||
|
||||
main_scroll = page.table.verticalScrollBar()
|
||||
fixed_scroll = page.table_host.fixed.verticalScrollBar()
|
||||
assert calls == [1, 2, 3]
|
||||
assert page.table.rowCount() == 40
|
||||
assert page.table_host.height() == host_height
|
||||
assert main_scroll.maximum() == fixed_scroll.maximum()
|
||||
main_scroll.setValue(main_scroll.maximum() // 2)
|
||||
|
||||
Reference in New Issue
Block a user