380 lines
16 KiB
Python
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
|