更新
This commit is contained in:
@@ -11,7 +11,7 @@ from PySide6.QtWidgets import QApplication, QDialog, QFrame, QToolButton, QWidge
|
||||
|
||||
from doctor_workstation.ui import shell as shell_module
|
||||
from doctor_workstation.ui.shell import NavigationItem, ShellWindow
|
||||
from doctor_workstation.ui.theme import apply_theme
|
||||
from doctor_workstation.ui.theme import COLORS, apply_theme
|
||||
|
||||
|
||||
def _logical_pixel(image: Any, x: int, y: int):
|
||||
@@ -134,7 +134,8 @@ def shell_window(
|
||||
),
|
||||
("prescriptions", "已开处方", "笺", "tcm.prescription/lists"),
|
||||
("patients", "我的患者", "患", "firstvisit.myPatient/lists"),
|
||||
("consultations", "问诊列表", "询", "tcm.diagnosis/lists"),
|
||||
("consultations", "问诊列表", "询", "tcm.diagnosis/lists"),
|
||||
("legacy_reference", "原版框架参照", "旧", "legacy.reference/lists"),
|
||||
)
|
||||
]
|
||||
monkeypatch.setattr(
|
||||
@@ -160,38 +161,40 @@ def shell_window(
|
||||
application.processEvents()
|
||||
|
||||
|
||||
def test_shell_matches_reference_geometry_at_both_acceptance_sizes(
|
||||
def test_legacy_shell_matches_reference_geometry_at_both_acceptance_sizes(
|
||||
application: QApplication,
|
||||
shell_window: ShellWindow,
|
||||
) -> None:
|
||||
assert shell_window.navigate("legacy_reference")
|
||||
for width, height in ((1024, 640), (1440, 900)):
|
||||
shell_window.resize(width, height)
|
||||
application.processEvents()
|
||||
|
||||
assert shell_window.sidebar.width() == 179
|
||||
# 独立legacy参照路由继续验证原有导轨和外圈留白。
|
||||
assert shell_window.sidebar.width() == 190
|
||||
assert shell_window.topbar.height() == 62
|
||||
assert shell_window.tabs_host.height() == 0
|
||||
assert shell_window.workspace.width() == width - 26 - 179
|
||||
assert shell_window.stack.width() == width - 26 - 179
|
||||
assert shell_window.workspace.width() == width - 26 - 190
|
||||
assert shell_window.stack.width() == width - 26 - 190
|
||||
assert shell_window.stack.height() == height - 26 - 62
|
||||
assert shell_window.stack.geometry().right() < shell_window.workspace.width()
|
||||
assert shell_window.stack.geometry().bottom() < shell_window.workspace.height()
|
||||
|
||||
image = shell_window.grab().toImage()
|
||||
assert _logical_pixel(image, 20, 300).name().lower() in {
|
||||
"#f2f5fd",
|
||||
"#f3f6fd",
|
||||
"#f2f6fe",
|
||||
"#f3f6fe",
|
||||
}
|
||||
# 侧边栏不再是画布上的一块面板:它就是画布本身,所以导轨内任意一点
|
||||
# 都必须与外圈留白同色。原先它是 #F4F7FE→#EEF3FD 的斜向渐变,
|
||||
# 沿整条左边缘都对不上画布,形成一道常驻接缝。
|
||||
assert _logical_pixel(image, 20, 300).name().lower() == COLORS["canvas"].lower()
|
||||
assert _logical_pixel(image, 6, 300).name().lower() == COLORS["canvas"].lower()
|
||||
assert _logical_pixel(image, 610, 20).name().lower() == "#ffffff"
|
||||
assert _logical_pixel(image, 220, 90).name().lower() == "#fcfdfe"
|
||||
assert _logical_pixel(image, 220, 90).name().lower() == COLORS["canvas_mid"].lower()
|
||||
|
||||
|
||||
def test_topbar_search_actions_and_navigation_controls_stay_aligned(
|
||||
def test_legacy_topbar_search_actions_and_navigation_controls_stay_aligned(
|
||||
application: QApplication,
|
||||
shell_window: ShellWindow,
|
||||
) -> None:
|
||||
assert shell_window.navigate("legacy_reference")
|
||||
for width, height in ((1024, 640), (1366, 768)):
|
||||
shell_window.resize(width, height)
|
||||
application.processEvents()
|
||||
@@ -243,6 +246,7 @@ def test_registered_pages_are_not_top_level_windows(shell_window: ShellWindow) -
|
||||
def test_reference_shell_has_integrated_search_ai_card_and_window_controls(
|
||||
shell_window: ShellWindow,
|
||||
) -> None:
|
||||
assert shell_window.navigate("legacy_reference")
|
||||
assert shell_window.windowFlags() & Qt.WindowType.FramelessWindowHint
|
||||
assert (
|
||||
shell_window.global_search.placeholderText() == "搜索患者姓名、手机号、病历号"
|
||||
@@ -405,15 +409,17 @@ def test_shell_ai_entry_without_selection_opens_patient_diagnosis_picker(
|
||||
]
|
||||
|
||||
|
||||
def test_shell_ai_entry_on_reception_still_opens_global_patient_picker(
|
||||
@pytest.mark.parametrize("key", ["appointments", "consultations", "reception", "patients", "prescriptions", "prescription_library"])
|
||||
def test_shell_ai_menu_on_approved_pages_opens_global_patient_picker(
|
||||
application: QApplication,
|
||||
shell_window: ShellWindow,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
key: str,
|
||||
) -> None:
|
||||
reception = shell_window.pages["reception"]
|
||||
assert isinstance(reception, _ShellPageDouble)
|
||||
assert shell_window.navigate("reception")
|
||||
reception.ai_context_available = True
|
||||
page = shell_window.pages[key]
|
||||
assert isinstance(page, _ShellPageDouble)
|
||||
assert shell_window.navigate(key)
|
||||
page.ai_context_available = True
|
||||
opened: list[tuple[tuple[Any, ...], dict[str, Any]]] = []
|
||||
monkeypatch.setattr(
|
||||
shell_module,
|
||||
@@ -421,25 +427,34 @@ def test_shell_ai_entry_on_reception_still_opens_global_patient_picker(
|
||||
lambda *args, **kwargs: opened.append((args, kwargs)) or False,
|
||||
)
|
||||
|
||||
shell_window.ai_top_button.click()
|
||||
assert shell_window.menu_ai_action.isVisible()
|
||||
shell_window.menu_ai_action.trigger()
|
||||
application.processEvents()
|
||||
|
||||
assert reception.ai_open_count == 0
|
||||
assert page.ai_open_count == 0
|
||||
assert len(opened) == 1
|
||||
assert shell_window.stack.currentWidget() is reception
|
||||
assert shell_window.stack.currentWidget() is page
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("key", "permission"),
|
||||
[("appointments", "doctor.appointment/lists"), ("consultations", "tcm.diagnosis/lists"),
|
||||
("patients", "firstvisit.myPatient/lists"), ("prescriptions", "tcm.prescription/lists"),
|
||||
("prescription_library", "tcm.prescriptionLibrary/lists")],
|
||||
)
|
||||
def test_shell_hides_global_ai_entries_without_ai_permission(
|
||||
application: QApplication,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
key: str,
|
||||
permission: str,
|
||||
) -> None:
|
||||
navigation = [
|
||||
NavigationItem(
|
||||
"appointments",
|
||||
key,
|
||||
"问诊列表",
|
||||
"号",
|
||||
_ShellPageDouble,
|
||||
("doctor.appointment/lists",),
|
||||
(permission,),
|
||||
)
|
||||
]
|
||||
monkeypatch.setattr(
|
||||
@@ -452,13 +467,19 @@ def test_shell_hides_global_ai_entries_without_ai_permission(
|
||||
window = ShellWindow(
|
||||
object(),
|
||||
{"user": {"name": "无 AI 权限医生"}, "demo_mode": True},
|
||||
permissions={"doctor.appointment/lists"},
|
||||
permissions={permission},
|
||||
)
|
||||
window.show()
|
||||
application.processEvents()
|
||||
|
||||
assert window.assistant_card.isHidden()
|
||||
assert window.ai_top_button.isHidden()
|
||||
assert not window.menu_ai_action.isVisible()
|
||||
assert window.menu_sidebar_action.isVisible()
|
||||
assert window._active_page_key == key
|
||||
assert window.sidebar.width() == 208
|
||||
assert window.topbar.height() == 76
|
||||
assert window.centralWidget().layout().contentsMargins().isNull()
|
||||
|
||||
window.close()
|
||||
application.processEvents()
|
||||
@@ -640,20 +661,135 @@ def test_non_fixed_tabs_close_and_active_close_renavigates(
|
||||
assert shell_window.stack.currentWidget() is shell_window.pages["appointments"]
|
||||
|
||||
|
||||
def test_sidebar_collapse_preserves_active_navigation(
|
||||
def test_topbar_refresh_button_reloads_whichever_page_is_open(
|
||||
application: QApplication,
|
||||
shell_window: ShellWindow,
|
||||
) -> None:
|
||||
assert shell_window.navigate("consultations")
|
||||
"""The button existed in the code but was never added to the layout.
|
||||
|
||||
It also matters more than it used to: list loads no longer raise a banner,
|
||||
so this is the only control that acknowledges a manual reload.
|
||||
"""
|
||||
|
||||
button = shell_window.refresh_button
|
||||
assert button.isVisible()
|
||||
assert button.parentWidget() is shell_window.topbar
|
||||
|
||||
for key in ("consultations", "patients"):
|
||||
assert shell_window.navigate(key)
|
||||
application.processEvents()
|
||||
page = shell_window.pages[key]
|
||||
before = page.refresh_count
|
||||
button.click()
|
||||
application.processEvents()
|
||||
assert page.refresh_count == before + 1
|
||||
|
||||
|
||||
def test_topbar_refresh_button_spins_while_the_page_loads(
|
||||
application: QApplication,
|
||||
shell_window: ShellWindow,
|
||||
) -> None:
|
||||
"""A spin that cannot stop is worse than no spin at all."""
|
||||
|
||||
button = shell_window.refresh_button
|
||||
resting = button.icon().cacheKey()
|
||||
|
||||
button.click()
|
||||
application.processEvents()
|
||||
assert button._timer.isActive()
|
||||
|
||||
# The page double never reports itself busy, so the spin ends as soon as the
|
||||
# minimum has elapsed rather than running for the full cap.
|
||||
button._elapsed = _ElapsedStub(button._MIN_MS + 1)
|
||||
button._tick()
|
||||
|
||||
assert not button._timer.isActive()
|
||||
assert button.icon().cacheKey() == resting
|
||||
|
||||
|
||||
class _ElapsedStub:
|
||||
def __init__(self, value: int) -> None:
|
||||
self._value = value
|
||||
|
||||
def elapsed(self) -> int:
|
||||
return self._value
|
||||
|
||||
def restart(self) -> None:
|
||||
return None
|
||||
|
||||
|
||||
def test_legacy_window_ground_carries_the_only_corner(
|
||||
application: QApplication,
|
||||
shell_window: ShellWindow,
|
||||
) -> None:
|
||||
"""The bottom-most layer is the one that rounds; nothing nests inside it.
|
||||
|
||||
The rail used to paint its own 16 px corner on top of a square canvas, so
|
||||
each window corner showed a corner inside a corner.
|
||||
"""
|
||||
|
||||
assert shell_window.navigate("legacy_reference")
|
||||
application.processEvents()
|
||||
image = shell_window.grab().toImage()
|
||||
|
||||
# Outside the ground's corner there is nothing at all ...
|
||||
assert _logical_pixel(image, 2, 2).alpha() == 0
|
||||
# ... and well inside it the ground is the flat canvas colour, both in the
|
||||
# outer gutter and inside the rail.
|
||||
assert _logical_pixel(image, 8, 200).name().lower() == COLORS["canvas"].lower()
|
||||
assert _logical_pixel(image, 60, 200).name().lower() == COLORS["canvas"].lower()
|
||||
|
||||
|
||||
def test_approved_pages_share_shell_geometry_and_other_pages_restore(
|
||||
application: QApplication,
|
||||
shell_window: ShellWindow,
|
||||
) -> None:
|
||||
"""The six approved pages share chrome; each remaining page keeps its geometry."""
|
||||
|
||||
geometries = set()
|
||||
for key in [*shell_window.pages, "reception", "consultations", "appointments", "patients", "prescriptions", "prescription_library"]:
|
||||
assert shell_window.navigate(key)
|
||||
application.processEvents()
|
||||
geometry = (
|
||||
shell_window.sidebar.width(),
|
||||
shell_window.workspace.x(),
|
||||
shell_window.workspace.width(),
|
||||
shell_window.stack.width(),
|
||||
)
|
||||
if key in {"appointments", "consultations", "reception", "patients", "prescriptions", "prescription_library"}:
|
||||
assert geometry == (208, 208, shell_window.width() - 208, shell_window.width() - 208)
|
||||
assert shell_window.topbar.height() == 76
|
||||
assert shell_window.workspace.y() == 0
|
||||
else:
|
||||
assert geometry == (190, 203, shell_window.width() - 216, shell_window.width() - 216)
|
||||
assert shell_window.topbar.height() == 62
|
||||
assert shell_window.workspace.y() == 13
|
||||
geometries.add(geometry)
|
||||
|
||||
assert len(geometries) == 1, f"navigation moved the shell: {geometries}"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("key", "expanded_width"),
|
||||
[("appointments", 208), ("consultations", 208), ("reception", 208), ("patients", 208), ("prescriptions", 208), ("prescription_library", 208), ("legacy_reference", 190)],
|
||||
)
|
||||
def test_sidebar_collapse_preserves_active_navigation(
|
||||
shell_window: ShellWindow,
|
||||
key: str,
|
||||
expanded_width: int,
|
||||
) -> None:
|
||||
assert shell_window.navigate(key)
|
||||
title = shell_window.nav_buttons[key].text()
|
||||
|
||||
shell_window.toggle_sidebar()
|
||||
assert shell_window.sidebar.width() == 68
|
||||
assert shell_window.nav_buttons["consultations"].text() == ""
|
||||
assert shell_window.nav_buttons["consultations"].isChecked()
|
||||
assert shell_window.nav_buttons[key].text() == ""
|
||||
assert shell_window.nav_buttons[key].isChecked()
|
||||
|
||||
shell_window.toggle_sidebar()
|
||||
assert shell_window.sidebar.width() == 195
|
||||
assert shell_window.nav_buttons["consultations"].text().endswith("问诊列表")
|
||||
assert shell_window.nav_buttons["consultations"].isChecked()
|
||||
assert shell_window.sidebar.width() == expanded_width
|
||||
assert shell_window.nav_buttons[key].text() == title
|
||||
assert shell_window.nav_buttons[key].isChecked()
|
||||
|
||||
|
||||
def test_shell_directional_controls_have_no_unicode_arrow_text(
|
||||
|
||||
Reference in New Issue
Block a user