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

380 lines
16 KiB
Python

from __future__ import annotations
import os
from typing import Any
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
import pytest
from PySide6.QtCore import QPoint, QSize
from PySide6.QtGui import QFont, QFontInfo, QIcon
from PySide6.QtWidgets import QApplication, QLabel, QVBoxLayout, QWidget
from doctor_workstation.ui import shell as shell_module
from doctor_workstation.ui.reception_style import (
TECH_BLUE,
body_family,
heading_family,
number_family,
)
from doctor_workstation.ui.shell import NavigationItem, ShellWindow
from doctor_workstation.ui.theme import COLORS, apply_theme
class _Page(QWidget):
def __init__(self, _repository: Any, *, parent: QWidget, **_kwargs: Any) -> None:
super().__init__(parent)
layout = QVBoxLayout(self)
self.label = QLabel("原有页面正文", self)
layout.addWidget(self.label)
@pytest.fixture(scope="module")
def application() -> QApplication:
app = QApplication.instance() or QApplication([])
apply_theme(app)
return app
@pytest.fixture
def window(application: QApplication, monkeypatch: pytest.MonkeyPatch):
navigation = [
NavigationItem(key, title, "", _Page, ("doctor.appointment/lists",))
for key, title in (
("appointments", "挂号列表"),
("consultations", "问诊列表"),
("reception", "接诊台"),
("patients", "我的患者"),
("prescriptions", "已开处方"),
("prescription_library", "我的处方库"),
("legacy_reference", "原版框架参照"),
)
]
monkeypatch.setattr(
shell_module,
"_resolve_navigation",
lambda *_args, **_kwargs: [(item, item.title) for item in navigation],
)
monkeypatch.setattr(shell_module.motion, "reduced_motion", lambda: True)
shell = ShellWindow(
object(),
{"user": {"name": "陈医生"}, "demo_mode": True},
permissions={"doctor.appointment/lists", "tcm.diagnosis/aiAssistant"},
)
shell.resize(1536, 960)
shell.show()
application.processEvents()
yield shell
shell.close()
application.processEvents()
def _pixel(widget: QWidget, x: int, y: int) -> str:
image = widget.grab().toImage()
ratio = image.devicePixelRatio()
return image.pixelColor(round(x * ratio), round(y * ratio)).name().upper()
def test_approved_chrome_restores_styles_fonts_icons_and_geometry(
application: QApplication, window: ShellWindow
) -> None:
assert window.navigate("legacy_reference")
application.processEvents()
original_app_qss = application.styleSheet()
original_app_font = application.font().toString()
original_colors = dict(COLORS)
original_page_fonts = {key: page.label.font().toString() for key, page in window.pages.items()}
original_styles = dict(window._legacy_chrome_styles)
original_avatar = window.user_menu_button.icon().pixmap(34, 34).toImage()
original_ai = window.ai_top_button.icon().pixmap(18, 18).toImage()
original_nav = (
window.nav_buttons["reception"]
.icon()
.pixmap(18, 18, QIcon.Mode.Normal, QIcon.State.On)
.toImage()
)
for key in ("appointments", "consultations", "reception", "patients", "prescriptions", "prescription_library", "consultations", "appointments"):
assert window.navigate(key)
application.processEvents()
assert window.sidebar.width() == 208
assert _pixel(window, 6, 300) == "#F3F7FD"
assert _pixel(window.sidebar, 5, 300) == "#F3F7FD"
assert _pixel(window.workspace, 600, 10) == TECH_BLUE["surface"]
assert window.nav_buttons[key].font().family() == body_family()
assert window.nav_buttons[key].font().weight() == QFont.Weight.Normal
assert window.brand_name.font().family() == heading_family()
assert window.brand_name.font().weight() == QFont.Weight.DemiBold
assert window.assistant_glyph.property("receptionTechBlue") is True
assert window.assistant_status.property("receptionTechBlue") is True
assert original_avatar != window.user_menu_button.icon().pixmap(34, 34).toImage()
assert original_ai != window.ai_top_button.icon().pixmap(18, 18).toImage()
assert (
original_nav
!= window.nav_buttons["reception"]
.icon()
.pixmap(18, 18, QIcon.Mode.Normal, QIcon.State.On)
.toImage()
)
assert {
key: page.label.font().toString() for key, page in window.pages.items()
} == original_page_fonts
assert window.navigate("legacy_reference")
application.processEvents()
assert window.sidebar.width() == 190
assert _pixel(window, 6, 300) == COLORS["canvas"].upper()
assert _pixel(window, 18, 313) == COLORS["canvas"].upper()
assert {widget: widget.styleSheet() for widget in original_styles} == original_styles
assert window.user_menu_button.icon().pixmap(34, 34).toImage() == original_avatar
assert window.ai_top_button.icon().pixmap(18, 18).toImage() == original_ai
assert (
window.nav_buttons["reception"]
.icon()
.pixmap(18, 18, QIcon.Mode.Normal, QIcon.State.On)
.toImage()
== original_nav
)
assert window.assistant_glyph.property("receptionTechBlue") is False
assert window.assistant_status.property("receptionTechBlue") is False
assert application.styleSheet() == original_app_qss
assert application.font().toString() == original_app_font
assert original_colors == COLORS
assert {
key: page.label.font().toString() for key, page in window.pages.items()
} == original_page_fonts
@pytest.mark.parametrize("key", ["appointments", "consultations", "reception", "patients", "prescriptions", "prescription_library"])
def test_selected_navigation_and_ai_tile_use_approved_blue(
application: QApplication, window: ShellWindow, key: str
) -> None:
window.navigate(key)
application.processEvents()
button = window.nav_buttons[key]
button.clearFocus()
application.processEvents()
assert _pixel(button, 1, button.height() // 2) == TECH_BLUE["accent"]
assert _pixel(button, button.width() - 12, button.height() // 2) == TECH_BLUE["selection"]
assert _pixel(window.assistant_glyph, 10, 25) == TECH_BLUE["accent"]
assert _pixel(window.assistant_status, 3, 9) == TECH_BLUE["accent"]
@pytest.mark.parametrize("key", ["appointments", "consultations", "reception", "patients", "prescriptions", "prescription_library"])
def test_approved_brand_mark_uses_blue_medical_cross_and_restores_original(
application: QApplication, window: ShellWindow, key: str
) -> None:
assert window.navigate("legacy_reference")
application.processEvents()
mark = window.brand_mark
original = mark.grab().toImage()
assert window.navigate(key)
application.processEvents()
assert mark.property("receptionTechBlue") is True
assert mark.width() == mark.height() == 38
assert _pixel(mark, 5, 18) == TECH_BLUE["accent"]
assert _pixel(mark, 19, 19) == TECH_BLUE["accent"]
assert _pixel(mark, 18, 9) == TECH_BLUE["surface"]
assert original != mark.grab().toImage()
assert window.navigate("legacy_reference")
application.processEvents()
assert mark.property("receptionTechBlue") is False
assert mark.grab().toImage() == original
def test_collapsed_rail_keeps_state_across_approved_and_original_pages(
application: QApplication, window: ShellWindow
) -> None:
window.toggle_sidebar()
assert window.sidebar.width() == 68
window.navigate("reception")
application.processEvents()
assert window.sidebar.width() == 68
assert window.nav_buttons["reception"].isChecked()
assert not window.assistant_card.isVisible()
assert window.nav_buttons["reception"].toolTip() == "接诊台"
window.toggle_sidebar()
application.processEvents()
assert window.sidebar.width() == 208
assert window.assistant_card.isVisible()
window.toggle_sidebar()
window.navigate("appointments")
application.processEvents()
assert window.sidebar.width() == 68
assert window.nav_buttons["appointments"].isChecked()
assert window.nav_buttons["appointments"].toolTip() == "挂号列表"
assert not window.assistant_card.isVisible()
window.navigate("consultations")
application.processEvents()
assert window.sidebar.width() == 68
assert window.nav_buttons["consultations"].isChecked()
assert window.nav_buttons["consultations"].toolTip() == "问诊列表"
assert not window.assistant_card.isVisible()
window.navigate("patients")
application.processEvents()
assert window.sidebar.width() == 68
assert window.nav_buttons["patients"].isChecked()
window.toggle_sidebar()
application.processEvents()
assert window.sidebar.width() == 208
window.toggle_sidebar()
window.navigate("prescriptions")
application.processEvents()
assert window.sidebar.width() == 68
assert window.nav_buttons["prescriptions"].isChecked()
assert window.nav_buttons["prescriptions"].toolTip() == "已开处方"
assert not window.assistant_card.isVisible()
window.toggle_sidebar()
application.processEvents()
assert window.sidebar.width() == 208
window.toggle_sidebar()
window.navigate("legacy_reference")
application.processEvents()
assert window.sidebar.width() == 68
window.toggle_sidebar()
application.processEvents()
assert window.sidebar.width() == 190
assert window.nav_buttons["legacy_reference"].text() == "原版框架参照"
@pytest.mark.parametrize("key", ["appointments", "consultations", "reception", "patients", "prescriptions", "prescription_library"])
def test_approved_shell_geometry_and_function_entries_restore(
application: QApplication, window: ShellWindow, key: str
) -> None:
assert window.navigate("legacy_reference")
application.processEvents()
original_geometry = {
widget: (widget.size(), widget.iconSize() if hasattr(widget, "iconSize") else None)
for widget in (
window.sidebar, window.topbar, window.search_host, window.brand_mark,
window.assistant_card, window.assistant_button, window.assistant_status,
window.upload_settings_button, window.user_menu_button,
window.refresh_button, window.notification_button, window.settings_button,
*window.nav_buttons.values(),
)
}
assert window.navigate(key)
application.processEvents()
assert window.workspace.pos() == QPoint(208, 0)
assert window.topbar.height() == 76
assert window.centralWidget().layout().contentsMargins().isNull()
assert window.search_host.mapTo(window, QPoint()) == QPoint(234, 15)
assert window.search_host.size() == QSize(384, 44)
assert window.assistant_card.mapTo(window, QPoint()) == QPoint(20, 695)
assert window.assistant_card.size() == QSize(168, 171)
assert window.assistant_button.height() == 46
assert window.brand_name.font().pixelSize() == 18
assert window.brand_subtitle.font().pixelSize() == 13
for index, button in enumerate(window.nav_buttons.values()):
assert button.mapTo(window, QPoint()).y() == 99 + 58 * index
assert button.height() == 52
assert button.font().pixelSize() == 15
assert button.iconSize() == QSize(20, 20)
assert not window.fold_button.isVisible()
assert not window.ai_top_button.isVisible()
assert window.menu_sidebar_action.isVisible()
assert window.menu_ai_action.isVisible()
window.menu_sidebar_action.trigger()
application.processEvents()
assert window.sidebar.width() == 68
assert window.menu_sidebar_action.isVisible()
window.menu_sidebar_action.trigger()
application.processEvents()
assert window.sidebar.width() == 208
assert window.navigate("legacy_reference")
application.processEvents()
assert window.workspace.pos() == QPoint(203, 13)
assert window.fold_button.isVisible()
assert window.ai_top_button.isVisible()
assert not window.menu_sidebar_action.isVisible()
assert not window.menu_ai_action.isVisible()
assert not window.user_separator.isVisible()
for widget, (size, icon_size) in original_geometry.items():
assert widget.size() == size
if icon_size is not None:
assert widget.iconSize() == icon_size
def test_reception_fonts_resolve_real_requested_weights(application: QApplication) -> None:
for family, weights in (
(body_family(), (QFont.Weight.Normal,)),
(heading_family(), (QFont.Weight.Medium, QFont.Weight.DemiBold)),
(number_family(), (QFont.Weight.Normal, QFont.Weight.DemiBold)),
):
for weight in weights:
font = QFont(family)
font.setPixelSize(15)
font.setWeight(weight)
resolved = QFontInfo(font)
assert resolved.family() == family
assert resolved.weight() == weight
@pytest.mark.parametrize("key", ["appointments", "consultations", "reception", "patients", "prescriptions", "prescription_library"])
def test_minimum_window_keeps_search_and_all_chrome_actions_separate(
application: QApplication, window: ShellWindow, key: str
) -> None:
window.navigate(key)
window.resize(1024, 640)
application.processEvents()
assert window.width() == 1024
assert 220 <= window.search_host.width() < 384
controls = (
window.search_host, window.refresh_button, window.notification_button,
window.settings_button, window.user_menu_button, window.minimize_button,
window.fullscreen_button, window.close_button,
)
for left, right in zip(controls, controls[1:], strict=False):
assert left.x() + left.width() <= right.x()
assert right.x() + right.width() <= window.topbar.width()
window.resize(1536, 960)
application.processEvents()
assert window.search_host.size() == QSize(384, 44)
def test_refresh_animation_keeps_running_and_restores_palette_on_page_switch(
application: QApplication, window: ShellWindow
) -> None:
window.navigate("legacy_reference")
application.processEvents()
button = window.refresh_button
original = button.icon().pixmap(18, 18).toImage()
button.start_spin()
for key in ("appointments", "consultations", "reception", "patients", "prescriptions", "prescription_library"):
window.navigate(key)
application.processEvents()
assert button._timer.isActive()
assert button.property("receptionTechBlue") is True
window.navigate("legacy_reference")
application.processEvents()
assert button._timer.isActive()
assert button.property("receptionTechBlue") is False
assert button._resting.pixmap(18, 18).toImage() == original
button._timer.stop()
def test_navigation_between_approved_pages_keeps_existing_chrome_assets(
application: QApplication, window: ShellWindow
) -> None:
assert window.navigate("appointments")
application.processEvents()
styles = {widget: widget.styleSheet() for widget in window._legacy_chrome_styles}
icons = {
widget: widget.icon().cacheKey()
for widget in (
window.user_menu_button, window.refresh_button, window.ai_top_button,
*window.nav_buttons.values(),
)
}
window.global_search.setText("患者查询")
for key in ("consultations", "reception", "patients", "prescriptions", "prescription_library", "appointments", "consultations"):
assert window.navigate(key)
application.processEvents()
assert window.nav_buttons[key].isChecked()
assert window.global_search.text() == "患者查询"
assert window._reception_chrome_active
assert {widget: widget.styleSheet() for widget in styles} == styles
assert {widget: widget.icon().cacheKey() for widget in icons} == icons