Files
AareDAQ/tests/unit/gui/test_camera_image.py
T
duan_jandClaude Fable 5 7b4764e7ca test: cover the camera overlays, theme migration, layout heal, and splash
Diff coverage vs main was 77%, under the 80% CI gate; the new camera
help/error/badge code and the theme+layout main_window paths were the
uncovered bulk. Locally: 82%.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 10:35:55 +02:00

163 lines
5.4 KiB
Python

import pytest
from aarecommon.math.coordinate import Coordinate, SmargonCoordinate
from aarecommon.math.diffraction_geometry import DiffractionGeometry
from aarecommon.math.sample_geometry import SampleGeometryModel
from aarecommon.models.models import (
BeamlineStateEnum,
BeamlineStatus,
CrystalSize,
DAQStatusModel,
SampleCameraSettings,
SessionsStateEnum,
SessionStatus,
)
from PySide6.QtCore import QEvent, QPoint, QPointF, Qt
from PySide6.QtGui import QMouseEvent
from aare.gui.scan_logic.raster_grid_manager import RasterGridManager
from aare.gui.styles import THEME_SUNRISE, THEME_SUNSET
from aare.gui.widgets.camera_image import SampleCameraImageLabel
def _geom() -> SampleGeometryModel:
return SampleGeometryModel(
beam_location_pxl=Coordinate(x=1000, y=1000),
pixel_in_mm=0.001,
aerotech=Coordinate(),
aerotech_meas=Coordinate(),
smargon=SmargonCoordinate(sh_mm=Coordinate(), phi_deg=0, chi_deg=0),
omega_deg=0,
beam_size_mm=Coordinate(x=0.01, y=0.01),
)
def _status(*, busy: bool, session: SessionsStateEnum) -> DAQStatusModel:
return DAQStatusModel(
geom=_geom(),
diffraction=DiffractionGeometry(
energy_keV=12.4,
dtz_mm=100.0,
detector_size_pxl=(1553, 1630),
pixel_size_mm=0.150,
beam_center_pxl=(750.0, 750.0),
detector_description="PILATUS 4",
detector_serial_number="1",
poni_rot1_rad=0.0,
poni_rot2_rad=0.0,
),
bl=BeamlineStatus(
name="SIMULATED",
ring_current_mA=400.0,
front_light=50.0,
back_light=50.0,
cryojet_K=100.0,
shutter_open=False,
exp_shutter_open=False,
flux_ph_s=1e12,
sample_camera=SampleCameraSettings(gain=1.0, exposure=0.02),
transmission=1.0,
zoom=1.0,
commissioning_mode=False,
dtz_min=120.0,
dtz_max=1600.0,
),
state=BeamlineStateEnum.SampleAlignment,
busy=busy,
session=SessionStatus(session=session, current_pgroup="p123", staff=True),
crystal_size=CrystalSize(x=0, y=0, z=0),
)
def _mouse_move(widget, pos: QPoint) -> None:
# qtbot.mouseMove drives the real cursor, which the offscreen platform
# ignores — deliver the move event directly instead.
event = QMouseEvent(
QEvent.Type.MouseMove,
QPointF(pos),
QPointF(widget.mapToGlobal(pos)),
Qt.MouseButton.NoButton,
Qt.MouseButton.NoButton,
Qt.KeyboardModifier.NoModifier,
)
widget.mouseMoveEvent(event)
@pytest.fixture
def camera(qtbot):
geom = _geom()
label = SampleCameraImageLabel(geom=geom, raster=RasterGridManager(geom), default_image=None)
qtbot.addWidget(label)
label.resize(800, 600)
return label
def test_help_badge_click_toggles_cheatsheet(camera, qtbot):
camera.grab() # paint records the collapsed "?" badge hit rect
badge = camera._help_hit_rect
assert badge is not None
assert not camera._help_expanded
qtbot.mouseClick(camera.viewport(), Qt.MouseButton.LeftButton, pos=badge.center().toPoint())
assert camera._help_expanded
camera.grab() # expanded overlay: hit rect grows to the whole cheatsheet box
box = camera._help_hit_rect
assert box is not None
assert box.height() > badge.height()
qtbot.mouseClick(camera.viewport(), Qt.MouseButton.LeftButton, pos=box.center().toPoint())
assert not camera._help_expanded
def test_camera_error_message_rewords_and_draws(camera):
camera.set_camera_available(False)
camera.set_camera_error_message("Sample camera feed unavailable: cable unplugged")
assert camera._camera_error_message == (
"Sample camera feed unavailable because cable unplugged"
)
camera.grab() # exercises the bottom-center unavailable overlay text path
camera.set_camera_available(True)
assert camera._camera_error_message is None
def test_busy_warning_is_not_a_click_target(camera):
camera.update_daq_status(_status(busy=True, session=SessionsStateEnum.OwnedByYou))
style = camera._busy_overlay_style
assert style is not None
assert style.text == "BEAMLINE BUSY"
camera.grab()
assert camera._session_badge_rect is None
def test_vacant_badge_hover_click_and_theme(camera, qtbot):
camera.update_daq_status(_status(busy=False, session=SessionsStateEnum.Vacant))
style = camera._busy_overlay_style
assert style is not None
assert style.text == "In viewing mode"
assert style.subtext # the grab-baton hint line
camera.grab() # paint records the badge rect
badge = camera._session_badge_rect
assert badge is not None
_mouse_move(camera, badge.center())
assert camera._session_badge_hovered
camera.grab() # hover fill, light-theme darken branch
camera.set_theme(THEME_SUNSET)
assert camera._dark_theme
camera.grab() # hover fill, sunset brighten branch
camera.set_theme(THEME_SUNRISE)
assert not camera._dark_theme
_mouse_move(camera, QPoint(1, 1))
assert not camera._session_badge_hovered
_mouse_move(camera, badge.center())
camera.leaveEvent(QEvent(QEvent.Type.Leave))
assert not camera._session_badge_hovered
with qtbot.waitSignal(camera.session_badge_clicked, timeout=1000):
qtbot.mouseClick(camera.viewport(), Qt.MouseButton.LeftButton, pos=badge.center())