diff --git a/bec_widgets/widgets/control/device_input/device_combobox/device_combobox.py b/bec_widgets/widgets/control/device_input/device_combobox/device_combobox.py index 9ebffa4e..a92350df 100644 --- a/bec_widgets/widgets/control/device_input/device_combobox/device_combobox.py +++ b/bec_widgets/widgets/control/device_input/device_combobox/device_combobox.py @@ -489,14 +489,17 @@ class DeviceComboBox(BECWidget, QComboBox): action: Device update action emitted by BEC. content: Device update payload. Currently unused. """ + if self._callback_id is None or getattr(self, "_destroyed", False): + return if action in ["add", "remove", "reload"]: self.device_config_update.emit() def cleanup(self): """Cleanup the widget.""" if self._callback_id is not None: - self.bec_dispatcher.client.callbacks.remove(self._callback_id) + callback_id = self._callback_id self._callback_id = None + self.bec_dispatcher.client.callbacks.remove(callback_id) super().cleanup() def get_current_device(self) -> object: diff --git a/bec_widgets/widgets/control/device_input/signal_combobox/signal_combobox.py b/bec_widgets/widgets/control/device_input/signal_combobox/signal_combobox.py index 72484fb5..e2f0e718 100644 --- a/bec_widgets/widgets/control/device_input/signal_combobox/signal_combobox.py +++ b/bec_widgets/widgets/control/device_input/signal_combobox/signal_combobox.py @@ -594,8 +594,9 @@ class SignalComboBox(BECWidget, QComboBox): def cleanup(self): """Cleanup the widget.""" if self._device_update_register is not None: - self.bec_dispatcher.client.callbacks.remove(self._device_update_register) + callback_id = self._device_update_register self._device_update_register = None + self.bec_dispatcher.client.callbacks.remove(callback_id) super().cleanup() @staticmethod diff --git a/tests/unit_tests/test_device_input_widgets.py b/tests/unit_tests/test_device_input_widgets.py index b5927c5e..767d741e 100644 --- a/tests/unit_tests/test_device_input_widgets.py +++ b/tests/unit_tests/test_device_input_widgets.py @@ -139,6 +139,23 @@ def test_device_input_combobox_cleanup_unregisters_callback(qtbot, mocked_client assert widget._callback_id is None +def test_device_input_combobox_cleanup_clears_callback_before_unregister(qtbot, mocked_client): + widget = DeviceComboBox(client=mocked_client) + qtbot.addWidget(widget) + callback_id = widget._callback_id + + def assert_callback_cleared(removed_callback_id): + assert removed_callback_id == callback_id + assert widget._callback_id is None + + with mock.patch.object( + mocked_client.callbacks, "remove", side_effect=assert_callback_cleared + ) as remove_mock: + widget.cleanup() + + remove_mock.assert_called_once_with(callback_id) + + def test_get_device_from_input_combobox_init(device_input_combobox): device_input_combobox.setCurrentIndex(0) device_text = device_input_combobox.currentText() diff --git a/tests/unit_tests/test_device_signal_input.py b/tests/unit_tests/test_device_signal_input.py index 56b90b6d..36b9111a 100644 --- a/tests/unit_tests/test_device_signal_input.py +++ b/tests/unit_tests/test_device_signal_input.py @@ -196,6 +196,41 @@ def test_device_signal_input_base_cleanup(qtbot, mocked_client): assert widget._device_update_register is None +def test_signal_combobox_cleanup_clears_callback_before_unregister(qtbot, mocked_client): + widget = create_widget(qtbot=qtbot, widget=SignalComboBox, client=mocked_client) + callback_id = widget._device_update_register + + def assert_callback_cleared(removed_callback_id): + assert removed_callback_id == callback_id + assert widget._device_update_register is None + + with mock.patch.object( + mocked_client.callbacks, "remove", side_effect=assert_callback_cleared + ) as remove_mock: + widget.cleanup() + + remove_mock.assert_called_once_with(callback_id) + + +def test_signal_combobox_cleanup_blocks_in_flight_device_update(qtbot, mocked_client): + widget = create_widget(qtbot=qtbot, widget=SignalComboBox, client=mocked_client) + callback_id = widget._device_update_register + + def trigger_in_flight_update(_): + widget.update_signals_from_filters("reload", {}) + + with ( + mock.patch.object( + mocked_client.callbacks, "remove", side_effect=trigger_in_flight_update + ) as remove_mock, + mock.patch.object(widget, "_set_signal_groups") as set_signal_groups, + ): + widget.cleanup() + + remove_mock.assert_called_once_with(callback_id) + set_signal_groups.assert_not_called() + + def test_signal_combobox_device_update_ignores_update_action(qtbot, mocked_client): widget = create_widget(qtbot=qtbot, widget=SignalComboBox, client=mocked_client)