from __future__ import annotations import os from pathlib import Path os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") import pytest from PySide6.QtCore import QBuffer, QCoreApplication, QEvent, QIODevice, QPoint, Qt from PySide6.QtGui import QColor, QDesktopServices, QFont, QImage, QPainter from PySide6.QtTest import QTest from PySide6.QtWidgets import QApplication from test_diagnosis_drawer_visual import ( ActionRepository, _open_dialog, _select_tab, ) from test_diagnosis_drawer_visual import ( application as application, ) from test_diagnosis_drawer_visual import ( immediate_async as immediate_async, ) from test_diagnosis_media_thumbnail_visual import ( _FakeManager, _hold_remote_load, _png_bytes, ) from doctor_workstation.ui.diagnosis_drawer import _RemoteImageButton from doctor_workstation.ui.diagnosis_media import ImagePreviewDialog from doctor_workstation.ui.theme import _register_preferred_cjk_fonts def _synthetic_image() -> bytes: image = QImage(1600, 1200, QImage.Format.Format_ARGB32) image.fill(QColor("#F4E3D8")) painter = QPainter(image) painter.setRenderHint(QPainter.RenderHint.Antialiasing) painter.setPen(Qt.PenStyle.NoPen) painter.setBrush(QColor("#C37B85")) painter.drawEllipse(440, 160, 720, 900) painter.setPen(QColor("#67434A")) painter.setFont(QFont("Arial", 36)) painter.drawText(image.rect(), Qt.AlignmentFlag.AlignCenter, "SYNTHETIC TEST IMAGE") painter.end() buffer = QBuffer() assert buffer.open(QIODevice.OpenModeFlag.WriteOnly) assert image.save(buffer, "PNG") return bytes(buffer.data()) @pytest.mark.parametrize("mode", ["edit", "readonly"]) def test_real_note_thumbnail_click_keeps_drawer_and_previews_all_eight_images( application: QApplication, monkeypatch: pytest.MonkeyPatch, mode: str, ) -> None: """Exercise the wired drawer click, not just the standalone lightbox class.""" monkeypatch.setattr(_RemoteImageButton, "load_url", _hold_remote_load) external: list[str] = [] monkeypatch.setattr(QDesktopServices, "openUrl", lambda url: external.append(url.toString()) or True) application.setFont(QFont(_register_preferred_cjk_fonts(), 10)) payload = _synthetic_image() def offline_request(preview: ImagePreviewDialog, target: str) -> None: preview._invalidate_request() preview.apply_payload(payload, preview._generation) monkeypatch.setattr(ImagePreviewDialog, "_request", offline_request) dialog = _open_dialog(application, (1440, 900), mode=mode, repository=ActionRepository()) urls = [f"https://media.example.invalid/tongue-{index}.jpg" for index in range(8)] if mode == "edit": _select_tab(dialog, "notes") dialog._fill_notes([{"id": 7001, "note_date": "2026-09-08", "content": "合成测试图片", "tongue_images": urls}]) QCoreApplication.sendPostedEvents(None, QEvent.Type.DeferredDelete) timeline = dialog.drawer_notes_timeline if mode == "edit" else dialog._notes_timelines[0] application.processEvents() thumbs = timeline.findChildren(_RemoteImageButton, "DiagnosisTongueThumb") assert len(thumbs) == 8 for thumb in thumbs: assert thumb._apply_payload(payload, thumb._generation) original_parent = dialog.parentWidget() original_generation = dialog._generation for index in (0, 7, 3): QTest.mouseClick(thumbs[index], Qt.MouseButton.LeftButton, pos=QPoint(24, 32)) application.processEvents() assert external == [], "Thumbnail click must not launch the system browser" preview = dialog._image_preview assert isinstance(preview, ImagePreviewDialog) assert preview.isVisible() and preview.parentWidget() is dialog assert dialog.isVisible() and dialog.parentWidget() is original_parent assert dialog._generation == original_generation assert preview.current_source == urls[index] assert preview.sources == urls assert not preview.canvas.pixmap().isNull() fitted_width = preview.canvas.pixmap().width() QTest.mouseClick(preview.zoom_button, Qt.MouseButton.LeftButton) assert preview.canvas.pixmap().width() == 1600 > fitted_width QTest.mouseClick(preview.next_button, Qt.MouseButton.LeftButton) assert preview.current_source == urls[(index + 1) % 8] QTest.mouseClick(preview.previous_button, Qt.MouseButton.LeftButton) assert preview.current_source == urls[index] if mode == "edit" and index == 7: output = Path(__file__).resolve().parents[2] / "artifacts/patient-render-image-fix" output.mkdir(parents=True, exist_ok=True) assert dialog.grab().save(str(output / "notes-eight-thumbnails.png")) QTest.mouseClick(preview.zoom_button, Qt.MouseButton.LeftButton) application.processEvents() assert preview.grab().save(str(output / "notes-image-preview.png")) if index == 3: QTest.keyClick(preview, Qt.Key.Key_Escape) else: QTest.mouseClick(preview.close_button, Qt.MouseButton.LeftButton) application.processEvents() assert dialog.isVisible() and timeline.isVisible() assert dialog._image_preview is None QCoreApplication.sendPostedEvents(None, QEvent.Type.DeferredDelete) assert external == [] QTest.mouseClick(thumbs[4], Qt.MouseButton.LeftButton, pos=QPoint(24, 32)) preview = dialog._image_preview QTest.mouseClick(preview.external_button, Qt.MouseButton.LeftButton) assert external == [urls[4]] dialog.reject() application.processEvents() assert dialog._image_preview is None @pytest.mark.parametrize("close_with_escape", [False, True]) def test_preview_close_cancels_pending_response( application: QApplication, monkeypatch: pytest.MonkeyPatch, close_with_escape: bool, ) -> None: monkeypatch.setattr(ImagePreviewDialog, "_request", lambda self, target: None) preview = ImagePreviewDialog(["https://media.example.invalid/pending.jpg"]) manager = _FakeManager(preview) manager.queue(_png_bytes(80, 60)) preview._manager = manager monkeypatch.undo() preview.reload_current() reply = manager.replies[-1] generation = preview._generation preview.show() if close_with_escape: QTest.keyClick(preview, Qt.Key.Key_Escape) else: QTest.mouseClick(preview.close_button, Qt.MouseButton.LeftButton) assert reply.aborted assert preview._generation > generation reply.finished.emit() assert preview._pixmap.isNull() application.processEvents()