1
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2026-03-04 16:02:51 +01:00
Files
bec_widgets/tests/unit_tests/test_device_signal_input.py

403 lines
15 KiB
Python

from unittest import mock
import pytest
from bec_lib.device import Signal
from qtpy.QtWidgets import QWidget
from bec_widgets.utils.ophyd_kind_util import Kind
from bec_widgets.widgets.control.device_input.base_classes.device_input_base import BECDeviceFilter
from bec_widgets.widgets.control.device_input.base_classes.device_signal_input_base import (
DeviceSignalInputBase,
)
from bec_widgets.widgets.control.device_input.device_combobox.device_combobox import DeviceComboBox
from bec_widgets.widgets.control.device_input.signal_combobox.signal_combobox import SignalComboBox
from bec_widgets.widgets.control.device_input.signal_line_edit.signal_line_edit import (
SignalLineEdit,
)
from .client_mocks import mocked_client
from .conftest import create_widget
class FakeSignal(Signal):
"""Fake signal to test the DeviceSignalInputBase."""
class DeviceInputWidget(DeviceSignalInputBase, QWidget):
"""Thin wrapper around DeviceInputBase to make it a QWidget"""
@pytest.fixture
def device_signal_base(qtbot, mocked_client):
"""Fixture with mocked FilterIO and WidgetIO"""
with mock.patch("bec_widgets.utils.filter_io.FilterIO.set_selection"):
with mock.patch("bec_widgets.utils.widget_io.WidgetIO.set_value"):
widget = create_widget(qtbot=qtbot, widget=DeviceInputWidget, client=mocked_client)
yield widget
@pytest.fixture
def device_signal_combobox(qtbot, mocked_client):
"""Fixture with mocked FilterIO and WidgetIO"""
widget = create_widget(qtbot=qtbot, widget=SignalComboBox, client=mocked_client)
yield widget
@pytest.fixture
def device_signal_line_edit(qtbot, mocked_client):
"""Fixture with mocked FilterIO and WidgetIO"""
widget = create_widget(qtbot=qtbot, widget=SignalLineEdit, client=mocked_client)
yield widget
@pytest.fixture
def test_device_signal_combo(qtbot, mocked_client):
"""Fixture to create a SignalComboBox widget and a DeviceInputWidget widget"""
input = create_widget(
qtbot=qtbot,
widget=DeviceComboBox,
client=mocked_client,
device_filter=[BECDeviceFilter.POSITIONER],
)
signal = create_widget(qtbot=qtbot, widget=SignalComboBox, client=mocked_client)
yield input, signal
def test_device_signal_base_init(device_signal_base):
"""Test if the DeviceSignalInputBase is initialized correctly"""
assert device_signal_base._device is None
assert device_signal_base._signal_filter == set()
assert device_signal_base._signals == []
assert device_signal_base._hinted_signals == []
assert device_signal_base._normal_signals == []
assert device_signal_base._config_signals == []
def test_device_signal_qproperties(device_signal_base):
"""Test if the DeviceSignalInputBase has the correct QProperties"""
assert device_signal_base._signal_filter == set()
device_signal_base.include_config_signals = False
device_signal_base.include_normal_signals = False
assert device_signal_base._signal_filter == set()
device_signal_base.include_config_signals = True
assert device_signal_base._signal_filter == {Kind.config}
device_signal_base.include_normal_signals = True
assert device_signal_base._signal_filter == {Kind.config, Kind.normal}
device_signal_base.include_hinted_signals = True
assert device_signal_base._signal_filter == {Kind.config, Kind.normal, Kind.hinted}
device_signal_base.include_hinted_signals = True
assert device_signal_base._signal_filter == {Kind.config, Kind.normal, Kind.hinted}
device_signal_base.include_hinted_signals = True
assert device_signal_base._signal_filter == {Kind.config, Kind.normal, Kind.hinted}
device_signal_base.include_hinted_signals = False
assert device_signal_base._signal_filter == {Kind.config, Kind.normal}
def test_signal_combobox(qtbot, device_signal_combobox):
"""Test the signal_combobox"""
container = []
def test_cb(input):
container.append(input)
device_signal_combobox.device_signal_changed.connect(test_cb)
assert device_signal_combobox._signals == []
device_signal_combobox.include_normal_signals = True
device_signal_combobox.include_hinted_signals = True
device_signal_combobox.include_config_signals = True
assert device_signal_combobox.signals == []
device_signal_combobox.set_device("samx")
samx = device_signal_combobox.dev.samx
assert device_signal_combobox.signals == [
("samx (readback)", samx._info["signals"].get("readback")),
("setpoint", samx._info["signals"].get("setpoint")),
("velocity", samx._info["signals"].get("velocity")),
]
qtbot.wait(100)
assert container == ["samx (readback)"]
# Set the type of class from the FakeDevice to Signal
fake_signal = FakeSignal(name="fake_signal", info={"device_info": {"signals": {}}})
device_signal_combobox.client.device_manager.add_devices([fake_signal])
device_signal_combobox.set_device("fake_signal")
fake_signal = device_signal_combobox.dev.fake_signal
assert device_signal_combobox.signals == [
("fake_signal", fake_signal._info["signals"].get("fake_signal", {}))
]
assert device_signal_combobox._config_signals == []
assert device_signal_combobox._normal_signals == []
assert device_signal_combobox._hinted_signals == [("fake_signal", {})]
def test_signal_lineedit(device_signal_line_edit):
"""Test the signal_combobox"""
assert device_signal_line_edit._signals == []
device_signal_line_edit.include_normal_signals = True
device_signal_line_edit.include_hinted_signals = True
device_signal_line_edit.include_config_signals = True
assert device_signal_line_edit.signals == []
device_signal_line_edit.set_device("samx")
assert device_signal_line_edit.signals == ["readback", "setpoint", "velocity"]
device_signal_line_edit.set_signal("readback")
assert device_signal_line_edit.text() == "readback"
assert device_signal_line_edit._is_valid_input is True
device_signal_line_edit.setText("invalid")
assert device_signal_line_edit._is_valid_input is False
def test_device_signal_input_base_cleanup(qtbot, mocked_client):
widget = DeviceInputWidget(client=mocked_client)
widget.close()
widget.deleteLater()
mocked_client.callbacks.remove.assert_called_once_with(widget._device_update_register)
def test_signal_combobox_get_signal_name_with_item_data(qtbot, device_signal_combobox):
"""Test get_signal_name returns obj_name from item data when available."""
device_signal_combobox.include_normal_signals = True
device_signal_combobox.include_hinted_signals = True
device_signal_combobox.set_device("samx")
# Select a signal that has item data with obj_name
device_signal_combobox.setCurrentText("samx (readback)")
# get_signal_name should return the obj_name from item data
signal_name = device_signal_combobox.get_signal_name()
assert signal_name == "samx"
def test_signal_combobox_get_signal_name_without_item_data(qtbot, device_signal_combobox):
"""Test get_signal_name returns currentText when no item data available."""
# Add a custom item without item data
device_signal_combobox.addItem("custom_signal")
device_signal_combobox.setCurrentText("custom_signal")
signal_name = device_signal_combobox.get_signal_name()
assert signal_name == "custom_signal"
def test_signal_combobox_get_signal_name_not_found(qtbot, device_signal_combobox):
"""Test get_signal_name when text is not found in combobox (index == -1)."""
# Set editable to allow text that's not in items
device_signal_combobox.setEditable(True)
device_signal_combobox.setCurrentText("nonexistent_signal")
signal_name = device_signal_combobox.get_signal_name()
assert signal_name == "nonexistent_signal"
def test_signal_combobox_get_signal_name_empty(qtbot, device_signal_combobox):
"""Test get_signal_name when combobox is empty."""
device_signal_combobox.clear()
device_signal_combobox.setEditable(True)
device_signal_combobox.setCurrentText("")
signal_name = device_signal_combobox.get_signal_name()
assert signal_name == ""
def test_signal_combobox_get_signal_name_with_velocity(qtbot, device_signal_combobox):
"""Test get_signal_name with velocity signal."""
device_signal_combobox.include_normal_signals = True
device_signal_combobox.include_hinted_signals = True
device_signal_combobox.include_config_signals = True
device_signal_combobox.set_device("samx")
# Select velocity signal
device_signal_combobox.setCurrentText("velocity")
signal_name = device_signal_combobox.get_signal_name()
assert signal_name == "samx_velocity"
def test_signal_combobox_get_signal_config(device_signal_combobox):
device_signal_combobox.include_normal_signals = True
device_signal_combobox.include_hinted_signals = True
device_signal_combobox.set_device("samx")
index = device_signal_combobox.currentIndex()
assert index != -1
expected_config = device_signal_combobox.itemData(index)
assert expected_config is not None
assert device_signal_combobox.get_signal_config() == expected_config
def test_signal_combobox_get_signal_config_disabled(qtbot, mocked_client):
combobox = create_widget(
qtbot=qtbot, widget=SignalComboBox, client=mocked_client, store_signal_config=False
)
combobox.include_normal_signals = True
combobox.include_hinted_signals = True
combobox.set_device("samx")
assert combobox.get_signal_config() is None
def test_signal_combobox_signal_class_filter_by_device(qtbot, mocked_client):
"""Test signal_class_filter restricts signals to the selected device."""
mocked_client.device_manager.get_bec_signals = mock.MagicMock(
return_value=[
("samx", "samx_readback_async", {"obj_name": "samx_readback_async"}),
("samy", "samy_readback_async", {"obj_name": "samy_readback_async"}),
("bpm4i", "bpm4i_value_async", {"obj_name": "bpm4i_value_async"}),
]
)
widget = create_widget(
qtbot=qtbot,
widget=SignalComboBox,
client=mocked_client,
signal_class_filter=["AsyncSignal"],
device="samx",
)
assert widget.signals == ["samx_readback_async"]
assert widget.signal_class_filter == ["AsyncSignal"]
widget.set_device("samy")
assert widget.signals == ["samy_readback_async"]
def test_signal_class_filter_setter_clears_to_kind_filters(qtbot, mocked_client):
"""Clearing signal_class_filter should rebuild list using Kind filters."""
mocked_client.device_manager.get_bec_signals = mock.MagicMock(
return_value=[("samx", "samx_readback_async", {"obj_name": "samx_readback_async"})]
)
widget = create_widget(
qtbot=qtbot,
widget=SignalComboBox,
client=mocked_client,
signal_class_filter=["AsyncSignal"],
device="samx",
)
assert widget.signals == ["samx_readback_async"]
widget.signal_class_filter = []
samx = widget.dev.samx
assert widget.signals == [
("samx (readback)", samx._info["signals"].get("readback")),
("setpoint", samx._info["signals"].get("setpoint")),
("velocity", samx._info["signals"].get("velocity")),
]
def test_signal_class_filter_setter_none_reverts_to_kind_filters(qtbot, mocked_client):
"""Setting signal_class_filter to None should revert to Kind-based filtering."""
mocked_client.device_manager.get_bec_signals = mock.MagicMock(
return_value=[("samx", "samx_readback_async", {"obj_name": "samx_readback_async"})]
)
widget = create_widget(
qtbot=qtbot,
widget=SignalComboBox,
client=mocked_client,
signal_class_filter=["AsyncSignal"],
device="samx",
)
assert widget.signals == ["samx_readback_async"]
widget.signal_class_filter = None
samx = widget.dev.samx
assert widget.signals == [
("samx (readback)", samx._info["signals"].get("readback")),
("setpoint", samx._info["signals"].get("setpoint")),
("velocity", samx._info["signals"].get("velocity")),
]
def test_signal_combobox_set_first_element_as_empty(qtbot, mocked_client):
"""set_first_element_as_empty should insert/remove the empty option."""
widget = create_widget(qtbot=qtbot, widget=SignalComboBox, client=mocked_client)
widget.addItem("item1")
widget.addItem("item2")
widget.set_first_element_as_empty = True
assert widget.itemText(0) == ""
widget.set_first_element_as_empty = False
assert widget.itemText(0) == "item1"
def test_signal_combobox_class_kind_ndim_filters(qtbot, mocked_client):
"""Test class + kind + ndim filters are all applied together."""
mocked_client.device_manager.get_bec_signals = mock.MagicMock(
return_value=[
(
"samx",
"sig1",
{
"obj_name": "samx_sig1",
"kind_str": "hinted",
"describe": {"signal_info": {"ndim": 1}},
},
),
(
"samx",
"sig2",
{
"obj_name": "samx_sig2",
"kind_str": "config",
"describe": {"signal_info": {"ndim": 2}},
},
),
(
"samy",
"sig3",
{
"obj_name": "samy_sig3",
"kind_str": "normal",
"describe": {"signal_info": {"ndim": 1}},
},
),
]
)
widget = create_widget(
qtbot=qtbot,
widget=SignalComboBox,
client=mocked_client,
signal_class_filter=["AsyncSignal"],
ndim_filter=1,
device="samx",
)
# Default kinds are hinted + normal, ndim=1, device=samx
assert widget.signals == ["sig1"]
# Enable config kinds and widen ndim to include sig2
widget.include_config_signals = True
widget.ndim_filter = 2
assert widget.signals == ["sig2"]
def test_signal_combobox_require_device_validation(qtbot, mocked_client):
"""Require device should block validation and list updates without a device."""
mocked_client.device_manager.get_bec_signals = mock.MagicMock(
return_value=[
(
"samx",
"sig1",
{
"obj_name": "samx_sig1",
"kind_str": "hinted",
"describe": {"signal_info": {"ndim": 1}},
},
)
]
)
widget = create_widget(
qtbot=qtbot,
widget=SignalComboBox,
client=mocked_client,
signal_class_filter=["AsyncSignal"],
require_device=True,
)
assert widget.signals == []
widget.set_device("samx")
assert widget.signals == ["sig1"]
resets: list[str] = []
widget.signal_reset.connect(lambda: resets.append("reset"))
widget.check_validity("")
assert resets == ["reset"]