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
141 lines
4.6 KiB
Python
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
|