323 lines
11 KiB
Python
323 lines
11 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
from types import SimpleNamespace
|
|
from typing import Any
|
|
from unittest.mock import Mock
|
|
|
|
import pytest
|
|
|
|
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
|
|
|
from PySide6.QtCore import QRect, QSize, Qt, QUrl
|
|
from PySide6.QtWidgets import QApplication, QMainWindow
|
|
|
|
from doctor_workstation.app import ApplicationController
|
|
from doctor_workstation.ui import theme
|
|
from doctor_workstation.video import window as video_window
|
|
from doctor_workstation.video.screenshot import ScreenshotPreviewSize
|
|
from doctor_workstation.video.security import TrustedDocumentPolicy
|
|
|
|
TRUSTED_URL = "https://video.example.test/companion/index.html?mode=call"
|
|
|
|
|
|
class _Window:
|
|
"""Supply geometry and state only, so preview sizing cannot alter other settings."""
|
|
|
|
def __init__(
|
|
self,
|
|
available: QRect | None = None,
|
|
geometry: QRect | None = None,
|
|
) -> None:
|
|
self.current_geometry = QRect(geometry) if geometry is not None else QRect(1362, 18, 540, 356)
|
|
self.available = QRect(available) if available is not None else QRect(0, 0, 1920, 1040)
|
|
self.compact_flag: bool | None = True
|
|
self.maximized = False
|
|
self.full_screen = False
|
|
self.geometry_changes: list[QRect] = []
|
|
|
|
def screen(self) -> SimpleNamespace:
|
|
return SimpleNamespace(availableGeometry=lambda: QRect(self.available))
|
|
|
|
def geometry(self) -> QRect:
|
|
return QRect(self.current_geometry)
|
|
|
|
def setGeometry(self, geometry: QRect) -> None: # noqa: N802 - Qt-compatible double
|
|
self.current_geometry = QRect(geometry)
|
|
self.geometry_changes.append(QRect(geometry))
|
|
|
|
def property(self, name: str) -> bool | None:
|
|
assert name == "doctorSkipOpenGeometry"
|
|
return self.compact_flag
|
|
|
|
def isMaximized(self) -> bool: # noqa: N802 - Qt-compatible double
|
|
return self.maximized
|
|
|
|
def isFullScreen(self) -> bool: # noqa: N802 - Qt-compatible double
|
|
return self.full_screen
|
|
|
|
|
|
@pytest.fixture
|
|
def qapp() -> QApplication:
|
|
return QApplication.instance() or QApplication([])
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("available", "target", "expected_size"),
|
|
[
|
|
(QRect(0, 0, 1920, 1040), None, QSize(1120, 760)),
|
|
(QRect(-1920, -120, 1920, 1040), None, QSize(1120, 760)),
|
|
(QRect(-1280, 180, 1280, 720), QRect(0, 0, 1400, 950), QSize(1232, 672)),
|
|
(QRect(100, 50, 800, 600), None, QSize(752, 552)),
|
|
(QRect(0, 0, 1920, 1040), QRect(40, 60, 1360, 850), QSize(1360, 850)),
|
|
],
|
|
ids=["default", "negative-monitor", "bounded-target", "small-screen", "normal-target"],
|
|
)
|
|
def test_preview_expands_within_current_screen_and_restores(
|
|
available: QRect, target: QRect | None, expected_size: QSize
|
|
) -> None:
|
|
window = _Window(available, QRect(available.x() + 30, available.y() + 30, 440, 300))
|
|
original = window.geometry()
|
|
preview = ScreenshotPreviewSize()
|
|
|
|
preview.open(window, target)
|
|
|
|
expanded = window.geometry()
|
|
assert expanded.size() == expected_size
|
|
assert available.adjusted(24, 24, -24, -24).contains(expanded)
|
|
assert abs(expanded.center().x() - available.center().x()) <= 1
|
|
assert abs(expanded.center().y() - available.center().y()) <= 1
|
|
assert window.compact_flag is True
|
|
|
|
preview.restore(window)
|
|
|
|
assert window.geometry() == original
|
|
assert window.compact_flag is True
|
|
|
|
|
|
def test_repeated_open_preserves_first_geometry_and_restore_is_idempotent() -> None:
|
|
window = _Window()
|
|
original = window.geometry()
|
|
preview = ScreenshotPreviewSize()
|
|
|
|
preview.open(window)
|
|
expanded = window.geometry()
|
|
preview.open(window, QRect(0, 0, 1500, 900))
|
|
|
|
assert window.geometry() == expanded
|
|
assert len(window.geometry_changes) == 1
|
|
|
|
preview.restore(window)
|
|
preview.restore(window)
|
|
|
|
assert window.geometry() == original
|
|
assert window.geometry_changes == [expanded, original]
|
|
|
|
|
|
@pytest.mark.parametrize("state", ["maximized", "full_screen", "large", "no-screen", "tiny-screen"])
|
|
def test_preview_does_not_modify_windows_that_do_not_need_expansion(state: str) -> None:
|
|
window = _Window()
|
|
if state == "large":
|
|
window.current_geometry = QRect(25, 30, 1300, 850)
|
|
elif state == "no-screen":
|
|
window.screen = lambda: None # type: ignore[method-assign, return-value]
|
|
elif state == "tiny-screen":
|
|
window.available = QRect(0, 0, 40, 40)
|
|
else:
|
|
setattr(window, state, True)
|
|
original = window.geometry()
|
|
preview = ScreenshotPreviewSize()
|
|
|
|
preview.open(window)
|
|
preview.restore(window)
|
|
|
|
assert window.geometry() == original
|
|
assert window.geometry_changes == []
|
|
|
|
|
|
@pytest.mark.parametrize("change", ["move", "resize", "compact-flag", "maximize", "fullscreen"])
|
|
def test_restore_preserves_external_window_changes_and_forgets_old_snapshot(change: str) -> None:
|
|
window = _Window()
|
|
preview = ScreenshotPreviewSize()
|
|
preview.open(window)
|
|
expanded = window.geometry()
|
|
if change == "move":
|
|
window.current_geometry.translate(40, 15)
|
|
elif change == "resize":
|
|
window.current_geometry.setWidth(window.current_geometry.width() + 50)
|
|
elif change == "compact-flag":
|
|
window.compact_flag = False
|
|
elif change == "maximize":
|
|
window.maximized = True
|
|
else:
|
|
window.full_screen = True
|
|
external_geometry = window.geometry()
|
|
writes_before_restore = len(window.geometry_changes)
|
|
|
|
preview.restore(window)
|
|
|
|
assert window.geometry() == external_geometry
|
|
assert len(window.geometry_changes) == writes_before_restore
|
|
|
|
# Even when those external changes later disappear, a late close is harmless.
|
|
window.current_geometry = expanded
|
|
window.compact_flag = True
|
|
window.maximized = False
|
|
window.full_screen = False
|
|
preview.restore(window)
|
|
|
|
assert window.geometry() == expanded
|
|
assert len(window.geometry_changes) == writes_before_restore
|
|
|
|
|
|
def test_real_window_preserves_theme_opt_out_minimum_and_flags(qapp: QApplication) -> None:
|
|
theme._install_business_dialog_styling(qapp)
|
|
main = QMainWindow()
|
|
main.setProperty("doctorMainWindow", True)
|
|
main.setGeometry(30, 30, 980, 720)
|
|
main.show()
|
|
window = QMainWindow()
|
|
window.setProperty("doctorSkipOpenGeometry", True)
|
|
window.setWindowFlag(Qt.WindowType.WindowStaysOnTopHint, True)
|
|
window.setMinimumSize(440, 300)
|
|
window.setGeometry(30, 30, 440, 300)
|
|
window.show()
|
|
qapp.processEvents()
|
|
original = window.geometry()
|
|
flags = window.windowFlags()
|
|
minimum = window.minimumSize()
|
|
preview = ScreenshotPreviewSize()
|
|
try:
|
|
preview.open(window, theme.main_window_target_rect(window))
|
|
qapp.processEvents()
|
|
|
|
assert window.width() > original.width()
|
|
assert window.height() > original.height()
|
|
assert window.minimumSize() == minimum
|
|
assert window.windowFlags() == flags
|
|
assert window.property("doctorSkipOpenGeometry") is True
|
|
|
|
preview.restore(window)
|
|
qapp.processEvents()
|
|
|
|
assert window.geometry() == original
|
|
assert window.minimumSize() == minimum
|
|
assert window.windowFlags() == flags
|
|
assert window.property("doctorSkipOpenGeometry") is True
|
|
finally:
|
|
window.close()
|
|
main.close()
|
|
|
|
|
|
def test_diagnosis_restore_clears_screenshot_layer_before_restoring_large_window(
|
|
qapp: QApplication,
|
|
) -> None:
|
|
theme._install_business_dialog_styling(qapp)
|
|
window = QMainWindow()
|
|
window.setProperty("doctorSkipOpenGeometry", True)
|
|
window.setMinimumSize(440, 300)
|
|
window.setWindowFlag(Qt.WindowType.WindowStaysOnTopHint, True)
|
|
window.setGeometry(30, 30, 440, 300)
|
|
window.show()
|
|
qapp.processEvents()
|
|
compact = window.geometry()
|
|
normal = QRect(40, 50, 670, 500)
|
|
preview = ScreenshotPreviewSize()
|
|
restored_layers: list[QRect] = []
|
|
|
|
def restore_screenshot_preview() -> None:
|
|
preview.restore(window)
|
|
restored_layers.append(window.geometry())
|
|
|
|
window.restore_screenshot_preview = restore_screenshot_preview
|
|
controller = SimpleNamespace(
|
|
_video_preview_generation=4,
|
|
_video_preview_state={
|
|
"window": window,
|
|
"geometry": normal,
|
|
"minimum_size": QSize(500, 360),
|
|
"maximized": False,
|
|
"full_screen": False,
|
|
"stays_on_top": False,
|
|
},
|
|
)
|
|
try:
|
|
preview.open(window, QRect(0, 0, 600, 450))
|
|
|
|
ApplicationController._restore_video_preview(controller, 4, activate=False)
|
|
qapp.processEvents()
|
|
|
|
assert restored_layers == [compact]
|
|
assert window.geometry() == normal
|
|
assert window.minimumSize() == QSize(500, 360)
|
|
assert not window.windowFlags() & Qt.WindowType.WindowStaysOnTopHint
|
|
assert window.property("doctorSkipOpenGeometry") is False
|
|
assert controller._video_preview_state is None
|
|
assert controller._video_preview_generation == 5
|
|
|
|
restore_screenshot_preview()
|
|
|
|
assert window.geometry() == normal
|
|
finally:
|
|
window.close()
|
|
|
|
|
|
def _host(**changes: Any) -> SimpleNamespace:
|
|
return SimpleNamespace(
|
|
**{
|
|
"_closing": False,
|
|
"_released": False,
|
|
"_shutdown_requested": False,
|
|
"_media_active": True,
|
|
"_policy": TrustedDocumentPolicy.from_url(TRUSTED_URL, is_local=False),
|
|
"_page": SimpleNamespace(url=lambda: QUrl(TRUSTED_URL)),
|
|
"_screenshot_preview_size": SimpleNamespace(open=Mock()),
|
|
"restore_screenshot_preview": Mock(),
|
|
**changes,
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"changes",
|
|
[
|
|
{"_closing": True},
|
|
{"_released": True},
|
|
{"_shutdown_requested": True},
|
|
{"_media_active": False},
|
|
{"_page": SimpleNamespace(url=lambda: QUrl("about:blank"))},
|
|
{"_page": SimpleNamespace(url=lambda: QUrl("https://untrusted.example.test/"))},
|
|
],
|
|
ids=["closing", "released", "shutdown", "inactive", "blank-document", "untrusted-document"],
|
|
)
|
|
def test_host_does_not_open_preview_when_inactive_or_untrusted(changes: dict[str, Any]) -> None:
|
|
host = _host(**changes)
|
|
|
|
video_window._EmbeddedVideoWindow._set_screenshot_preview_visible(host, True)
|
|
|
|
host._screenshot_preview_size.open.assert_not_called()
|
|
host.restore_screenshot_preview.assert_not_called()
|
|
|
|
|
|
def test_host_uses_normal_window_target_for_trusted_preview(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
host = _host()
|
|
target = QRect(30, 40, 1360, 850)
|
|
target_for_window = Mock(return_value=target)
|
|
monkeypatch.setattr(theme, "main_window_target_rect", target_for_window)
|
|
|
|
video_window._EmbeddedVideoWindow._set_screenshot_preview_visible(host, True)
|
|
|
|
target_for_window.assert_called_once_with(host)
|
|
host._screenshot_preview_size.open.assert_called_once_with(host, target)
|
|
host.restore_screenshot_preview.assert_not_called()
|
|
|
|
|
|
@pytest.mark.parametrize("changes", [{}, {"_shutdown_requested": True}, {"_media_active": False}])
|
|
def test_host_allows_preview_cleanup_after_shutdown_request(changes: dict[str, Any]) -> None:
|
|
host = _host(**changes)
|
|
|
|
video_window._EmbeddedVideoWindow._set_screenshot_preview_visible(host, False)
|
|
|
|
host.restore_screenshot_preview.assert_called_once_with()
|
|
host._screenshot_preview_size.open.assert_not_called()
|