feat: BL plugin menu in BECDockArea

This commit is contained in:
2026-05-12 13:50:15 +02:00
parent 1057db9d76
commit c0600a0cba
4 changed files with 149 additions and 13 deletions
+30 -1
View File
@@ -2,7 +2,10 @@ from importlib.machinery import FileFinder, SourceFileLoader
from types import ModuleType
from unittest import mock
from bec_widgets.utils.bec_plugin_helper import _all_widgets_from_all_submods
from bec_widgets.utils.bec_plugin_helper import (
_all_widgets_from_all_submods,
get_plugin_widget_icons,
)
from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.plugin_utils import BECClassContainer, BECClassInfo
@@ -69,3 +72,29 @@ def test_all_widgets_from_module_no_widgets():
widgets = _all_widgets_from_all_submods(module).as_dict()
assert widgets == {}
def test_get_plugin_widget_icons_from_designer_module():
designer_module = mock.MagicMock(spec=ModuleType)
designer_module.widget_icons = {"PluginWidget": "star"}
get_plugin_widget_icons.cache_clear()
try:
with mock.patch(
"bec_widgets.utils.bec_plugin_helper.get_plugin_designer_module",
return_value=designer_module,
):
assert get_plugin_widget_icons() == {"PluginWidget": "star"}
finally:
get_plugin_widget_icons.cache_clear()
def test_get_plugin_widget_icons_without_designer_module():
get_plugin_widget_icons.cache_clear()
try:
with mock.patch(
"bec_widgets.utils.bec_plugin_helper.get_plugin_designer_module", return_value=None
):
assert get_plugin_widget_icons() == {}
finally:
get_plugin_widget_icons.cache_clear()
+66
View File
@@ -11,6 +11,7 @@ from qtpy.QtGui import QPixmap
from qtpy.QtWidgets import QDialog, QMessageBox, QWidget
import bec_widgets.widgets.containers.dock_area.basic_dock_area as basic_dock_module
import bec_widgets.widgets.containers.dock_area.dock_area as dock_area_module
import bec_widgets.widgets.containers.dock_area.profile_utils as profile_utils
from bec_widgets.widgets.containers.dock_area.basic_dock_area import (
DockAreaWidget,
@@ -68,6 +69,13 @@ def temp_profile_dir():
return os.environ["BECWIDGETS_PROFILE_DIR"]
@pytest.fixture
def clear_plugin_toolbar_actions_cache():
dock_area_module._plugin_toolbar_actions.cache_clear()
yield
dock_area_module._plugin_toolbar_actions.cache_clear()
@pytest.fixture
def module_profile_factory(monkeypatch, tmp_path):
"""Provide a helper to create synthetic module-level (read-only) profiles."""
@@ -985,6 +993,64 @@ class TestToolbarFunctionality:
# Verify save was called with the filename
mock_screenshot.save.assert_called_once_with(str(screenshot_path))
def test_plugin_toolbar_actions_empty_when_no_plugins(self, clear_plugin_toolbar_actions_cache):
"""Test that no plugin toolbar actions are produced when no plugin widgets exist."""
with patch(
"bec_widgets.widgets.containers.dock_area.dock_area.get_plugin_rpc_widget_registry",
return_value={},
):
plugin_actions = dock_area_module._plugin_toolbar_actions()
assert plugin_actions == {}
def test_plugin_toolbar_actions_include_available_plugins(
self, clear_plugin_toolbar_actions_cache
):
"""Test that plugin toolbar actions are built from RPC widgets and generated icons."""
plugin_registry = {
"FakePluginWidget": ("fake_plugin.widgets.fake_plugin_widget", "FakePluginWidget")
}
with (
patch(
"bec_widgets.widgets.containers.dock_area.dock_area.get_plugin_rpc_widget_registry",
return_value=plugin_registry,
),
patch(
"bec_widgets.widgets.containers.dock_area.dock_area.get_plugin_widget_icons",
return_value={"FakePluginWidget": "star"},
),
):
plugin_actions = dock_area_module._plugin_toolbar_actions()
assert plugin_actions == {
"FakePluginWidget": ("star", "Add FakePluginWidget", "FakePluginWidget")
}
def test_plugin_toolbar_actions_ignore_builtin_name_collisions(
self, clear_plugin_toolbar_actions_cache
):
"""Test that plugin widgets shadowed by built-ins are not added to the plugin menu."""
plugin_registry = {"Waveform": ("fake_plugin.widgets.waveform", "Waveform")}
with patch(
"bec_widgets.widgets.containers.dock_area.dock_area.get_plugin_rpc_widget_registry",
return_value=plugin_registry,
):
plugin_actions = dock_area_module._plugin_toolbar_actions()
assert plugin_actions == {}
def test_new_plugin_widget_passes_toolbar_icon_to_new(self):
"""Test that plugin widget creation passes the toolbar icon to dock creation."""
dock_area = MagicMock()
toolbar_action = MagicMock()
dock_icon = object()
toolbar_action.get_icon.return_value = dock_icon
BECDockArea._new_plugin_widget(dock_area, "FakePluginWidget", toolbar_action)
toolbar_action.get_icon.assert_called_once_with()
dock_area.new.assert_called_once_with(widget="FakePluginWidget", dock_icon=dock_icon)
class TestDockSettingsDialog:
"""Test dock settings dialog functionality."""