diff --git a/bec_widgets/widgets/editors/dict_backed_table.py b/bec_widgets/widgets/editors/dict_backed_table.py index 78a76b66..99a0439f 100644 --- a/bec_widgets/widgets/editors/dict_backed_table.py +++ b/bec_widgets/widgets/editors/dict_backed_table.py @@ -53,6 +53,7 @@ class DictBackedTableModel(QAbstractTableModel): if value in self._disallowed_keys or value in self._other_keys(index.row()): return False self._data[index.row()][index.column()] = str(value) + self.dataChanged.emit(index, index) return True return False @@ -109,6 +110,7 @@ class DictBackedTableModel(QAbstractTableModel): class DictBackedTable(QWidget): delete_rows = Signal(list) + data_updated = Signal() def __init__(self, initial_data: list[list[str]]): """Widget which uses a DictBackedTableModel to display an editable table @@ -141,6 +143,11 @@ class DictBackedTable(QWidget): self._add_button.clicked.connect(self._table_model.add_row) self._remove_button.clicked.connect(self.delete_selected_rows) self.delete_rows.connect(self._table_model.delete_rows) + self._table_model.dataChanged.connect(self._emit_data_updated) + + def _emit_data_updated(self, *args, **kwargs): + """Just to swallow the args""" + self.data_updated.emit() def delete_selected_rows(self): """Delete rows which are part of the selection model""" diff --git a/bec_widgets/widgets/editors/scan_metadata/scan_metadata.py b/bec_widgets/widgets/editors/scan_metadata/scan_metadata.py index 580878ba..918576d1 100644 --- a/bec_widgets/widgets/editors/scan_metadata/scan_metadata.py +++ b/bec_widgets/widgets/editors/scan_metadata/scan_metadata.py @@ -43,6 +43,7 @@ class ScanMetadata(PydanticModelForm): self._additional_metadata = DictBackedTable(initial_extras or []) self._scan_name = scan_name or "" self._md_schema = get_metadata_schema_for_scan(self._scan_name) + self._additional_metadata.data_updated.connect(self.validate_form) super().__init__(parent=parent, metadata_model=self._md_schema, client=client, **kwargs) diff --git a/tests/unit_tests/test_scan_control.py b/tests/unit_tests/test_scan_control.py index 5248b995..d47f661b 100644 --- a/tests/unit_tests/test_scan_control.py +++ b/tests/unit_tests/test_scan_control.py @@ -1,9 +1,11 @@ # pylint: disable = no-name-in-module,missing-class-docstring, missing-module-docstring -from unittest.mock import MagicMock +from types import SimpleNamespace +from unittest.mock import MagicMock, patch import pytest from bec_lib.endpoints import MessageEndpoints from bec_lib.messages import AvailableResourceMessage, ScanQueueHistoryMessage, ScanQueueMessage +from qtpy.QtCore import QModelIndex, QPoint, Qt from bec_widgets.utils.forms_from_types.items import StrMetadataField from bec_widgets.utils.widget_io import WidgetIO @@ -540,6 +542,29 @@ def test_get_scan_parameters_from_redis(scan_control, mocked_client): assert kwargs == {"steps": 10, "relative": False, "exp_time": 2.0, "burst_at_each_point": 1} +TEST_MD = {"sample_name": "Test Sample", "test key 1": "test value 1", "test key 2": "test value 2"} +TEST_TABLE_ENTRY = [["test key 1", "test value 1"], ["test key 2", "test value 2"]] + + +def test_scan_metadata_is_updated_even_without_default_form_changes( + scan_control: ScanControl, qtbot +): + assert scan_control._metadata_form._scan_name == "line_scan" + scan_control.comboBox_scan_selection.setCurrentText("grid_scan") + assert scan_control._metadata_form._scan_name == "grid_scan" + scan_control._metadata_form._additional_metadata._add_button.click() + qtbot.wait(100) + table_model = scan_control._metadata_form._additional_metadata._table_model + model_key = table_model.index(0, 0, QModelIndex()) + table_model.setData(model_key, "test key 1", Qt.EditRole) + model_value = model_key.siblingAtColumn(1) + table_model.setData(model_value, "test value 1", Qt.EditRole) + assert scan_control._metadata_form._additional_metadata.dump_dict() == { + "test key 1": "test value 1" + } + assert scan_control._scan_metadata == {"sample_name": "", "test key 1": "test value 1"} + + def test_scan_metadata_is_connected(scan_control): assert scan_control._metadata_form._scan_name == "line_scan" scan_control.comboBox_scan_selection.setCurrentText("grid_scan") @@ -548,16 +573,28 @@ def test_scan_metadata_is_connected(scan_control): assert isinstance(sample_name, StrMetadataField) sample_name._main_widget.setText("Test Sample") - scan_control._metadata_form._additional_metadata._table_model._data = [ - ["test key 1", "test value 1"], - ["test key 2", "test value 2"], - ] + scan_control._metadata_form._additional_metadata._table_model._data = TEST_TABLE_ENTRY scan_control._metadata_form.validate_form() - assert scan_control._scan_metadata == { - "sample_name": "Test Sample", - "test key 1": "test value 1", - "test key 2": "test value 2", - } + assert scan_control._scan_metadata == TEST_MD + + +def test_scan_metadata_is_passed_to_scan_function(scan_control: ScanControl): + scan_control.comboBox_scan_selection.setCurrentText("grid_scan") + + sample_name = scan_control._metadata_form._form_grid.layout().itemAtPosition(0, 1).widget() + sample_name._main_widget.setText("Test Sample") + scan_control._metadata_form._additional_metadata._table_model._data = TEST_TABLE_ENTRY + scan_control._metadata_form.validate_form() + + assert scan_control._scan_metadata == TEST_MD + + scans = SimpleNamespace(grid_scan=MagicMock()) + with ( + patch.object(scan_control, "scans", scans), + patch.object(scan_control, "get_scan_parameters", lambda: ((), {})), + ): + scan_control.run_scan() + scans.grid_scan.assert_called_once_with(metadata=TEST_MD) def test_restore_parameters_with_fewer_arg_bundles(scan_control, qtbot):