Files
AareDAQ/tests/unit/gui/test_motor_move_group.py
T
duan_jandClaude Fable 5 a7bd46c14b test: sample padding pixel so movestate colors survive Linux fonts
x=20 sits on a digit glyph with CI's Linux fonts, so the assert compared
an antialiased glyph blend against the pure background. The padding area
(border 1px + padding 6px) can never contain text on any font.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0147jE48bQUTm9AqNQApT6b2
2026-08-24 19:24:29 +02:00

141 lines
4.6 KiB
Python

"""MotorMoveGroup is motor protection UX: typing must never start a move —
targets are staged (orange), sent only by the Move button (green), and the
box returns to neutral when the motor actually arrives."""
import pytest
from PySide6.QtCore import Qt
from aare.gui.widgets.motor_move_group import MotorMoveGroup
from aare.gui.widgets.number_line_edit import NumberLineEdit
@pytest.fixture
def chi(qtbot):
box = NumberLineEdit(-0.2, 40, decimals=1)
qtbot.addWidget(box)
group = MotorMoveGroup()
group.add_box("chi", box)
return group, box
def _type(qtbot, box, text):
box.clear()
qtbot.keyClicks(box, text)
def test_stage_apply_settle(chi, qtbot):
group, box = chi
sent = []
group.applied.connect(sent.append)
_type(qtbot, box, "25.0")
assert box.property("movestate") == "pending"
assert group.button.isEnabled()
assert not sent # typing (even Enter) must not move the motor
group.button.click()
assert sent == [{"chi": 25.0}]
assert box.property("movestate") == "moving"
assert not group.button.isEnabled()
group.update_actual("chi", 10.0) # still travelling
assert box.property("movestate") == "moving"
assert box.value == 25.0 # box keeps showing the target
group.update_actual("chi", 25.05) # within tol -> arrived
assert box.property("movestate") == ""
group.update_actual("chi", 3.0) # neutral boxes track the actual again
assert box.value == 3.0
def test_edit_back_to_current_cancels_pending(chi, qtbot):
group, box = chi
_type(qtbot, box, "5.0")
assert group.button.isEnabled()
_type(qtbot, box, "0.0") # back to the actual position
assert box.property("movestate") == ""
assert not group.button.isEnabled()
def test_over_max_is_invalid_and_never_sent(chi, qtbot):
group, box = chi
sent = []
group.applied.connect(sent.append)
_type(qtbot, box, "500")
assert box.property("invalid") is True # red via existing validator path
group.button.click()
assert not sent # out-of-range target is never applied
assert box.property("movestate") == "pending"
def test_incomplete_entry_stays_pending(chi, qtbot):
_group, box = chi
_type(qtbot, box, "-") # not a number (yet): no tips, no crash
assert box.property("movestate") == "pending"
# Enter on the incomplete text must be swallowed just as quietly
qtbot.keyClick(box, Qt.Key.Key_Return)
assert box.property("movestate") == "pending"
def test_below_min_enter_hints_but_never_sends(chi, qtbot):
group, box = chi
sent = []
group.applied.connect(sent.append)
_type(qtbot, box, "-5") # below the -0.2 bottom
qtbot.keyClick(box, Qt.Key.Key_Return) # triggers the min-value tooltip path
assert not sent # Enter must never start a move
def test_update_limits_reranges_box(chi, qtbot):
_group, box = chi
box.update_limits(-1.0, 50.0)
assert box.range_validator.bottom() == -1.0
assert box.range_validator.top() == 50.0
assert "50" in box.toolTip()
def test_spin_move_state_colors_actually_render(qtbot):
# Regression: movestate used to sit on the spin's inner QLineEdit, which
# the theme paints transparent — the property asserts passed while no
# color ever showed. Sample real pixels through the theme stylesheet.
from PySide6.QtGui import QColor
from PySide6.QtWidgets import QDoubleSpinBox, QPushButton
from aare.gui.styles import (
INPUT_MOVING_BG,
INPUT_PENDING_BG,
THEME_SUNRISE,
build_app_stylesheet,
)
from aare.gui.widgets.motor_move_group import SpinMoveState
spin = QDoubleSpinBox()
spin.setRange(4.0, 20.0)
spin.setStyleSheet(build_app_stylesheet(THEME_SUNRISE))
button = QPushButton()
qtbot.addWidget(spin)
qtbot.addWidget(button)
state = SpinMoveState(spin, button, tol=0.01)
spin.resize(140, 24)
spin.show()
def value_area_color():
# Sample inside the left padding (1px border + 6px QSS padding), not
# the text area: glyph positions are font-dependent and on CI's Linux
# fonts x=20 lands on a digit of "12.40", returning an antialiased
# glyph/background blend instead of the plain background color.
img = spin.grab().toImage()
return img.pixelColor(4, img.height() // 2)
state.update_actual(12.0)
neutral = value_area_color()
spin.setValue(12.4) # user staging
assert value_area_color() == QColor(INPUT_PENDING_BG)
button.click()
assert value_area_color() == QColor(INPUT_MOVING_BG)
state.update_actual(12.398) # arrived within tol
assert value_area_color() == neutral