diff --git a/bec_widgets/widgets/device_inputs/device_combobox/device_combobox.py b/bec_widgets/widgets/device_inputs/device_combobox/device_combobox.py index 713dcb25..7b348181 100644 --- a/bec_widgets/widgets/device_inputs/device_combobox/device_combobox.py +++ b/bec_widgets/widgets/device_inputs/device_combobox/device_combobox.py @@ -9,33 +9,40 @@ if TYPE_CHECKING: class DeviceComboBox(DeviceInputBase, QComboBox): + """ + Line edit widget for device input with autocomplete for device names. + + Args: + parent: Parent widget. + client: BEC client object. + config: Device input configuration. + gui_id: GUI ID. + device_filter: Device filter, name of the device class. + default_device: Default device name. + arg_name: Argument name, can be used for the other widgets which has to call some other function in bec using correct argument names. + """ + def __init__( self, parent=None, client=None, config: DeviceInputConfig = None, gui_id: str | None = None, - device_filter: str = None, - default_device: str = None, + device_filter: str | None = None, + default_device: str | None = None, + arg_name: str | None = None, ): - super().__init__( - client=client, - config=config, - gui_id=gui_id, - device_filter=device_filter, - default_device=default_device, - ) + super().__init__(client=client, config=config, gui_id=gui_id) QComboBox.__init__(self, parent=parent) self.populate_combobox() - self._set_defaults() - def _set_defaults(self): - """Set the default device and device filter.""" - if self.config.default_device is not None: - self.set_default_device(self.config.default_device) - if self.config.device_filter is not None: - self.set_device_filter(self.config.device_filter) + if arg_name is not None: + self.config.arg_name = arg_name + if device_filter is not None: + self.set_device_filter(device_filter) + if default_device is not None: + self.set_default_device(default_device) def set_device_filter(self, device_filter: str): """ diff --git a/bec_widgets/widgets/device_inputs/device_input_base.py b/bec_widgets/widgets/device_inputs/device_input_base.py index ded61a82..a3d6b883 100644 --- a/bec_widgets/widgets/device_inputs/device_input_base.py +++ b/bec_widgets/widgets/device_inputs/device_input_base.py @@ -6,6 +6,7 @@ from bec_widgets.utils import BECConnector, ConnectionConfig class DeviceInputConfig(ConnectionConfig): device_filter: str | list[str] | None = None default_device: str | None = None + arg_name: str | None = None class DeviceInputBase(BECConnector): @@ -14,15 +15,7 @@ class DeviceInputBase(BECConnector): on the current text of the widget. """ - def __init__( - self, - parent=None, - client=None, - config=None, - gui_id=None, - device_filter: str = None, - default_device: str = None, - ): + def __init__(self, client=None, config=None, gui_id=None): if config is None: config = DeviceInputConfig(widget_class=self.__class__.__name__) else: @@ -32,10 +25,6 @@ class DeviceInputBase(BECConnector): super().__init__(client=client, config=config, gui_id=gui_id) self.get_bec_shortcuts() - if device_filter is not None: - self.set_device_filter(device_filter) - if default_device is not None: - self.set_default_device(default_device) self._devices = [] @property @@ -75,8 +64,7 @@ class DeviceInputBase(BECConnector): Args: default_device(str): Default device name. """ - if default_device not in self.get_device_list(self.config.device_filter): - raise ValueError(f"Default device {default_device} is not in the device list.") + self.validate_device(default_device) self.config.default_device = default_device def get_device_list(self, filter: str | list[str] | None = None) -> list[str]: @@ -128,5 +116,5 @@ class DeviceInputBase(BECConnector): Args: device(str): Device to validate. """ - if device not in self.get_device_list(): + if device not in self.get_device_list(self.config.device_filter): raise ValueError(f"Device {device} is not valid.") diff --git a/bec_widgets/widgets/device_inputs/device_line_edit/device_line_edit.py b/bec_widgets/widgets/device_inputs/device_line_edit/device_line_edit.py index bd26cbbc..f90bf3e1 100644 --- a/bec_widgets/widgets/device_inputs/device_line_edit/device_line_edit.py +++ b/bec_widgets/widgets/device_inputs/device_line_edit/device_line_edit.py @@ -9,37 +9,42 @@ if TYPE_CHECKING: class DeviceLineEdit(DeviceInputBase, QLineEdit): + """ + Line edit widget for device input with autocomplete for device names. + + Args: + parent: Parent widget. + client: BEC client object. + config: Device input configuration. + gui_id: GUI ID. + device_filter: Device filter, name of the device class. + default_device: Default device name. + arg_name: Argument name, can be used for the other widgets which has to call some other function in bec using correct argument names. + """ + def __init__( self, parent=None, client=None, config: DeviceInputConfig = None, gui_id: str | None = None, - device_filter: str | list[str] = None, - default_device: str = None, + device_filter: str | list[str] | None = None, + default_device: str | None = None, + arg_name: str | None = None, ): QLineEdit.__init__(self, parent=parent) - DeviceInputBase.__init__( - self, - client=client, - config=config, - gui_id=gui_id, - device_filter=device_filter, - default_device=default_device, - ) + DeviceInputBase.__init__(self, client=client, config=config, gui_id=gui_id) self.completer = QCompleter(self) self.setCompleter(self.completer) - self.populate_completer() - self._set_defaults() - def _set_defaults(self): - """Set the default device and device filter.""" - if self.config.default_device is not None: - self.set_default_device(self.config.default_device) - if self.config.device_filter is not None: - self.set_device_filter(self.config.device_filter) + if arg_name is not None: + self.config.arg_name = arg_name + if device_filter is not None: + self.set_device_filter(device_filter) + if default_device is not None: + self.set_default_device(default_device) def set_device_filter(self, device_filter: str | list[str]): """ diff --git a/tests/unit_tests/client_mocks.py b/tests/unit_tests/client_mocks.py index 4aeb732f..22e6ff49 100644 --- a/tests/unit_tests/client_mocks.py +++ b/tests/unit_tests/client_mocks.py @@ -47,6 +47,7 @@ class FakePositioner(FakeDevice): super().__init__(name, enabled) self.limits = limits if limits is not None else [0, 0] self.read_value = read_value + self.name = name def set_read_value(self, value): self.read_value = value diff --git a/tests/unit_tests/test_device_input_base.py b/tests/unit_tests/test_device_input_base.py new file mode 100644 index 00000000..e7599727 --- /dev/null +++ b/tests/unit_tests/test_device_input_base.py @@ -0,0 +1,66 @@ +import pytest + +from bec_widgets.widgets.device_inputs.device_input_base import DeviceInputBase + +from .client_mocks import mocked_client + + +@pytest.fixture +def device_input_base(mocked_client): + widget = DeviceInputBase(client=mocked_client) + yield widget + + +def test_device_input_base_init(device_input_base): + assert device_input_base is not None + assert device_input_base.client is not None + assert isinstance(device_input_base, DeviceInputBase) + assert device_input_base.config.widget_class == "DeviceInputBase" + assert device_input_base.config.device_filter is None + assert device_input_base.config.default_device is None + assert device_input_base.devices == [] + + +def test_device_input_base_init_with_config(mocked_client): + config = { + "widget_class": "DeviceInputBase", + "gui_id": "test_gui_id", + "device_filter": "FakePositioner", + "default_device": "samx", + } + widget = DeviceInputBase(client=mocked_client, config=config) + assert widget.config.gui_id == "test_gui_id" + assert widget.config.device_filter == "FakePositioner" + assert widget.config.default_device == "samx" + + +def test_device_input_base_set_device_filter(device_input_base): + device_input_base.set_device_filter("FakePositioner") + assert device_input_base.config.device_filter == "FakePositioner" + + +def test_device_input_base_set_device_filter_error(device_input_base): + with pytest.raises(ValueError) as excinfo: + device_input_base.set_device_filter("NonExistingClass") + assert "Device filter NonExistingClass is not in the device list." in str(excinfo.value) + + +def test_device_input_base_set_default_device(device_input_base): + device_input_base.set_default_device("samx") + assert device_input_base.config.default_device == "samx" + + +def test_device_input_base_set_default_device_error(device_input_base): + with pytest.raises(ValueError) as excinfo: + device_input_base.set_default_device("NonExistingDevice") + assert "Default device NonExistingDevice is not in the device list." in str(excinfo.value) + + +def test_device_input_base_get_device_list(device_input_base): + devices = device_input_base.get_device_list("FakePositioner") + assert devices == ["samx", "samy", "aptrx", "aptry"] + + +def test_device_input_base_get_filters(device_input_base): + filters = device_input_base.get_available_filters() + assert filters == {"FakePositioner", "FakeDevice"} diff --git a/tests/unit_tests/test_device_input_widgets.py b/tests/unit_tests/test_device_input_widgets.py new file mode 100644 index 00000000..95d7b76f --- /dev/null +++ b/tests/unit_tests/test_device_input_widgets.py @@ -0,0 +1,176 @@ +import pytest + +from bec_widgets.widgets.device_inputs.device_combobox.device_combobox import DeviceComboBox +from bec_widgets.widgets.device_inputs.device_line_edit.device_line_edit import DeviceLineEdit + +from .client_mocks import mocked_client + + +@pytest.fixture +def device_input_combobox(qtbot, mocked_client): + widget = DeviceComboBox(client=mocked_client) + qtbot.addWidget(widget) + qtbot.waitExposed(widget) + yield widget + widget.close() + + +@pytest.fixture +def device_input_combobox_with_config(qtbot, mocked_client): + config = { + "widget_class": "DeviceComboBox", + "gui_id": "test_gui_id", + "device_filter": "FakePositioner", + "default_device": "samx", + "arg_name": "test_arg_name", + } + widget = DeviceComboBox(client=mocked_client, config=config) + qtbot.addWidget(widget) + qtbot.waitExposed(widget) + yield widget + widget.close() + + +@pytest.fixture +def device_input_combobox_with_kwargs(qtbot, mocked_client): + widget = DeviceComboBox( + client=mocked_client, + gui_id="test_gui_id", + device_filter="FakePositioner", + default_device="samx", + arg_name="test_arg_name", + ) + qtbot.addWidget(widget) + qtbot.waitExposed(widget) + yield widget + widget.close() + + +def test_device_input_combobox_init(device_input_combobox): + assert device_input_combobox is not None + assert device_input_combobox.client is not None + assert isinstance(device_input_combobox, DeviceComboBox) + assert device_input_combobox.config.widget_class == "DeviceComboBox" + assert device_input_combobox.config.device_filter is None + assert device_input_combobox.config.default_device is None + assert device_input_combobox.devices == [ + "samx", + "samy", + "aptrx", + "aptry", + "gauss_bpm", + "gauss_adc1", + "gauss_adc2", + "gauss_adc3", + "bpm4i", + "bpm3a", + "bpm3i", + "eiger", + ] + + +def test_device_input_combobox_init_with_config(device_input_combobox_with_config): + assert device_input_combobox_with_config.config.gui_id == "test_gui_id" + assert device_input_combobox_with_config.config.device_filter == "FakePositioner" + assert device_input_combobox_with_config.config.default_device == "samx" + assert device_input_combobox_with_config.config.arg_name == "test_arg_name" + + +def test_device_input_combobox_init_with_kwargs(device_input_combobox_with_kwargs): + assert device_input_combobox_with_kwargs.config.gui_id == "test_gui_id" + assert device_input_combobox_with_kwargs.config.device_filter == "FakePositioner" + assert device_input_combobox_with_kwargs.config.default_device == "samx" + assert device_input_combobox_with_kwargs.config.arg_name == "test_arg_name" + + +def test_get_device_from_input_combobox_init(device_input_combobox): + device_input_combobox.setCurrentIndex(0) + device_text = device_input_combobox.currentText() + current_device = device_input_combobox.get_device() + + assert current_device.name == device_text + + +@pytest.fixture +def device_input_line_edit(qtbot, mocked_client): + widget = DeviceLineEdit(client=mocked_client) + qtbot.addWidget(widget) + qtbot.waitExposed(widget) + yield widget + widget.close() + + +@pytest.fixture +def device_input_line_edit_with_config(qtbot, mocked_client): + config = { + "widget_class": "DeviceLineEdit", + "gui_id": "test_gui_id", + "device_filter": "FakePositioner", + "default_device": "samx", + "arg_name": "test_arg_name", + } + widget = DeviceLineEdit(client=mocked_client, config=config) + qtbot.addWidget(widget) + qtbot.waitExposed(widget) + yield widget + widget.close() + + +@pytest.fixture +def device_input_line_edit_with_kwargs(qtbot, mocked_client): + widget = DeviceLineEdit( + client=mocked_client, + gui_id="test_gui_id", + device_filter="FakePositioner", + default_device="samx", + arg_name="test_arg_name", + ) + qtbot.addWidget(widget) + qtbot.waitExposed(widget) + yield widget + widget.close() + + +def test_device_input_line_edit_init(device_input_line_edit): + assert device_input_line_edit is not None + assert device_input_line_edit.client is not None + assert isinstance(device_input_line_edit, DeviceLineEdit) + assert device_input_line_edit.config.widget_class == "DeviceLineEdit" + assert device_input_line_edit.config.device_filter is None + assert device_input_line_edit.config.default_device is None + assert device_input_line_edit.devices == [ + "samx", + "samy", + "aptrx", + "aptry", + "gauss_bpm", + "gauss_adc1", + "gauss_adc2", + "gauss_adc3", + "bpm4i", + "bpm3a", + "bpm3i", + "eiger", + ] + + +def test_device_input_line_edit_init_with_config(device_input_line_edit_with_config): + assert device_input_line_edit_with_config.config.gui_id == "test_gui_id" + assert device_input_line_edit_with_config.config.device_filter == "FakePositioner" + assert device_input_line_edit_with_config.config.default_device == "samx" + assert device_input_line_edit_with_config.config.arg_name == "test_arg_name" + + +def test_device_input_line_edit_init_with_kwargs(device_input_line_edit_with_kwargs): + assert device_input_line_edit_with_kwargs.config.gui_id == "test_gui_id" + assert device_input_line_edit_with_kwargs.config.device_filter == "FakePositioner" + assert device_input_line_edit_with_kwargs.config.default_device == "samx" + assert device_input_line_edit_with_kwargs.config.arg_name == "test_arg_name" + + +def test_get_device_from_input_line_edit_init(device_input_line_edit): + device_input_line_edit.setText("samx") + device_text = device_input_line_edit.text() + current_device = device_input_line_edit.get_device() + + assert current_device.name == device_text