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

191 lines
8.5 KiB
Python

from __future__ import annotations
import pytest
from PySide6.QtCore import Qt
from PySide6.QtGui import QPalette
from PySide6.QtWidgets import QApplication, QFrame, QLabel, QPushButton
from doctor_workstation.core import PermissionSet
from doctor_workstation.ui.pages import reception as reception_module
from doctor_workstation.ui.pages.reception import ReceptionPage
from doctor_workstation.ui.theme import apply_theme
@pytest.fixture(scope="module")
def application():
app = QApplication.instance() or QApplication([])
apply_theme(app)
return app
def settle(app):
for _ in range(8):
app.processEvents()
@pytest.fixture
def page(application, monkeypatch):
monkeypatch.setattr(reception_module, "run_async", lambda *_args, **_kwargs: None)
widget = ReceptionPage(object(), PermissionSet(["*"]))
widget.resize(1340, 900)
widget.show()
widget.poll_timer.stop()
widget.detail_stack.setCurrentIndex(1)
widget.patient_name_label.setText("林晓岚")
widget.patient_meta_label.setText("女 · 46岁 · 138****1203 | 就诊号:101")
settle(application)
yield widget
widget.close()
widget.deleteLater()
settle(application)
def test_primary_action_and_reading_ink_use_separate_colors(page, application):
page.video_button.setEnabled(True)
settle(application)
assert page.video_button.palette().color(QPalette.ColorRole.Button).name() == "#1769e8"
assert page.video_button.palette().color(QPalette.ColorRole.ButtonText).name() == "#ffffff"
assert page.case_labels["present"].palette().color(QPalette.ColorRole.WindowText).name() == "#273244"
page.video_button.setEnabled(False)
settle(application)
assert page.video_button.palette().color(QPalette.ColorRole.ButtonText).name() == "#8e8f90"
def test_typography_preserves_regular_body_and_distinct_values(page):
body = page.case_labels["present"]
assert body.font().pixelSize() == 14
assert body.font().weight() == 400
assert page.patient_name_label.font().pixelSize() == 22
assert page.patient_name_label.font().weight() == 600
assert page.vital_labels["height"].font().pixelSize() == 18
@pytest.mark.parametrize("width", [1340, 1080, 816])
def test_identity_and_actions_stay_inside_hero_at_both_layouts(page, application, width):
page.resize(width, 900)
settle(application)
hero = page.findChild(QFrame, "ReceptionHero")
for button in (page.notify_button, page.history_button, page.video_button, page.more_button):
assert hero.rect().contains(button.geometry())
assert button.width() >= button.minimumSizeHint().width()
if width == 1340:
assert abs(page.video_button.geometry().center().y() - page.patient_avatar_label.geometry().center().y()) < 20
else:
assert page.video_button.geometry().top() >= page.patient_meta_label.geometry().bottom()
def test_body_growth_stays_readable_and_does_not_cover_actions(page, application):
body = page.case_labels["present"]
body.setText("患者自述近期口干,睡眠较浅,日常饮食与作息较规律。\n" * 12)
settle(application)
assert body.height() >= body.heightForWidth(body.width())
assert page.detail_scroll.verticalScrollBar().maximum() > 0
assert body.textFormat() == Qt.TextFormat.PlainText
@pytest.mark.parametrize("value", ["6.8 mmol/L", "1234.56 mg/g", "≤0.25 mg/L", "—"])
def test_lab_value_keeps_full_plain_text_and_wraps_long_units(page, application, value):
label = page.lab_labels["fasting"]
label.setText(value)
settle(application)
assert label.text() == value
assert label.textFormat() == Qt.TextFormat.PlainText
for width in (55, 120):
layout, size = label._value_layout(width)
assert sum(layout.lineAt(i).textLength() for i in range(layout.lineCount())) == len(value)
assert size.height() == label.heightForWidth(width)
for i in range(layout.lineCount()):
line = layout.lineAt(i)
assert line.naturalTextWidth() <= width + 1
assert line.y() + line.height() <= size.height()
def test_queue_summary_and_navigation_tabs_remain_available(page):
summary = page.findChild(QLabel, "ReceptionQueueSummary")
assert summary.isVisible()
assert [page.detail_tabs.tabText(i) for i in range(page.detail_tabs.count())] == [
"问诊信息", "检查报告", "用药记录", "日常记录", "随访记录", "健康数据"
]
def test_risks_remain_in_full_report_while_preview_matches_approved_image(page, application):
page.ai_analysis_stack.setCurrentWidget(page.ai_analysis_content_page)
settle(application)
page._render_ai_risk_chips([
{"label": "血糖波动风险", "level": "medium"},
{"label": "睡眠质量下降", "level": "low"},
])
settle(application)
assert not page.ai_risk_chip_host.isVisible()
dialog = reception_module._ReceptionAiAnalysisDialog({"qwen": {
"diagnosis_advice": "需要结合病史复核。",
"risk_assessment": [{"label": "血糖波动风险", "level": "medium"}],
"treatment_advice": "完整治疗建议仍可阅读。",
}})
dialog.show()
settle(application)
try:
risk = next(label for label in dialog.findChildren(QLabel) if label.text() == "血糖波动风险")
assert risk.isVisible()
assert risk.width() >= risk.fontMetrics().horizontalAdvance(risk.text())
assert any(label.text() == "完整治疗建议仍可阅读。" for label in dialog.findChildren(QLabel))
finally:
dialog.close()
def test_compact_ai_keeps_full_treatment_data_without_pushing_controls_out_of_view(page, application):
page.ai_analysis_stack.setCurrentWidget(page.ai_analysis_content_page)
page.ai_summary_label.setText("需要结合完整病史与复诊记录核对。" * 25)
page.ai_treatment_label.setText("请结合患者实际情况复核完整分析。" * 25)
page._render_ai_risk_chips([{"label": "血糖波动风险", "level": "medium"}])
settle(application)
assert page.ai_treatment_label.text() == "请结合患者实际情况复核完整分析。" * 25
assert not page.ai_treatment_label.isVisible()
assert not page.ai_question_edit.isVisible()
assert not page.ai_analysis_history_button.isVisible()
actions = [action.text() for action in page.more_button.menu().actions()]
assert "向 AI 提问" in actions
assert "历史 AI 报告" in actions
assert "重新分析 AI 报告" in actions
assert page.ai_analysis_card.height() < 320
def test_patient_switch_clears_previous_condition_tooltip(page):
page.patient_meta_label.setToolTip("上一位患者的病情提示")
page._reset_detail_content({"id": 202, "patient_name": "新患者"})
assert page.patient_meta_label.toolTip() == ""
page.patient_meta_label.setToolTip("已失效的病情提示")
page._render_detail_load_failure("详情暂不可用")
assert page.patient_meta_label.toolTip() == ""
def test_moved_ai_controls_keep_actions_and_enabled_state(application, monkeypatch):
calls = []
monkeypatch.setattr(reception_module, "run_async", lambda *_args, **_kwargs: None)
monkeypatch.setattr(ReceptionPage, "_open_ai_analysis_dialog", lambda self: calls.append("history"))
monkeypatch.setattr(ReceptionPage, "_regenerate_patient_ai_reports", lambda self: calls.append("regenerate"))
monkeypatch.setattr(ReceptionPage, "_open_ai_assistant", lambda self, prompt: calls.append(("ask", prompt)))
widget = ReceptionPage(object(), PermissionSet(["*"]))
try:
widget.poll_timer.stop()
assert not widget.ai_history_action.isEnabled()
assert not widget.ai_regenerate_action.isEnabled()
widget._ai_analysis_histories["qwen"] = [{"diagnosis_advice": "示例分析"}]
widget._ai_analysis_patient_flow = True
widget._ai_analysis_patient_id = 101
widget._sync_ai_analysis_view()
assert widget.ai_history_action.isEnabled()
assert widget.ai_regenerate_action.isEnabled()
widget.ai_history_action.trigger()
widget.ai_regenerate_action.trigger()
next(action for action in widget.more_button.menu().actions() if action.text() == "向 AI 提问").trigger()
widget.ai_assistant_card.findChild(QPushButton, "ReceptionAiTitle").click()
assert calls == ["history", "regenerate", ("ask", ""), ("ask", "")]
widget._ai_analysis_regenerating = True
widget._sync_ai_analysis_view()
assert not widget.ai_regenerate_action.isEnabled()
finally:
widget.close()
widget.deleteLater()
settle(application)