Files
AareDAQ/tests/unit/gui/test_beamline_state_panel.py
T
duan_jandClaude Fable 5 8b6ae51ce6 fix: satisfy lint and diff-coverage gates for admin gating
basedpyright requires _admin_tip_anchor to exist after __init__, so the
anchor is created eagerly instead of lazily. Cover the red-tip branch and
the status-bar menu gate; the menu test swaps in a QMenu subclass with a
no-op exec because PySide method lookup ignores class-attr monkeypatches.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 19:54:50 +02:00

159 lines
6.4 KiB
Python

from aarecommon.models.models import BeamlineStateEnum
from PySide6.QtCore import Qt
from aare.gui.panels.beamline_state_panel import BeamlineStatePanel
from aare.gui.styles import STATE_AVAILABLE, STATE_MSG_ERROR, STATE_MSG_INFO, STATE_UNAVAILABLE
def _panel(qtbot, staff=True):
panel = BeamlineStatePanel(staff=staff)
qtbot.addWidget(panel)
return panel
def test_availability_from_maintenance(qtbot):
panel = _panel(qtbot)
panel.set_current_state(BeamlineStateEnum.Maintenance)
assert panel._available_targets() == frozenset({BeamlineStateEnum.SampleExchange})
def test_availability_is_union_of_routes_and_menu_shortcuts(qtbot):
panel = _panel(qtbot)
panel.set_current_state(BeamlineStateEnum.SampleAlignment)
targets = panel._available_targets()
assert BeamlineStateEnum.FluxMeasurement in targets # one-hop route
assert BeamlineStateEnum.SampleExchange in targets # status-bar shortcut
assert BeamlineStateEnum.XtalSnapshot not in targets # two hops away
def test_non_staff_never_reach_admin_only_states(qtbot):
panel = _panel(qtbot, staff=False)
panel.set_current_state(BeamlineStateEnum.SampleAlignment)
targets = panel._available_targets()
assert BeamlineStateEnum.BeamLocation not in targets
assert BeamlineStateEnum.BeamstopAlignment not in targets
assert BeamlineStateEnum.FluxMeasurement not in targets
assert BeamlineStateEnum.DataCollection in targets # non-admin route stays
# Greyed like unreachable states, and the warning tip carries the red
# QToolTip wash while a normal-hint button does not.
gated = panel._buttons[BeamlineStateEnum.BeamLocation]
assert gated.cursor().shape() == Qt.CursorShape.ForbiddenCursor
assert "QToolTip" in gated.styleSheet()
assert "QToolTip" not in panel._buttons[BeamlineStateEnum.XtalSnapshot].styleSheet()
with qtbot.assertNotEmitted(panel.beam_location):
panel._emit_for_state(BeamlineStateEnum.BeamLocation)
panel._on_left_click(BeamlineStateEnum.BeamLocation) # shows the admin tip
def test_no_targets_while_moving_or_unknown(qtbot):
panel = _panel(qtbot)
panel.set_current_state(None)
assert panel._available_targets() == frozenset()
panel.set_current_state(BeamlineStateEnum.Moving)
assert panel._available_targets() == frozenset()
def test_emit_gated_by_availability(qtbot):
panel = _panel(qtbot)
panel.set_current_state(BeamlineStateEnum.Maintenance)
with qtbot.waitSignal(panel.sample_exchange, timeout=1000):
panel._emit_for_state(BeamlineStateEnum.SampleExchange)
with qtbot.assertNotEmitted(panel.flux_measurement):
panel._emit_for_state(BeamlineStateEnum.FluxMeasurement)
def test_active_maintenance_is_red_and_bold(qtbot):
panel = _panel(qtbot)
panel.set_current_state(BeamlineStateEnum.Maintenance)
maintenance = panel._buttons[BeamlineStateEnum.Maintenance]
assert maintenance.font().bold()
assert STATE_MSG_ERROR in maintenance.styleSheet()
def test_availability_palette_and_cursors(qtbot):
panel = _panel(qtbot)
panel.set_current_state(BeamlineStateEnum.Maintenance)
available = panel._buttons[BeamlineStateEnum.SampleExchange]
grey = panel._buttons[BeamlineStateEnum.FluxMeasurement]
assert STATE_AVAILABLE in available.styleSheet()
assert available.cursor().shape() == Qt.CursorShape.PointingHandCursor
assert not available.font().bold()
assert STATE_UNAVAILABLE in grey.styleSheet()
assert grey.cursor().shape() == Qt.CursorShape.ForbiddenCursor
assert grey.toolTip() == ""
assert available.toolTip() != ""
def test_active_non_maintenance_is_blue(qtbot):
panel = _panel(qtbot)
panel.set_current_state(BeamlineStateEnum.SampleAlignment)
active = panel._buttons[BeamlineStateEnum.SampleAlignment]
assert active.font().bold()
assert STATE_MSG_INFO in active.styleSheet()
def test_labels_wrap_when_narrow_and_unwrap_when_wide(qtbot):
panel = _panel(qtbot)
sample_exchange = panel._buttons[BeamlineStateEnum.SampleExchange]
panel.resize(600, 60)
panel._update_label_mode()
assert not panel._single_line
assert sample_exchange.text() == "Manual sample\nexchange"
panel.resize(4000, 60)
panel._update_label_mode()
assert panel._single_line
assert sample_exchange.text() == "Manual sample exchange"
def test_left_click_never_transitions_but_hints(qtbot):
panel = _panel(qtbot)
panel.set_current_state(BeamlineStateEnum.Maintenance)
with qtbot.assertNotEmitted(panel.sample_exchange):
panel._on_left_click(BeamlineStateEnum.SampleExchange) # reminder tip
panel._on_left_click(BeamlineStateEnum.FluxMeasurement) # reachability hint
panel._on_left_click(BeamlineStateEnum.Maintenance) # current: no-op
def test_hover_hint_timer_lifecycle(qtbot):
panel = _panel(qtbot)
panel.set_current_state(BeamlineStateEnum.Maintenance)
panel._set_hovered_state(BeamlineStateEnum.FluxMeasurement)
assert panel._hover_hint_timer.isActive()
panel._show_hover_hint() # runs the unavailable-hint path
panel._clear_hovered_state()
assert not panel._hover_hint_timer.isActive()
assert panel._hovered_state is None
def test_update_daq_status_sets_state(qtbot, daq_status_factory):
panel = _panel(qtbot)
panel.update_daq_status(daq_status_factory(state=BeamlineStateEnum.SampleAlignment))
assert panel._current_state == BeamlineStateEnum.SampleAlignment
def test_pending_target_cleared_on_arrival(qtbot):
panel = _panel(qtbot)
panel.set_current_state(BeamlineStateEnum.Maintenance)
panel._emit_for_state(BeamlineStateEnum.SampleExchange)
assert panel._pending_target_state == BeamlineStateEnum.SampleExchange
panel.set_current_state(BeamlineStateEnum.SampleExchange)
assert panel._pending_target_state is None
def test_pending_target_breathes_only_while_moving(qtbot):
panel = _panel(qtbot)
panel.set_current_state(BeamlineStateEnum.Maintenance)
panel._emit_for_state(BeamlineStateEnum.SampleExchange)
target = panel._buttons[BeamlineStateEnum.SampleExchange]
assert target.graphicsEffect() is None # not Moving yet
panel.set_current_state(BeamlineStateEnum.Moving)
assert panel._breathing_button is target
assert target.graphicsEffect() is not None
panel.set_current_state(BeamlineStateEnum.SampleExchange)
assert panel._breathing_button is None
assert target.graphicsEffect() is None