This commit is contained in:
Your Name
2026-08-13 11:54:00 +08:00
parent 07188e82f8
commit 72a5300b08
19 changed files with 1441 additions and 577 deletions
+89
View File
@@ -0,0 +1,89 @@
from __future__ import annotations
import os
from types import SimpleNamespace
from typing import Any
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
import pytest
from PySide6.QtCore import QSettings
from PySide6.QtWidgets import QApplication, QWidget
from doctor_workstation.ui.login import LoginWindow
from doctor_workstation.ui.widgets import BusyOverlay
@pytest.fixture(scope="module")
def application() -> QApplication:
return QApplication.instance() or QApplication([])
def _visible_windows(application: QApplication) -> list[QWidget]:
return [
widget
for widget in application.topLevelWidgets()
if widget.isWindow() and widget.isVisible()
]
def test_busy_overlay_children_are_not_windows(application: QApplication) -> None:
host = QWidget()
host.resize(360, 240)
host.show()
application.processEvents()
before = {id(widget) for widget in _visible_windows(application)}
overlay = BusyOverlay(host, "正在验证账号…")
overlay.setVisible(True)
application.processEvents()
assert overlay.parentWidget() is host
assert not overlay.isWindow()
assert overlay.progress.objectName() == "BusyOverlayProgress"
assert overlay.progress.parentWidget() is overlay
assert overlay.label.parentWidget() is overlay
assert not overlay.progress.isWindow()
assert not overlay.label.isWindow()
extra = [
widget
for widget in _visible_windows(application)
if id(widget) not in before and widget is not host
]
assert extra == []
overlay.setVisible(False)
host.close()
application.processEvents()
def test_login_loading_does_not_spawn_extra_windows(
application: QApplication, tmp_path: Any
) -> None:
settings = QSettings(str(tmp_path / "login.ini"), QSettings.Format.IniFormat)
window = LoginWindow(
object(),
config=SimpleNamespace(
api_base_url="https://127.0.0.1:9",
request_timeout=30,
demo_mode=False,
remembered_account="admin",
),
settings=settings,
)
window.show()
application.processEvents()
before = {id(widget) for widget in _visible_windows(application)}
window._set_loading(True)
application.processEvents()
extra = [widget for widget in _visible_windows(application) if id(widget) not in before]
assert extra == []
assert window.busy_overlay.isVisible()
assert not window.busy_overlay.isWindow()
assert not window.busy_overlay.progress.isWindow()
assert window.busy_overlay.label.text() == "正在验证账号…"
window.close()
application.processEvents()
+87 -6
View File
@@ -7,9 +7,12 @@ from typing import Any
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
import pytest
from PySide6.QtCore import Qt
from PySide6.QtCore import QSize, Qt
from PySide6.QtGui import QColor, QImage, QPainter
from PySide6.QtPdf import QPdfDocument
from PySide6.QtWidgets import QApplication
from doctor_workstation import app as app_module
from doctor_workstation.core import PermissionSet
from doctor_workstation.services import DemoDoctorRepository
from doctor_workstation.ui.dialogs import prescription as dialog_module
@@ -530,8 +533,9 @@ def test_order_payload_and_a4_print_document(
assert "林晓岚" in rendered
assert "黄芪" in rendered
assert "酸枣仁" in rendered
assert "服药前请核对姓名、电话、医生等信息" in rendered
assert '<table align="center" class="paper" width="794" height="1123"' in rendered
assert "服药前请核对姓名、电话、医生等信息以及服法、医嘱等要点" in rendered
assert '<table align="center" class="paper" width="100%"' in rendered
assert 'height="1123"' not in rendered
assert '<col width="44"/><col/><col width="64"/><col width="24"/>' in rendered
assert "Rp." in rendered
assert "药房联" in rendered
@@ -539,18 +543,93 @@ def test_order_payload_and_a4_print_document(
assert "辅方" in rendered
assert "105克" in rendered
assert "84克" in rendered
assert "剂量:</span> 27克" in rendered
assert "剂量:</span> 27克" in rendered
assert "每天2次, 一次2袋, 每袋5g, 温水送服, 饭后" in rendered
assert "主服法:" in rendered
assert "辅服法:" in rendered
assert "处方编号:" in rendered
assert "流转编号(挂号):" in rendered
assert "成都双流甄养堂互联网医院有限公司 联系方式:4001667339" in rendered
assert "四川省成都市双流区黄甲街道黄龙大道二段280号" in rendered
user_rendered = render_prescription_html(prescription, variant="user")
assert "成都双流甄养堂互联网医院 处方笺" in user_rendered
assert "用量" in user_rendered
assert "总量" not in user_rendered
assert "主服法" not in user_rendered
assert "类型:" not in user_rendered
assert "黄芪 (15克)" not in user_rendered
assert "黄芪" in user_rendered
viewer = PrescriptionDetailDialog(prescription)
document_html = viewer.document.toHtml()
assert "用药 (单剂)" in document_html
assert viewer.document.idealWidth() == pytest.approx(794.0)
assert viewer.preview.__class__.__name__ == "_PrescriptionPaperPreview"
assert [viewer.tabs.tabText(index) for index in range(2)] == ["药房联", "处方联"]
viewer.close()
order.close()
application.processEvents()
def test_prescription_pdf_export_writes_visible_content(
application: QApplication,
tmp_path: Any,
) -> None:
prescription = {
"id": 12,
"sn": "CF-12",
"patient_name": "林晓岚",
"phone": "13800000000",
"gender": 0,
"age": 33,
"clinical_diagnosis": "脾气虚",
"doctor_name": "周医生",
"audit_status": 1,
"dose_count": 7,
"herbs": [{"name": "黄芪", "dosage": 15, "formula_type": "主方"}],
}
image = dialog_module.render_prescription_slip_image(prescription)
ink = dialog_module.count_slip_ink_pixels(image)
page = QImage(1240, 1754, QImage.Format.Format_RGB32)
page.fill(QColor("#ffffff"))
painter = QPainter(page)
dialog_module._draw_slip_image_on_page(painter, image)
painter.end()
page_ink = dialog_module.count_slip_ink_pixels(page)
png_path = tmp_path / "prescription-slip.png"
assert image.save(str(png_path), "PNG")
viewer = PrescriptionDetailDialog(prescription)
output = tmp_path / "prescription-slip.pdf"
app_module._install_chinese_translations(application)
viewer.export_pdf(output)
payload = output.read_bytes()
pdf = QPdfDocument()
assert pdf.load(str(output)) == QPdfDocument.Error.None_
assert pdf.pageCount() == 1
page_size = pdf.pagePointSize(0)
rendered_page = pdf.render(0, QSize(1240, 1754))
visible_ink = sum(
1
for y in range(0, rendered_page.height(), 3)
for x in range(0, rendered_page.width(), 3)
if (color := rendered_page.pixelColor(x, y)).alpha() > 0 and color.lightness() < 245
)
viewer.close()
application.processEvents()
assert dialog_module._ensure_slip_fonts()
assert image.format() == QImage.Format.Format_RGB32
assert image.width() >= 700
assert image.height() >= 400
assert ink > 200
assert page_ink > 200
assert png_path.stat().st_size > 40_000
assert payload.startswith(b"%PDF")
assert b"/Image" in payload or b"/XObject" in payload
assert len(payload) > 50_000
assert page_size.width() == pytest.approx(595.0, abs=2.0)
assert page_size.height() == pytest.approx(842.0, abs=2.0)
assert visible_ink > 1_000
def test_prescription_detail_can_open_immutable_case_record_tab(
application: QApplication,
) -> None:
@@ -569,7 +648,9 @@ def test_prescription_detail_can_open_immutable_case_record_tab(
}
viewer = PrescriptionDetailDialog(prescription, initial_tab="case")
assert viewer.tabs.count() == 2
assert viewer.tabs.count() == 3
assert viewer.tabs.tabText(0) == "药房联"
assert viewer.tabs.tabText(1) == "处方联"
assert viewer.tabs.tabText(viewer.tabs.currentIndex()) == "详细病历"
assert viewer.case_document is not None
case_html = viewer.case_document.toHtml()
+8 -1
View File
@@ -19,8 +19,9 @@ class _ShellPageDouble(QWidget):
*,
permissions: Any,
current_user: Any,
parent: QWidget | None = None,
) -> None:
super().__init__()
super().__init__(parent)
self.permissions = permissions
self.current_user = current_user
self.refresh_count = 0
@@ -93,6 +94,12 @@ def test_shell_matches_admin_geometry_at_both_acceptance_sizes(
assert image.pixelColor(220, 110).name().lower() == "#f5f7fb"
def test_registered_pages_are_not_top_level_windows(shell_window: ShellWindow) -> None:
for page in shell_window.pages.values():
assert page.parentWidget() is shell_window.stack
assert not page.isWindow()
def test_every_visible_page_navigates_and_visited_tabs_track_active_page(
shell_window: ShellWindow,
) -> None:
+10 -11
View File
@@ -3,7 +3,8 @@ from __future__ import annotations
from types import SimpleNamespace
from typing import Any
from PySide6.QtCore import QSettings
from PySide6.QtCore import QCoreApplication, QSettings
from PySide6.QtGui import QPageSize
from PySide6.QtWidgets import QApplication, QDialogButtonBox, QMessageBox
from doctor_workstation import app as app_module
@@ -319,7 +320,9 @@ def test_server_settings_panel_keeps_controls_separated_at_minimum_window(
assert window.server_url_label.geometry().bottom() < window.server_url_edit.geometry().top()
assert window.server_url_edit.geometry().bottom() < window.timeout_label.geometry().top()
assert window.timeout_spin.geometry().right() < window.save_server_button.geometry().left()
assert window.timeout_label.geometry().bottom() < window.allow_self_signed_check.geometry().top()
assert (
window.timeout_label.geometry().bottom() < window.allow_self_signed_check.geometry().top()
)
assert window.allow_self_signed_check.geometry().bottom() < window.server_hint.geometry().top()
window.allow_self_signed_check.setChecked(True)
@@ -425,9 +428,7 @@ def test_qt_standard_dialog_buttons_are_localized_to_chinese() -> None:
app_module._install_chinese_translations(application)
question = QMessageBox()
question.setStandardButtons(
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.Cancel
)
question.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.Cancel)
assert question.button(QMessageBox.StandardButton.Yes).text() == ""
assert question.button(QMessageBox.StandardButton.Cancel).text() == "取消"
@@ -439,12 +440,12 @@ def test_qt_standard_dialog_buttons_are_localized_to_chinese() -> None:
assert buttons.button(QDialogButtonBox.StandardButton.Ok).text() == "确定"
assert buttons.button(QDialogButtonBox.StandardButton.Save).text() == "保存"
assert buttons.button(QDialogButtonBox.StandardButton.Close).text() == "关闭"
assert QCoreApplication.translate("QPageSize", "A4") == "A4"
assert QPageSize(QPageSize.PageSizeId.A4).isValid()
def test_friendly_error_hides_english_technical_messages() -> None:
message = friendly_error(
TypeError("invoke() takes 2 positional arguments but 3 were given")
)
message = friendly_error(TypeError("invoke() takes 2 positional arguments but 3 were given"))
assert message == "程序执行失败,请重试;若问题持续出现,请联系管理员。"
assert friendly_error(RuntimeError("API response envelope must be an object")) == (
"服务器返回的数据格式不正确,请联系管理员检查接口。"
@@ -463,9 +464,7 @@ def test_certificate_error_opens_server_settings(tmp_path: Any) -> None:
)
window = LoginWindow(object(), config=config, settings=settings)
window._on_login_error(
RuntimeError("[SSL: CERTIFICATE_VERIFY_FAILED] self-signed certificate")
)
window._on_login_error(RuntimeError("[SSL: CERTIFICATE_VERIFY_FAILED] self-signed certificate"))
assert window.server_toggle.isChecked()
assert not window.server_panel.isHidden()