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

test(device-manager-view): improve test coverage for device-manager-view

This commit is contained in:
2026-01-16 19:56:55 +01:00
committed by Christian Appel
parent b325d1bb4f
commit 2b5b7360ae
2 changed files with 100 additions and 34 deletions

View File

@@ -77,27 +77,27 @@ class CustomBusyWidget(QWidget):
super().__init__(parent=parent)
# Widgets
progress = DeviceInitializationProgressBar(parent=self, client=client)
progress.setMinimumWidth(320)
self.progress = DeviceInitializationProgressBar(parent=self, client=client)
self.progress.setMinimumWidth(320)
# Spinner
spinner = SpinnerWidget(parent=self)
self.spinner = SpinnerWidget(parent=self)
scale = self._ui_scale()
spinner_size = int(scale * 0.12) if scale else 1
spinner_size = max(32, min(spinner_size, 96))
spinner.setFixedSize(spinner_size, spinner_size)
self.spinner.setFixedSize(spinner_size, spinner_size)
# Cancel button
cancel_button = QPushButton("Cancel Upload", parent=self)
cancel_button.setIcon(material_icon("cancel"))
cancel_button.clicked.connect(self.cancel_requested.emit)
self.cancel_button = QPushButton("Cancel Upload", parent=self)
self.cancel_button.setIcon(material_icon("cancel"))
self.cancel_button.clicked.connect(self.cancel_requested.emit)
button_height = int(spinner_size * 0.9)
button_height = max(36, min(button_height, 72))
aspect_ratio = 3.8 # width / height, visually stable for text buttons
button_width = int(button_height * aspect_ratio)
cancel_button.setFixedSize(button_width, button_height)
self.cancel_button.setFixedSize(button_width, button_height)
color = get_accent_colors()
cancel_button.setStyleSheet(
self.cancel_button.setStyleSheet(
f"""
QPushButton {{
background-color: {color.emergency.name()};
@@ -113,10 +113,10 @@ class CustomBusyWidget(QWidget):
content_layout.setContentsMargins(24, 24, 24, 24)
content_layout.setSpacing(16)
content_layout.addStretch()
content_layout.addWidget(spinner, 0, Qt.AlignmentFlag.AlignHCenter)
content_layout.addWidget(progress, 0, Qt.AlignmentFlag.AlignHCenter)
content_layout.addWidget(self.spinner, 0, Qt.AlignmentFlag.AlignHCenter)
content_layout.addWidget(self.progress, 0, Qt.AlignmentFlag.AlignHCenter)
content_layout.addStretch()
content_layout.addWidget(cancel_button, 0, Qt.AlignmentFlag.AlignHCenter)
content_layout.addWidget(self.cancel_button, 0, Qt.AlignmentFlag.AlignHCenter)
if hasattr(color, "_colors"):
bg_color = color._colors.get("BG", None)
@@ -138,14 +138,12 @@ class CustomBusyWidget(QWidget):
def showEvent(self, event):
"""Show event to start the spinner."""
super().showEvent(event)
for child in self.findChildren(SpinnerWidget):
child.start()
self.spinner.start()
def hideEvent(self, event):
"""Hide event to stop the spinner."""
super().hideEvent(event)
for child in self.findChildren(SpinnerWidget):
child.stop()
self.spinner.stop()
class DeviceManagerDisplayWidget(DockAreaWidget):
@@ -171,9 +169,6 @@ class DeviceManagerDisplayWidget(DockAreaWidget):
self._config_helper = config_helper.ConfigHelper(self.client.connector)
self._shared_selection = SharedSelectionSignal()
# Custom upload widget for busy overlay
self._custom_overlay_widget: QWidget | None = None
# Device Table View widget
self.device_table_view = DeviceTable(self)
@@ -687,20 +682,9 @@ class DeviceManagerDisplayWidget(DockAreaWidget):
# Config is in sync with BEC, so we update the state
self.device_table_view.device_config_in_sync_with_redis.emit(False)
# Cleanup custom overlay widget
if self._custom_overlay_widget is not None:
self._custom_overlay_widget.close()
self._custom_overlay_widget.deleteLater()
self._custom_overlay_widget = None
def _handle_push_complete_to_communicator(self):
"""Handle completion of the config push to Redis."""
self._set_busy_wrapper(enabled=False)
# Cleanup custom overlay widget
if self._custom_overlay_widget is not None:
self._custom_overlay_widget.close()
self._custom_overlay_widget.deleteLater()
self._custom_overlay_widget = None
def _handle_exception_from_communicator(self, exception: Exception):
"""Handle exceptions from the config communicator."""
@@ -710,10 +694,6 @@ class DeviceManagerDisplayWidget(DockAreaWidget):
f"An error occurred while uploading the configuration to BEC Server:\n{str(exception)}",
)
self._set_busy_wrapper(enabled=False)
if self._custom_overlay_widget is not None:
self._custom_overlay_widget.close()
self._custom_overlay_widget.deleteLater()
self._custom_overlay_widget = None
@SafeSlot()
def _save_to_disk_action(self):

View File

@@ -30,6 +30,7 @@ from bec_widgets.applications.views.device_manager_view.device_manager_view impo
DeviceManagerView,
DeviceManagerWidget,
)
from bec_widgets.utils.colors import get_accent_colors
from bec_widgets.widgets.control.device_manager.components import (
DeviceTable,
DMConfigView,
@@ -612,6 +613,34 @@ class TestDeviceManagerView:
cfg_iter.append(dev_config_copy)
return cfg_iter
def test_custom_busy_widget(self, custom_busy: CustomBusyWidget, qtbot):
"""Test the CustomBusyWidget functionality."""
# Check layout
assert custom_busy.progress is not None
assert custom_busy.spinner is not None
assert custom_busy.spinner._started is False
# Check background
color = get_accent_colors()
bg = color._colors["BG"]
sheet = custom_busy.styleSheet()
assert bg.name() in sheet
assert "border-radius: 12px" in sheet
# Show event should start spinner
custom_busy.showEvent(None)
assert custom_busy.spinner._started is True
with qtbot.waitSignal(custom_busy.cancel_requested) as sig_blocker:
qtbot.mouseClick(custom_busy.cancel_button, QtCore.Qt.LeftButton)
# Check that the signal was emitted
assert sig_blocker.signal_triggered is True
# Hide should
custom_busy.hideEvent(None)
assert custom_busy.spinner._started is False
def test_device_manager_view_add_remove_device(
self, device_manager_display_widget: DeviceManagerDisplayWidget, device_config
):
@@ -750,3 +779,60 @@ class TestDeviceManagerView:
"rerun_validation"
].action.action.triggered.emit()
assert len(mock_change_configs.call_args[0][0]) == 1
def test_handle_cancel_config_upload_failed(
self, device_manager_display_widget: DeviceManagerDisplayWidget, qtbot
):
"""Test handling cancel during config upload failure."""
dm_view = device_manager_display_widget
validation_results = {
"Device_1": (
{"name": "Device_1"},
ConfigStatus.VALID.value,
ConnectionStatus.CANNOT_CONNECT.value,
),
"Device_2": (
{"name": "Device_2"},
ConfigStatus.INVALID.value,
ConnectionStatus.UNKNOWN.value,
),
}
with mock.patch.object(
dm_view.device_table_view, "get_validation_results", return_value=validation_results
):
with (
mock.patch.object(
dm_view.device_table_view, "update_multiple_device_validations"
) as mock_update,
mock.patch.object(
dm_view.ophyd_test_view, "change_device_configs"
) as mock_change_configs,
):
with qtbot.waitSignal(
dm_view.device_table_view.device_config_in_sync_with_redis
) as sig_blocker:
dm_view._handle_cancel_config_upload_failed(
exception=Exception("Test Exception")
)
assert sig_blocker.signal_triggered is True
mock_change_configs.assert_called_once_with(
[validation_results["Device_1"][0], validation_results["Device_2"][0]],
added=True,
skip_validation=False,
)
mock_update.assert_called_once_with(
[
(
validation_results["Device_1"][0],
validation_results["Device_1"][1],
ConnectionStatus.UNKNOWN.value,
"Upload Cancelled",
),
(
validation_results["Device_2"][0],
validation_results["Device_2"][1],
ConnectionStatus.UNKNOWN.value,
"Upload Cancelled",
),
]
)