0
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-07-13 19:21:50 +02:00

test: add tests for config dialog

This commit is contained in:
2025-06-06 14:18:36 +02:00
committed by David Perl
parent 886964bb54
commit a9613a07b0
6 changed files with 148 additions and 38 deletions

View File

@ -430,7 +430,7 @@ class ListMetadataField(DynamicFormItem):
return self._data return self._data
def setValue(self, value: list): def setValue(self, value: list):
if set(map(type, value)) != {self._types.item}: if set(map(type, value)) | {self._types.item} != {self._types.item}:
raise ValueError(f"This widget only accepts items of type {self._types.item}") raise ValueError(f"This widget only accepts items of type {self._types.item}")
self._repop(value) self._repop(value)

View File

@ -193,8 +193,8 @@ class DictBackedTable(QWidget):
def clear(self): def clear(self):
self._table_model.replaceData({}) self._table_model.replaceData({})
def replace_data(self, data: dict): def replace_data(self, data: dict | None):
self._table_model.replaceData(data) self._table_model.replaceData(data or {})
def delete_selected_rows(self): def delete_selected_rows(self):
"""Delete rows which are part of the selection model""" """Delete rows which are part of the selection model"""

View File

@ -28,9 +28,13 @@ class DeviceConfigDialog(BECWidget, QDialog):
RPC = False RPC = False
def __init__( def __init__(
self, parent=None, device: str | None = None, config_helper: ConfigHelper | None = None self,
parent=None,
device: str | None = None,
config_helper: ConfigHelper | None = None,
**kwargs,
): ):
super().__init__(parent=parent) super().__init__(parent=parent, **kwargs)
self._config_helper = config_helper or ConfigHelper( self._config_helper = config_helper or ConfigHelper(
self.client.connector, self.client._service_name self.client.connector, self.client._service_name
) )

View File

@ -0,0 +1,97 @@
from unittest.mock import MagicMock, patch
import pytest
from bec_lib.atlas_models import Device as DeviceConfigModel
from bec_widgets.widgets.services.device_browser.device_item.device_config_dialog import (
DeviceConfigDialog,
)
_BASIC_CONFIG = {
"name": "test_device",
"enabled": True,
"deviceClass": "TestDevice",
"readoutPriority": "monitored",
}
@pytest.fixture
def dialog(qtbot):
"""Fixture to create a DeviceConfigDialog instance."""
mock_device = MagicMock(_config=DeviceConfigModel.model_validate(_BASIC_CONFIG).model_dump())
mock_client = MagicMock()
mock_client.device_manager.devices = {"test_device": mock_device}
dialog = DeviceConfigDialog(device="test_device", config_helper=MagicMock(), client=mock_client)
qtbot.addWidget(dialog)
return dialog
def test_initialization(dialog):
assert dialog._device == "test_device"
assert dialog._container.count() == 2
def test_fill_form(dialog):
with patch.object(dialog._form, "set_data") as mock_set_data:
dialog._fill_form()
mock_set_data.assert_called_once_with(DeviceConfigModel.model_validate(_BASIC_CONFIG))
def test_updated_config(dialog):
"""Test that updated_config returns the correct changes."""
dialog._initial_config = {"key1": "value1", "key2": "value2"}
with patch.object(
dialog._form, "get_form_data", return_value={"key1": "value1", "key2": "new_value"}
):
updated = dialog.updated_config()
assert updated == {"key2": "new_value"}
def test_apply(dialog):
with patch.object(dialog, "_process_update_action") as mock_process_update:
dialog.apply()
mock_process_update.assert_called_once()
def test_accept(dialog):
with (
patch.object(dialog, "_process_update_action") as mock_process_update,
patch("qtpy.QtWidgets.QDialog.accept") as mock_parent_accept,
):
dialog.accept()
mock_process_update.assert_called_once()
mock_parent_accept.assert_called_once()
def test_waiting_display(dialog, qtbot):
with (
patch.object(dialog._spinner, "start") as mock_spinner_start,
patch.object(dialog._spinner, "stop") as mock_spinner_stop,
):
dialog.show()
dialog._start_waiting_display()
qtbot.waitUntil(dialog._overlay_widget.isVisible, timeout=100)
mock_spinner_start.assert_called_once()
mock_spinner_stop.assert_not_called()
dialog._stop_waiting_display()
qtbot.waitUntil(lambda: not dialog._overlay_widget.isVisible(), timeout=100)
mock_spinner_stop.assert_called_once()
def test_update_cycle(dialog, qtbot):
update = {"enabled": False, "readoutPriority": "baseline", "deviceTags": ["tag"]}
def _mock_send(a, c, w):
dialog.client.device_manager.devices["test_device"]._config = c["test_device"]
dialog._config_helper.send_config_request = MagicMock(side_effect=_mock_send)
for item in dialog._form.enumerate_form_widgets():
if (val := update.get(item.label.property("_model_field_name"))) is not None:
item.widget.setValue(val)
assert dialog.updated_config() == update
dialog.apply()
dialog._config_helper.send_config_request.assert_called_with(
action="update", config={"test_device": update}, wait_for_response=False
)

View File

@ -3,7 +3,7 @@ from decimal import Decimal
import pytest import pytest
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from bec_widgets.utils.forms_from_types.forms import PydanticModelForm from bec_widgets.utils.forms_from_types.forms import PydanticModelForm, TypedForm
from bec_widgets.utils.forms_from_types.items import ( from bec_widgets.utils.forms_from_types.items import (
FloatDecimalMetadataField, FloatDecimalMetadataField,
IntMetadataField, IntMetadataField,

View File

@ -1,5 +1,5 @@
import sys import sys
from typing import Any, Literal from typing import Any, Literal, get_args
import pytest import pytest
from pydantic import ValidationError from pydantic import ValidationError
@ -68,7 +68,7 @@ def test_form_item_spec(input, validity):
{"type": list[float], "value": [0.1, 0.2, 0.3], "extra": 79.0}, {"type": list[float], "value": [0.1, 0.2, 0.3], "extra": 79.0},
] ]
) )
def list_metadata_field_and_values(request, qtbot): def list_field_and_values(request, qtbot):
itype, vals, extra = ( itype, vals, extra = (
request.param.get("type"), request.param.get("type"),
request.param.get("value"), request.param.get("value"),
@ -77,40 +77,49 @@ def list_metadata_field_and_values(request, qtbot):
spec = FormItemSpec(item_type=itype, name="test_list", info=FieldInfo(annotation=itype)) spec = FormItemSpec(item_type=itype, name="test_list", info=FieldInfo(annotation=itype))
(widget := ListMetadataField(parent=None, spec=spec)).setValue(vals) (widget := ListMetadataField(parent=None, spec=spec)).setValue(vals)
qtbot.addWidget(widget) qtbot.addWidget(widget)
yield widget, vals, extra yield widget, vals, extra, get_args(itype)[0]
def test_list_metadata_field(list_metadata_field_and_values: tuple[ListMetadataField, list, Any]): def test_list_metadata_field(list_field_and_values: tuple[ListMetadataField, list, Any, type]):
list_metadata_field, vals, extra = list_metadata_field_and_values list_field, vals, extra, _ = list_field_and_values
assert list_metadata_field.getValue() == vals assert list_field.getValue() == vals
assert list_metadata_field._main_widget.count() == 3 assert list_field._main_widget.count() == 3
list_metadata_field._add_button.click() list_field._add_button.click()
assert len(list_metadata_field.getValue()) == 4 assert len(list_field.getValue()) == 4
assert list_metadata_field._main_widget.count() == 4 assert list_field._main_widget.count() == 4
list_metadata_field._main_widget.setCurrentRow(-1) list_field._main_widget.setCurrentRow(-1)
list_metadata_field._remove_button.click() list_field._remove_button.click()
assert len(list_metadata_field.getValue()) == 4 assert len(list_field.getValue()) == 4
assert list_metadata_field._main_widget.count() == 4 assert list_field._main_widget.count() == 4
list_metadata_field._main_widget.setCurrentRow(2) list_field._main_widget.setCurrentRow(2)
list_metadata_field._remove_button.click() list_field._remove_button.click()
assert list_metadata_field.getValue() == vals[:2] + [list_metadata_field._types.default] assert list_field.getValue() == vals[:2] + [list_field._types.default]
assert list_metadata_field._main_widget.count() == 3 assert list_field._main_widget.count() == 3
list_metadata_field._main_widget.setCurrentRow(1) list_field._main_widget.setCurrentRow(1)
WidgetIO.set_value( WidgetIO.set_value(list_field._main_widget.itemWidget(list_field._main_widget.item(1)), extra)
list_metadata_field._main_widget.itemWidget(list_metadata_field._main_widget.item(1)), extra assert list_field._main_widget.count() == 3
) assert list_field.getValue() == [vals[0], extra, list_field._types.default]
assert list_metadata_field._main_widget.count() == 3
assert list_metadata_field.getValue() == [vals[0], extra, list_metadata_field._types.default]
list_metadata_field._add_item(extra) list_field._add_item(extra)
assert list_metadata_field._main_widget.count() == 4 assert list_field._main_widget.count() == 4
assert list_metadata_field.getValue() == [ assert list_field.getValue() == [vals[0], extra, list_field._types.default, extra]
vals[0],
extra,
list_metadata_field._types.default, def test_list_field_value_acceptance(
extra, list_field_and_values: tuple[ListMetadataField, list, Any, type],
] ):
class _WrongType(object): ...
list_field, _, _, t = list_field_and_values
list_field.setValue([])
assert list_field._main_widget.count() == 0
list_field.setValue([t(), t(), t()])
assert list_field._main_widget.count() == 3
with pytest.raises(ValueError) as e:
list_field.setValue([_WrongType()])
assert list_field._main_widget.count() == 3
assert e.match(f"This widget only accepts items of type {t}")