1
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2026-03-05 00:12:49 +01:00

refactor(toolbar): split toolbar into components, bundles and connections

This commit is contained in:
2025-06-25 10:49:39 +02:00
committed by Jan Wyzula
parent f10140e0f3
commit db720e8fa4
48 changed files with 3415 additions and 2567 deletions

View File

@@ -5,19 +5,18 @@ from qtpy.QtCore import QPoint, Qt
from qtpy.QtGui import QContextMenuEvent
from qtpy.QtWidgets import QComboBox, QLabel, QMenu, QStyle, QToolButton, QWidget
from bec_widgets.utils.toolbar import (
from bec_widgets.utils.toolbars.actions import (
DeviceSelectionAction,
ExpandableMenuAction,
IconAction,
LongPressToolButton,
MaterialIconAction,
ModularToolBar,
QtIconAction,
SeparatorAction,
SwitchableToolBarAction,
ToolbarBundle,
WidgetAction,
)
from bec_widgets.utils.toolbars.bundles import ToolbarBundle
from bec_widgets.utils.toolbars.toolbar import ModularToolBar
@pytest.fixture
@@ -34,14 +33,12 @@ def toolbar_fixture(qtbot, request, dummy_widget):
"""Parametrized fixture to create a ModularToolBar with different orientations."""
orientation: Literal["horizontal", "vertical"] = request.param
toolbar = ModularToolBar(
target_widget=dummy_widget,
orientation=orientation,
background_color="rgba(255, 255, 255, 255)", # White background for testing
)
qtbot.addWidget(toolbar)
qtbot.waitExposed(toolbar)
yield toolbar
toolbar.close()
@pytest.fixture
@@ -50,12 +47,6 @@ def separator_action():
return SeparatorAction()
@pytest.fixture
def icon_action():
"""Fixture to create an IconAction."""
return IconAction(icon_path="assets/BEC-Icon.png", tooltip="Test Icon Action", checkable=True)
@pytest.fixture
def material_icon_action():
"""Fixture to create a MaterialIconAction."""
@@ -64,6 +55,14 @@ def material_icon_action():
)
@pytest.fixture
def material_icon_action_2():
"""Fixture to create another MaterialIconAction."""
return MaterialIconAction(
icon_name="home", tooltip="Test Material Icon Action 2", checkable=False
)
@pytest.fixture
def qt_icon_action():
"""Fixture to create a QtIconAction."""
@@ -121,7 +120,7 @@ def test_initialization(toolbar_fixture):
else:
pytest.fail("Toolbar orientation is neither horizontal nor vertical.")
assert toolbar.background_color == "rgba(255, 255, 255, 255)"
assert toolbar.widgets == {}
assert len(toolbar.components._components) == 1 # only the separator
assert not toolbar.isMovable()
assert not toolbar.isFloatable()
@@ -152,80 +151,60 @@ def test_set_orientation(toolbar_fixture, qtbot, dummy_widget):
assert toolbar.orientation() == Qt.Vertical
def test_add_action(
toolbar_fixture,
icon_action,
separator_action,
material_icon_action,
qt_icon_action,
dummy_widget,
):
"""Test adding different types of actions to the toolbar."""
def test_add_action(toolbar_fixture, material_icon_action, qt_icon_action):
"""Test adding different types of actions to the toolbar components."""
toolbar = toolbar_fixture
# Add IconAction
toolbar.add_action("icon_action", icon_action, dummy_widget)
assert "icon_action" in toolbar.widgets
assert toolbar.widgets["icon_action"] == icon_action
assert icon_action.action in toolbar.actions()
# Add SeparatorAction
toolbar.add_action("separator_action", separator_action, dummy_widget)
assert "separator_action" in toolbar.widgets
assert toolbar.widgets["separator_action"] == separator_action
# Add MaterialIconAction
toolbar.add_action("material_icon_action", material_icon_action, dummy_widget)
assert "material_icon_action" in toolbar.widgets
assert toolbar.widgets["material_icon_action"] == material_icon_action
assert material_icon_action.action in toolbar.actions()
toolbar.add_action("material_icon_action", material_icon_action)
assert toolbar.components.exists("material_icon_action")
assert toolbar.components.get_action("material_icon_action") == material_icon_action
# Add QtIconAction
toolbar.add_action("qt_icon_action", qt_icon_action, dummy_widget)
assert "qt_icon_action" in toolbar.widgets
assert toolbar.widgets["qt_icon_action"] == qt_icon_action
assert qt_icon_action.action in toolbar.actions()
toolbar.add_action("qt_icon_action", qt_icon_action)
assert toolbar.components.exists("qt_icon_action")
assert toolbar.components.get_action("qt_icon_action") == qt_icon_action
def test_hide_show_action(toolbar_fixture, icon_action, qtbot, dummy_widget):
def test_hide_show_action(toolbar_fixture, qt_icon_action, qtbot):
"""Test hiding and showing actions on the toolbar."""
toolbar = toolbar_fixture
# Add an action
toolbar.add_action("icon_action", icon_action, dummy_widget)
assert icon_action.action.isVisible()
toolbar.add_action("icon_action", qt_icon_action)
assert qt_icon_action.action.isVisible()
# Hide the action
toolbar.hide_action("icon_action")
qtbot.wait(100)
assert not icon_action.action.isVisible()
assert not qt_icon_action.action.isVisible()
# Show the action
toolbar.show_action("icon_action")
qtbot.wait(100)
assert icon_action.action.isVisible()
assert qt_icon_action.action.isVisible()
def test_add_duplicate_action(toolbar_fixture, icon_action, dummy_widget):
def test_add_duplicate_action(toolbar_fixture, qt_icon_action):
"""Test that adding an action with a duplicate action_id raises a ValueError."""
toolbar = toolbar_fixture
# Add an action
toolbar.add_action("icon_action", icon_action, dummy_widget)
assert "icon_action" in toolbar.widgets
toolbar.add_action("qt_icon_action", qt_icon_action)
assert toolbar.components.exists("qt_icon_action")
# Attempt to add another action with the same ID
with pytest.raises(ValueError) as excinfo:
toolbar.add_action("icon_action", icon_action, dummy_widget)
assert "Action with ID 'icon_action' already exists." in str(excinfo.value)
toolbar.add_action("qt_icon_action", qt_icon_action)
assert "Bundle with name 'qt_icon_action' already exists." in str(excinfo.value)
def test_update_material_icon_colors(toolbar_fixture, material_icon_action, dummy_widget):
def test_update_material_icon_colors(toolbar_fixture, material_icon_action):
"""Test updating the color of MaterialIconAction icons."""
toolbar = toolbar_fixture
# Add MaterialIconAction
toolbar.add_action("material_icon_action", material_icon_action, dummy_widget)
toolbar.add_action("material_icon_action", material_icon_action)
assert material_icon_action.action is not None
# Initial icon
@@ -242,11 +221,12 @@ def test_update_material_icon_colors(toolbar_fixture, material_icon_action, dumm
assert initial_icon != updated_icon
def test_device_selection_action(toolbar_fixture, device_selection_action, dummy_widget):
def test_device_selection_action(toolbar_fixture, device_selection_action):
"""Test adding a DeviceSelectionAction to the toolbar."""
toolbar = toolbar_fixture
toolbar.add_action("device_selection", device_selection_action, dummy_widget)
assert "device_selection" in toolbar.widgets
toolbar.add_action("device_selection", device_selection_action)
assert toolbar.components.exists("device_selection")
toolbar.show_bundles(["device_selection"])
# DeviceSelectionAction adds a QWidget, so it should be present in the toolbar's widgets
# Check if the widget is added
widget = device_selection_action.device_combobox.parentWidget()
@@ -256,11 +236,12 @@ def test_device_selection_action(toolbar_fixture, device_selection_action, dummy
assert label.text() == "Select Device:"
def test_widget_action(toolbar_fixture, widget_action, dummy_widget):
def test_widget_action(toolbar_fixture, widget_action):
"""Test adding a WidgetAction to the toolbar."""
toolbar = toolbar_fixture
toolbar.add_action("widget_action", widget_action, dummy_widget)
assert "widget_action" in toolbar.widgets
toolbar.add_action("widget_action", widget_action)
assert toolbar.components.exists("widget_action")
toolbar.show_bundles(["widget_action"])
# WidgetAction adds a QWidget to the toolbar
container = widget_action.widget.parentWidget()
assert container in toolbar.findChildren(QWidget)
@@ -269,11 +250,12 @@ def test_widget_action(toolbar_fixture, widget_action, dummy_widget):
assert label.text() == "Sample Label:"
def test_expandable_menu_action(toolbar_fixture, expandable_menu_action, dummy_widget):
def test_expandable_menu_action(toolbar_fixture, expandable_menu_action):
"""Test adding an ExpandableMenuAction to the toolbar."""
toolbar = toolbar_fixture
toolbar.add_action("expandable_menu", expandable_menu_action, dummy_widget)
assert "expandable_menu" in toolbar.widgets
toolbar.add_action("expandable_menu", expandable_menu_action)
assert toolbar.components.exists("expandable_menu")
toolbar.show_bundles(["expandable_menu"])
# ExpandableMenuAction adds a QToolButton with a QMenu
# Find the QToolButton
tool_buttons = toolbar.findChildren(QToolButton)
@@ -300,44 +282,47 @@ def test_update_material_icon_colors_no_material_actions(toolbar_fixture, dummy_
def test_hide_action_nonexistent(toolbar_fixture):
"""Test hiding an action that does not exist raises a ValueError."""
"""Test hiding an action that does not exist raises a KeyError."""
toolbar = toolbar_fixture
with pytest.raises(ValueError) as excinfo:
with pytest.raises(KeyError) as excinfo:
toolbar.hide_action("nonexistent_action")
assert "Action with ID 'nonexistent_action' does not exist." in str(excinfo.value)
excinfo.match("Component with name 'nonexistent_action' does not exist.")
def test_show_action_nonexistent(toolbar_fixture):
"""Test showing an action that does not exist raises a ValueError."""
"""Test showing an action that does not exist raises a KeyError."""
toolbar = toolbar_fixture
with pytest.raises(ValueError) as excinfo:
with pytest.raises(KeyError) as excinfo:
toolbar.show_action("nonexistent_action")
assert "Action with ID 'nonexistent_action' does not exist." in str(excinfo.value)
excinfo.match("Component with name 'nonexistent_action' does not exist.")
def test_add_bundle(toolbar_fixture, dummy_widget, icon_action, material_icon_action):
def test_add_bundle(toolbar_fixture, material_icon_action):
"""Test adding a bundle of actions to the toolbar."""
toolbar = toolbar_fixture
bundle = ToolbarBundle(
bundle_id="test_bundle",
actions=[
("icon_action_in_bundle", icon_action),
("material_icon_in_bundle", material_icon_action),
],
)
toolbar.add_bundle(bundle, dummy_widget)
assert "test_bundle" in toolbar.bundles
assert "icon_action_in_bundle" in toolbar.widgets
assert "material_icon_in_bundle" in toolbar.widgets
assert icon_action.action in toolbar.actions()
toolbar.add_action("material_icon_in_bundle", material_icon_action)
bundle = ToolbarBundle("test_bundle", toolbar.components)
bundle.add_action("material_icon_in_bundle")
toolbar.add_bundle(bundle)
assert toolbar.get_bundle("test_bundle")
assert toolbar.components.exists("material_icon_in_bundle")
toolbar.show_bundles(["test_bundle"])
assert material_icon_action.action in toolbar.actions()
def test_invalid_orientation(dummy_widget):
def test_invalid_orientation():
"""Test that an invalid orientation raises a ValueError."""
toolbar = ModularToolBar(target_widget=dummy_widget, orientation="horizontal")
with pytest.raises(ValueError):
toolbar.set_orientation("diagonal")
try:
toolbar = ModularToolBar(orientation="horizontal")
with pytest.raises(ValueError):
toolbar.set_orientation("diagonal")
finally:
toolbar.close()
toolbar.deleteLater()
def test_widget_action_calculate_minimum_width(qtbot):
@@ -353,24 +338,26 @@ def test_widget_action_calculate_minimum_width(qtbot):
def test_add_action_to_bundle(toolbar_fixture, dummy_widget, material_icon_action):
# Create an initial bundle with one action
bundle = ToolbarBundle(
bundle_id="test_bundle", actions=[("initial_action", material_icon_action)]
)
toolbar_fixture.add_bundle(bundle, dummy_widget)
toolbar_fixture.add_action("initial_action", material_icon_action)
bundle = ToolbarBundle("test_bundle", toolbar_fixture.components)
bundle.add_action("initial_action")
toolbar_fixture.add_bundle(bundle)
# Create a new action to add to the existing bundle
new_action = MaterialIconAction(
icon_name="counter_1", tooltip="New Action", checkable=True, parent=dummy_widget
)
toolbar_fixture.add_action_to_bundle("test_bundle", "new_action", new_action, dummy_widget)
toolbar_fixture.components.add_safe("new_action", new_action)
toolbar_fixture.get_bundle("test_bundle").add_action("new_action")
toolbar_fixture.show_bundles(["test_bundle"])
# Verify the new action is registered in the toolbar's widgets
assert "new_action" in toolbar_fixture.widgets
assert toolbar_fixture.widgets["new_action"] == new_action
assert toolbar_fixture.components.exists("new_action")
assert toolbar_fixture.components.get_action("new_action") == new_action
# Verify the new action is included in the bundle tracking
assert "new_action" in toolbar_fixture.bundles["test_bundle"]
assert toolbar_fixture.bundles["test_bundle"][-1] == "new_action"
assert toolbar_fixture.bundles["test_bundle"].bundle_actions["new_action"]() == new_action
# Verify the new action's QAction is present in the toolbar's action list
actions_list = toolbar_fixture.actions()
@@ -384,7 +371,7 @@ def test_add_action_to_bundle(toolbar_fixture, dummy_widget, material_icon_actio
def test_context_menu_contains_added_actions(
toolbar_fixture, icon_action, material_icon_action, dummy_widget, monkeypatch
toolbar_fixture, material_icon_action, material_icon_action_2, monkeypatch
):
"""
Test that the toolbar's context menu lists all added toolbar actions.
@@ -392,9 +379,13 @@ def test_context_menu_contains_added_actions(
toolbar = toolbar_fixture
# Add two different actions
toolbar.add_action("icon_action", icon_action, dummy_widget)
toolbar.add_action("material_icon_action", material_icon_action, dummy_widget)
toolbar.components.add_safe("material_icon_action", material_icon_action)
toolbar.components.add_safe("material_icon_action_2", material_icon_action_2)
bundle = toolbar.new_bundle("test_bundle")
bundle.add_action("material_icon_action")
bundle.add_action("material_icon_action_2")
toolbar.show_bundles(["test_bundle"])
# Mock the QMenu.exec_ method to prevent the context menu from being displayed and block CI pipeline
monkeypatch.setattr(QMenu, "exec_", lambda self, pos=None: None)
event = QContextMenuEvent(QContextMenuEvent.Mouse, QPoint(10, 10))
@@ -404,23 +395,26 @@ def test_context_menu_contains_added_actions(
assert len(menus) > 0
menu = menus[-1]
menu_action_texts = [action.text() for action in menu.actions()]
assert any(icon_action.tooltip in text or "icon_action" in text for text in menu_action_texts)
assert any(
material_icon_action.tooltip in text or "material_icon_action" in text
for text in menu_action_texts
)
tooltips = [
action.action.tooltip
for action in toolbar.components._components.values()
if not isinstance(action.action, SeparatorAction)
]
menu_actions_tooltips = [
action.toolTip() for action in menu.actions() if action.toolTip() != ""
]
assert menu_action_texts == tooltips
def test_context_menu_toggle_action_visibility(
toolbar_fixture, icon_action, dummy_widget, monkeypatch
):
def test_context_menu_toggle_action_visibility(toolbar_fixture, material_icon_action, monkeypatch):
"""
Test that toggling action visibility works correctly through the toolbar's context menu.
"""
toolbar = toolbar_fixture
# Add an action
toolbar.add_action("icon_action", icon_action, dummy_widget)
assert icon_action.action.isVisible()
toolbar.add_action("material_icon_action", material_icon_action)
toolbar.show_bundles(["material_icon_action"])
assert material_icon_action.action.isVisible()
# Manually trigger the context menu event
monkeypatch.setattr(QMenu, "exec_", lambda self, pos=None: None)
@@ -433,7 +427,7 @@ def test_context_menu_toggle_action_visibility(
menu = menus[-1]
# Locate the QAction in the menu
matching_actions = [m for m in menu.actions() if m.text() == icon_action.tooltip]
matching_actions = [m for m in menu.actions() if m.text() == material_icon_action.tooltip]
assert len(matching_actions) == 1
action_in_menu = matching_actions[0]
@@ -441,23 +435,24 @@ def test_context_menu_toggle_action_visibility(
action_in_menu.setChecked(False)
menu.triggered.emit(action_in_menu)
# The action on the toolbar should now be hidden
assert not icon_action.action.isVisible()
assert not material_icon_action.action.isVisible()
# Toggle it on (check)
action_in_menu.setChecked(True)
menu.triggered.emit(action_in_menu)
# The action on the toolbar should be visible again
assert icon_action.action.isVisible()
assert material_icon_action.action.isVisible()
def test_switchable_toolbar_action_add(toolbar_fixture, dummy_widget, switchable_toolbar_action):
def test_switchable_toolbar_action_add(toolbar_fixture, switchable_toolbar_action):
"""Test that a switchable toolbar action can be added to the toolbar correctly."""
toolbar = toolbar_fixture
toolbar.add_action("switch_action", switchable_toolbar_action, dummy_widget)
toolbar.add_action("switch_action", switchable_toolbar_action)
toolbar.show_bundles(["switch_action"])
# Verify the action was added correctly
assert "switch_action" in toolbar.widgets
assert toolbar.widgets["switch_action"] == switchable_toolbar_action
assert toolbar.components.exists("switch_action")
assert toolbar.components.get_action("switch_action") == switchable_toolbar_action
# Verify the button is present and is the correct type
button = switchable_toolbar_action.main_button
@@ -468,11 +463,10 @@ def test_switchable_toolbar_action_add(toolbar_fixture, dummy_widget, switchable
assert button.toolTip() == "Action 1"
def test_switchable_toolbar_action_switching(
toolbar_fixture, dummy_widget, switchable_toolbar_action, qtbot
):
def test_switchable_toolbar_action_switching(toolbar_fixture, switchable_toolbar_action, qtbot):
toolbar = toolbar_fixture
toolbar.add_action("switch_action", switchable_toolbar_action, dummy_widget)
toolbar.add_action("switch_action", switchable_toolbar_action)
toolbar.show_bundles(["switch_action"])
# Verify initial state is set to action1
assert switchable_toolbar_action.current_key == "action1"
assert switchable_toolbar_action.main_button.toolTip() == "Action 1"
@@ -494,9 +488,10 @@ def test_switchable_toolbar_action_switching(
assert switchable_toolbar_action.main_button.toolTip() == "Action 2"
def test_long_pressbutton(toolbar_fixture, dummy_widget, switchable_toolbar_action, qtbot):
def test_long_pressbutton(toolbar_fixture, switchable_toolbar_action, qtbot):
toolbar = toolbar_fixture
toolbar.add_action("switch_action", switchable_toolbar_action, dummy_widget)
toolbar.add_action("switch_action", switchable_toolbar_action)
toolbar.show_bundles(["switch_action"])
# Verify the button is a LongPressToolButton
button = switchable_toolbar_action.main_button
@@ -521,92 +516,73 @@ def test_long_pressbutton(toolbar_fixture, dummy_widget, switchable_toolbar_acti
# Additional tests for action/bundle removal
def test_remove_standalone_action(toolbar_fixture, icon_action, dummy_widget):
def test_remove_standalone_action(toolbar_fixture, material_icon_action):
"""
Ensure that a standalone action is fully removed and no longer accessible.
"""
toolbar = toolbar_fixture
# Add the action and check it is present
toolbar.add_action("icon_action", icon_action, dummy_widget)
assert "icon_action" in toolbar.widgets
assert icon_action.action in toolbar.actions()
toolbar.add_action("icon_action", material_icon_action)
assert toolbar.components.exists("icon_action")
toolbar.show_bundles(["icon_action"])
assert material_icon_action.action in toolbar.actions()
# Now remove it
toolbar.remove_action("icon_action")
toolbar.components.remove_action("icon_action")
# Action bookkeeping
assert "icon_action" not in toolbar.widgets
assert not toolbar.components.exists("icon_action")
# QAction list
assert icon_action.action not in toolbar.actions()
assert material_icon_action.action not in toolbar.actions()
# Attempting to hide / show it should raise
with pytest.raises(ValueError):
with pytest.raises(KeyError):
toolbar.hide_action("icon_action")
with pytest.raises(ValueError):
with pytest.raises(KeyError):
toolbar.show_action("icon_action")
def test_remove_action_from_bundle(
toolbar_fixture, dummy_widget, icon_action, material_icon_action
):
def test_remove_action_from_bundle(toolbar_fixture, material_icon_action, material_icon_action_2):
"""
Remove a single action that is part of a bundle and verify cleanup.
Remove a single action that is part of a bundle. This should not remove the action
from the toolbar's components, but only from the bundle tracking.
"""
toolbar = toolbar_fixture
bundle = ToolbarBundle(
bundle_id="test_bundle",
actions=[("icon_action", icon_action), ("material_action", material_icon_action)],
)
toolbar.add_bundle(bundle, dummy_widget)
bundle = toolbar.new_bundle("test_bundle")
# Add two actions to the bundle
toolbar.components.add_safe("material_action", material_icon_action)
toolbar.components.add_safe("material_action_2", material_icon_action_2)
bundle.add_action("material_action")
bundle.add_action("material_action_2")
toolbar.show_bundles(["test_bundle"])
# Initial assertions
assert "test_bundle" in toolbar.bundles
assert "icon_action" in toolbar.widgets
assert "material_action" in toolbar.widgets
assert toolbar.components.exists("material_action")
assert toolbar.components.exists("material_action_2")
# Remove one action from the bundle
toolbar.remove_action("icon_action")
toolbar.get_bundle("test_bundle").remove_action("material_action")
# icon_action should be fully gone
assert "icon_action" not in toolbar.widgets
assert icon_action.action not in toolbar.actions()
# Bundle tracking should be updated
assert "icon_action" not in toolbar.bundles["test_bundle"]
# The other action must still exist
assert "material_action" in toolbar.widgets
assert material_icon_action.action in toolbar.actions()
# The bundle should still exist
assert "test_bundle" in toolbar.bundles
# The removed action should still exist in the components
assert toolbar.components.exists("material_action")
# The removed action should not be in the bundle anymore
assert "material_action" not in toolbar.bundles["test_bundle"].bundle_actions
def test_remove_last_action_from_bundle_removes_bundle(toolbar_fixture, dummy_widget, icon_action):
"""
Removing the final action from a bundle should delete the bundle entry itself.
"""
def test_remove_entire_bundle(toolbar_fixture, material_icon_action, material_icon_action_2):
toolbar = toolbar_fixture
bundle = ToolbarBundle(bundle_id="single_action_bundle", actions=[("only_action", icon_action)])
toolbar.add_bundle(bundle, dummy_widget)
# Sanity check
assert "single_action_bundle" in toolbar.bundles
assert "only_action" in toolbar.widgets
# Remove the sole action
toolbar.remove_action("only_action")
# Bundle should be gone
assert "single_action_bundle" not in toolbar.bundles
# QAction removed
assert icon_action.action not in toolbar.actions()
def test_remove_entire_bundle(toolbar_fixture, dummy_widget, icon_action, material_icon_action):
"""
Ensure that removing a bundle deletes all its actions and separators.
"""
toolbar = toolbar_fixture
bundle = ToolbarBundle(
bundle_id="to_remove",
actions=[("icon_action", icon_action), ("material_action", material_icon_action)],
)
toolbar.add_bundle(bundle, dummy_widget)
toolbar.components.add_safe("material_action", material_icon_action)
toolbar.components.add_safe("material_action_2", material_icon_action_2)
# Create a bundle with two actions
bundle = toolbar.new_bundle("to_remove")
bundle.add_action("material_action")
bundle.add_action("material_action_2")
# Confirm bundle presence
assert "to_remove" in toolbar.bundles
@@ -616,58 +592,23 @@ def test_remove_entire_bundle(toolbar_fixture, dummy_widget, icon_action, materi
# Bundle mapping gone
assert "to_remove" not in toolbar.bundles
# All actions gone
for aid, act in [("icon_action", icon_action), ("material_action", material_icon_action)]:
assert aid not in toolbar.widgets
assert act.action not in toolbar.actions()
def test_trigger_removed_action_raises(toolbar_fixture, icon_action, dummy_widget, qtbot):
"""
Add an action, connect a mock slot, then remove the action and verify that
attempting to trigger it afterwards raises RuntimeError (since the underlying
QAction has been deleted).
"""
toolbar = toolbar_fixture
# Add the action and connect a mock slot
toolbar.add_action("icon_action", icon_action, dummy_widget)
called = []
def mock_slot():
called.append(True)
icon_action.action.triggered.connect(mock_slot)
# Trigger once to confirm connection works
icon_action.action.trigger()
assert called == [True]
# Now remove the action
toolbar.remove_action("icon_action")
# Allow deleteLater event to process
qtbot.wait(50)
# The underlying C++ object should be deleted; triggering should raise
with pytest.raises(RuntimeError):
icon_action.action.trigger()
def test_remove_nonexistent_action(toolbar_fixture):
"""
Attempting to remove an action that does not exist should raise ValueError.
Attempting to remove an action that does not exist should raise KeyError.
"""
toolbar = toolbar_fixture
with pytest.raises(ValueError) as excinfo:
toolbar.remove_action("nonexistent_action")
with pytest.raises(KeyError) as excinfo:
toolbar.components.remove_action("nonexistent_action")
assert "Action with ID 'nonexistent_action' does not exist." in str(excinfo.value)
def test_remove_nonexistent_bundle(toolbar_fixture):
"""
Attempting to remove a bundle that does not exist should raise ValueError.
Attempting to remove a bundle that does not exist should raise KeyError.
"""
toolbar = toolbar_fixture
with pytest.raises(ValueError) as excinfo:
with pytest.raises(KeyError) as excinfo:
toolbar.remove_bundle("nonexistent_bundle")
assert "Bundle 'nonexistent_bundle' does not exist." in str(excinfo.value)
excinfo.match("Bundle with name 'nonexistent_bundle' does not exist.")