mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-13 19:21:50 +02:00
refactor(toolbar): split toolbar into components, bundles and connections
This commit is contained in:
@ -97,13 +97,15 @@ def test_new_dock_raises_for_invalid_name(bec_dock_area):
|
||||
# Toolbar Actions
|
||||
###################################
|
||||
def test_toolbar_add_plot_waveform(bec_dock_area):
|
||||
bec_dock_area.toolbar.widgets["menu_plots"].widgets["waveform"].trigger()
|
||||
bec_dock_area.toolbar.components.get_action("menu_plots").actions["waveform"].action.trigger()
|
||||
assert "waveform_0" in bec_dock_area.panels
|
||||
assert bec_dock_area.panels["waveform_0"].widgets[0].config.widget_class == "Waveform"
|
||||
|
||||
|
||||
def test_toolbar_add_plot_scatter_waveform(bec_dock_area):
|
||||
bec_dock_area.toolbar.widgets["menu_plots"].widgets["scatter_waveform"].trigger()
|
||||
bec_dock_area.toolbar.components.get_action("menu_plots").actions[
|
||||
"scatter_waveform"
|
||||
].action.trigger()
|
||||
assert "scatter_waveform_0" in bec_dock_area.panels
|
||||
assert (
|
||||
bec_dock_area.panels["scatter_waveform_0"].widgets[0].config.widget_class
|
||||
@ -112,19 +114,22 @@ def test_toolbar_add_plot_scatter_waveform(bec_dock_area):
|
||||
|
||||
|
||||
def test_toolbar_add_plot_image(bec_dock_area):
|
||||
bec_dock_area.toolbar.widgets["menu_plots"].widgets["image"].trigger()
|
||||
bec_dock_area.toolbar.components.get_action("menu_plots").actions["image"].action.trigger()
|
||||
assert "image_0" in bec_dock_area.panels
|
||||
assert bec_dock_area.panels["image_0"].widgets[0].config.widget_class == "Image"
|
||||
|
||||
|
||||
def test_toolbar_add_plot_motor_map(bec_dock_area):
|
||||
bec_dock_area.toolbar.widgets["menu_plots"].widgets["motor_map"].trigger()
|
||||
bec_dock_area.toolbar.components.get_action("menu_plots").actions["motor_map"].action.trigger()
|
||||
assert "motor_map_0" in bec_dock_area.panels
|
||||
assert bec_dock_area.panels["motor_map_0"].widgets[0].config.widget_class == "MotorMap"
|
||||
|
||||
|
||||
def test_toolbar_add_multi_waveform(bec_dock_area):
|
||||
bec_dock_area.toolbar.widgets["menu_plots"].widgets["multi_waveform"].trigger()
|
||||
bec_dock_area.toolbar.components.get_action("menu_plots").actions[
|
||||
"multi_waveform"
|
||||
].action.trigger()
|
||||
# Check if the MultiWaveform panel is created
|
||||
assert "multi_waveform_0" in bec_dock_area.panels
|
||||
assert (
|
||||
bec_dock_area.panels["multi_waveform_0"].widgets[0].config.widget_class == "MultiWaveform"
|
||||
@ -132,7 +137,9 @@ def test_toolbar_add_multi_waveform(bec_dock_area):
|
||||
|
||||
|
||||
def test_toolbar_add_device_positioner_box(bec_dock_area):
|
||||
bec_dock_area.toolbar.widgets["menu_devices"].widgets["positioner_box"].trigger()
|
||||
bec_dock_area.toolbar.components.get_action("menu_devices").actions[
|
||||
"positioner_box"
|
||||
].action.trigger()
|
||||
assert "positioner_box_0" in bec_dock_area.panels
|
||||
assert (
|
||||
bec_dock_area.panels["positioner_box_0"].widgets[0].config.widget_class == "PositionerBox"
|
||||
@ -143,19 +150,21 @@ def test_toolbar_add_utils_queue(bec_dock_area, bec_queue_msg_full):
|
||||
bec_dock_area.client.connector.set_and_publish(
|
||||
MessageEndpoints.scan_queue_status(), bec_queue_msg_full
|
||||
)
|
||||
bec_dock_area.toolbar.widgets["menu_utils"].widgets["queue"].trigger()
|
||||
bec_dock_area.toolbar.components.get_action("menu_utils").actions["queue"].action.trigger()
|
||||
assert "bec_queue_0" in bec_dock_area.panels
|
||||
assert bec_dock_area.panels["bec_queue_0"].widgets[0].config.widget_class == "BECQueue"
|
||||
|
||||
|
||||
def test_toolbar_add_utils_status(bec_dock_area):
|
||||
bec_dock_area.toolbar.widgets["menu_utils"].widgets["status"].trigger()
|
||||
bec_dock_area.toolbar.components.get_action("menu_utils").actions["status"].action.trigger()
|
||||
assert "bec_status_box_0" in bec_dock_area.panels
|
||||
assert bec_dock_area.panels["bec_status_box_0"].widgets[0].config.widget_class == "BECStatusBox"
|
||||
|
||||
|
||||
def test_toolbar_add_utils_progress_bar(bec_dock_area):
|
||||
bec_dock_area.toolbar.widgets["menu_utils"].widgets["progress_bar"].trigger()
|
||||
bec_dock_area.toolbar.components.get_action("menu_utils").actions[
|
||||
"progress_bar"
|
||||
].action.trigger()
|
||||
assert "ring_progress_bar_0" in bec_dock_area.panels
|
||||
assert (
|
||||
bec_dock_area.panels["ring_progress_bar_0"].widgets[0].config.widget_class
|
||||
|
@ -157,10 +157,10 @@ def test_curve_tree_init(curve_tree_fixture):
|
||||
assert curve_tree.color_palette == "plasma"
|
||||
assert curve_tree.tree.columnCount() == 7
|
||||
|
||||
assert "add" in curve_tree.toolbar.widgets
|
||||
assert "expand_all" in curve_tree.toolbar.widgets
|
||||
assert "collapse_all" in curve_tree.toolbar.widgets
|
||||
assert "renormalize_colors" in curve_tree.toolbar.widgets
|
||||
assert curve_tree.toolbar.components.exists("add")
|
||||
assert curve_tree.toolbar.components.exists("expand")
|
||||
assert curve_tree.toolbar.components.exists("collapse")
|
||||
assert curve_tree.toolbar.components.exists("renormalize_colors")
|
||||
|
||||
|
||||
def test_add_new_curve(curve_tree_fixture):
|
||||
|
@ -39,9 +39,11 @@ def test_initialization(roi_tree, image_widget):
|
||||
assert len(roi_tree.tree.findItems("", Qt.MatchContains)) == 0 # Empty tree initially
|
||||
|
||||
# Check toolbar actions
|
||||
assert hasattr(roi_tree, "add_rect_action")
|
||||
assert hasattr(roi_tree, "add_circle_action")
|
||||
assert hasattr(roi_tree, "expand_toggle")
|
||||
assert roi_tree.toolbar.components.get_action("roi_rectangle")
|
||||
assert roi_tree.toolbar.components.get_action("roi_circle")
|
||||
assert roi_tree.toolbar.components.get_action("roi_ellipse")
|
||||
assert roi_tree.toolbar.components.get_action("expand_toggle")
|
||||
assert roi_tree.toolbar.components.get_action("lock_unlock_all")
|
||||
|
||||
# Check tree view setup
|
||||
assert roi_tree.tree.columnCount() == 3
|
||||
@ -216,23 +218,25 @@ def test_draw_mode_toggle(roi_tree, qtbot):
|
||||
assert roi_tree._roi_draw_mode is None
|
||||
|
||||
# Toggle rect mode on
|
||||
roi_tree.add_rect_action.action.toggle()
|
||||
rect_action = roi_tree.toolbar.components.get_action("roi_rectangle").action
|
||||
circle_action = roi_tree.toolbar.components.get_action("roi_circle").action
|
||||
rect_action.toggle()
|
||||
assert roi_tree._roi_draw_mode == "rect"
|
||||
assert roi_tree.add_rect_action.action.isChecked()
|
||||
assert not roi_tree.add_circle_action.action.isChecked()
|
||||
assert rect_action.isChecked()
|
||||
assert not circle_action.isChecked()
|
||||
|
||||
# Toggle circle mode on (should turn off rect mode)
|
||||
roi_tree.add_circle_action.action.toggle()
|
||||
circle_action.toggle()
|
||||
qtbot.wait(200)
|
||||
assert roi_tree._roi_draw_mode == "circle"
|
||||
assert not roi_tree.add_rect_action.action.isChecked()
|
||||
assert roi_tree.add_circle_action.action.isChecked()
|
||||
assert not rect_action.isChecked()
|
||||
assert circle_action.isChecked()
|
||||
|
||||
# Toggle circle mode off
|
||||
roi_tree.add_circle_action.action.toggle()
|
||||
circle_action.toggle()
|
||||
assert roi_tree._roi_draw_mode is None
|
||||
assert not roi_tree.add_rect_action.action.isChecked()
|
||||
assert not roi_tree.add_circle_action.action.isChecked()
|
||||
assert not circle_action.isChecked()
|
||||
assert not rect_action.isChecked()
|
||||
|
||||
|
||||
def test_add_roi_from_toolbar(qtbot, mocked_client):
|
||||
@ -250,7 +254,7 @@ def test_add_roi_from_toolbar(qtbot, mocked_client):
|
||||
|
||||
# Test rectangle ROI creation
|
||||
# 1. Activate rectangle drawing mode
|
||||
roi_tree.add_rect_action.action.setChecked(True)
|
||||
roi_tree.toolbar.components.get_action("roi_rectangle").action.setChecked(True)
|
||||
assert roi_tree._roi_draw_mode == "rect"
|
||||
|
||||
# Get plot widget and view
|
||||
@ -294,8 +298,8 @@ def test_add_roi_from_toolbar(qtbot, mocked_client):
|
||||
|
||||
# Test circle ROI creation
|
||||
# Reset ROI draw mode
|
||||
roi_tree.add_rect_action.action.setChecked(False)
|
||||
roi_tree.add_circle_action.action.setChecked(True)
|
||||
roi_tree.toolbar.components.get_action("roi_rectangle").action.setChecked(False)
|
||||
roi_tree.toolbar.components.get_action("roi_circle").action.setChecked(True)
|
||||
assert roi_tree._roi_draw_mode == "circle"
|
||||
|
||||
# Define new positions for circle ROI
|
||||
|
@ -242,10 +242,11 @@ def test_image_data_update_1d(qtbot, mocked_client):
|
||||
|
||||
def test_toolbar_actions_presence(qtbot, mocked_client):
|
||||
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
|
||||
assert "autorange_image" in bec_image_view.toolbar.widgets
|
||||
assert "lock_aspect_ratio" in bec_image_view.toolbar.bundles["mouse_interaction"]
|
||||
assert "processing" in bec_image_view.toolbar.bundles
|
||||
assert "selection" in bec_image_view.toolbar.bundles
|
||||
assert bec_image_view.toolbar.components.exists("image_autorange")
|
||||
assert bec_image_view.toolbar.components.exists("lock_aspect_ratio")
|
||||
assert bec_image_view.toolbar.components.exists("image_processing_fft")
|
||||
assert bec_image_view.toolbar.components.exists("image_device_combo")
|
||||
assert bec_image_view.toolbar.components.exists("image_dim_combo")
|
||||
|
||||
|
||||
def test_image_processing_fft_toggle(qtbot, mocked_client):
|
||||
@ -304,8 +305,8 @@ def test_setting_vrange_with_colorbar(qtbot, mocked_client, colorbar_type):
|
||||
def test_setup_image_from_toolbar(qtbot, mocked_client):
|
||||
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
|
||||
|
||||
bec_image_view.selection_bundle.device_combo_box.setCurrentText("eiger")
|
||||
bec_image_view.selection_bundle.dim_combo_box.setCurrentText("2d")
|
||||
bec_image_view.device_combo_box.setCurrentText("eiger")
|
||||
bec_image_view.dim_combo_box.setCurrentText("2d")
|
||||
|
||||
assert bec_image_view.monitor == "eiger"
|
||||
assert bec_image_view.subscriptions["main"].source == "device_monitor_2d"
|
||||
@ -318,17 +319,17 @@ def test_image_actions_interactions(qtbot, mocked_client):
|
||||
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
|
||||
bec_image_view.autorange = False # Change the initial state to False
|
||||
|
||||
bec_image_view.autorange_mean_action.action.trigger()
|
||||
bec_image_view.toolbar.components.get_action("image_autorange_mean").action.trigger()
|
||||
assert bec_image_view.autorange is True
|
||||
assert bec_image_view.main_image.autorange is True
|
||||
assert bec_image_view.autorange_mode == "mean"
|
||||
|
||||
bec_image_view.autorange_max_action.action.trigger()
|
||||
bec_image_view.toolbar.components.get_action("image_autorange_max").action.trigger()
|
||||
assert bec_image_view.autorange is True
|
||||
assert bec_image_view.main_image.autorange is True
|
||||
assert bec_image_view.autorange_mode == "max"
|
||||
|
||||
bec_image_view.toolbar.widgets["lock_aspect_ratio"].action.trigger()
|
||||
bec_image_view.toolbar.components.get_action("lock_aspect_ratio").action.trigger()
|
||||
assert bec_image_view.lock_aspect_ratio is False
|
||||
assert bool(bec_image_view.plot_item.getViewBox().state["aspectLocked"]) is False
|
||||
|
||||
@ -336,7 +337,7 @@ def test_image_actions_interactions(qtbot, mocked_client):
|
||||
def test_image_toggle_action_fft(qtbot, mocked_client):
|
||||
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
|
||||
|
||||
bec_image_view.processing_bundle.fft.action.trigger()
|
||||
bec_image_view.toolbar.components.get_action("image_processing_fft").action.trigger()
|
||||
|
||||
assert bec_image_view.fft is True
|
||||
assert bec_image_view.main_image.fft is True
|
||||
@ -346,7 +347,7 @@ def test_image_toggle_action_fft(qtbot, mocked_client):
|
||||
def test_image_toggle_action_log(qtbot, mocked_client):
|
||||
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
|
||||
|
||||
bec_image_view.processing_bundle.log.action.trigger()
|
||||
bec_image_view.toolbar.components.get_action("image_processing_log").action.trigger()
|
||||
|
||||
assert bec_image_view.log is True
|
||||
assert bec_image_view.main_image.log is True
|
||||
@ -356,7 +357,7 @@ def test_image_toggle_action_log(qtbot, mocked_client):
|
||||
def test_image_toggle_action_transpose(qtbot, mocked_client):
|
||||
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
|
||||
|
||||
bec_image_view.processing_bundle.transpose.action.trigger()
|
||||
bec_image_view.toolbar.components.get_action("image_processing_transpose").action.trigger()
|
||||
|
||||
assert bec_image_view.transpose is True
|
||||
assert bec_image_view.main_image.transpose is True
|
||||
@ -366,7 +367,7 @@ def test_image_toggle_action_transpose(qtbot, mocked_client):
|
||||
def test_image_toggle_action_rotate_right(qtbot, mocked_client):
|
||||
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
|
||||
|
||||
bec_image_view.processing_bundle.right.action.trigger()
|
||||
bec_image_view.toolbar.components.get_action("image_processing_rotate_right").action.trigger()
|
||||
|
||||
assert bec_image_view.num_rotation_90 == 3
|
||||
assert bec_image_view.main_image.num_rotation_90 == 3
|
||||
@ -376,7 +377,7 @@ def test_image_toggle_action_rotate_right(qtbot, mocked_client):
|
||||
def test_image_toggle_action_rotate_left(qtbot, mocked_client):
|
||||
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
|
||||
|
||||
bec_image_view.processing_bundle.left.action.trigger()
|
||||
bec_image_view.toolbar.components.get_action("image_processing_rotate_left").action.trigger()
|
||||
|
||||
assert bec_image_view.num_rotation_90 == 1
|
||||
assert bec_image_view.main_image.num_rotation_90 == 1
|
||||
@ -392,7 +393,7 @@ def test_image_toggle_action_reset(qtbot, mocked_client):
|
||||
bec_image_view.transpose = True
|
||||
bec_image_view.num_rotation_90 = 2
|
||||
|
||||
bec_image_view.processing_bundle.reset.action.trigger()
|
||||
bec_image_view.toolbar.components.get_action("image_processing_reset").action.trigger()
|
||||
|
||||
assert bec_image_view.num_rotation_90 == 0
|
||||
assert bec_image_view.main_image.num_rotation_90 == 0
|
||||
@ -473,8 +474,8 @@ def test_show_roi_manager_popup(qtbot, mocked_client):
|
||||
view = create_widget(qtbot, Image, client=mocked_client, popups=True)
|
||||
|
||||
# ROI-manager toggle is exposed via the toolbar.
|
||||
assert "roi_mgr" in view.toolbar.widgets
|
||||
roi_action = view.toolbar.widgets["roi_mgr"].action
|
||||
assert view.toolbar.components.exists("roi_mgr")
|
||||
roi_action = view.toolbar.components.get_action("roi_mgr").action
|
||||
assert roi_action.isChecked() is False, "Should start unchecked"
|
||||
|
||||
# Open the popup.
|
||||
@ -497,10 +498,10 @@ def test_show_roi_manager_popup(qtbot, mocked_client):
|
||||
|
||||
def test_crosshair_roi_panels_visibility(qtbot, mocked_client):
|
||||
"""
|
||||
Verify that enabling the ROI‑crosshair shows ROI panels and disabling hides them.
|
||||
Verify that enabling the ROI-crosshair shows ROI panels and disabling hides them.
|
||||
"""
|
||||
bec_image_view = create_widget(qtbot, Image, client=mocked_client)
|
||||
switch = bec_image_view.toolbar.widgets["switch_crosshair"]
|
||||
switch = bec_image_view.toolbar.components.get_action("image_switch_crosshair")
|
||||
|
||||
# Initially panels should be hidden
|
||||
assert bec_image_view.side_panel_x.panel_height == 0
|
||||
@ -548,7 +549,7 @@ def test_roi_plot_data_from_image(qtbot, mocked_client):
|
||||
bec_image_view.on_image_update_2d({"data": test_data}, {})
|
||||
|
||||
# Activate ROI crosshair
|
||||
switch = bec_image_view.toolbar.widgets["switch_crosshair"]
|
||||
switch = bec_image_view.toolbar.components.get_action("image_switch_crosshair")
|
||||
switch.actions["crosshair_roi"].action.trigger()
|
||||
qtbot.wait(50)
|
||||
|
||||
@ -579,11 +580,10 @@ def test_roi_plot_data_from_image(qtbot, mocked_client):
|
||||
def test_monitor_selection_reverse_device_items(qtbot, mocked_client):
|
||||
"""
|
||||
Verify that _reverse_device_items correctly reverses the order of items in the
|
||||
device combo‑box while preserving the current selection.
|
||||
device combobox while preserving the current selection.
|
||||
"""
|
||||
view = create_widget(qtbot, Image, client=mocked_client)
|
||||
bundle = view.selection_bundle
|
||||
combo = bundle.device_combo_box
|
||||
combo = view.device_combo_box
|
||||
|
||||
# Replace existing items with a deterministic list
|
||||
combo.clear()
|
||||
@ -593,7 +593,7 @@ def test_monitor_selection_reverse_device_items(qtbot, mocked_client):
|
||||
combo.setCurrentText("samy")
|
||||
|
||||
# Reverse the items
|
||||
bundle._reverse_device_items()
|
||||
view._reverse_device_items()
|
||||
|
||||
# Order should be reversed and selection preserved
|
||||
assert [combo.itemText(i) for i in range(combo.count())] == ["samz", "samy", "samx"]
|
||||
@ -606,7 +606,6 @@ def test_monitor_selection_populate_preview_signals(qtbot, mocked_client, monkey
|
||||
with the correct userData.
|
||||
"""
|
||||
view = create_widget(qtbot, Image, client=mocked_client)
|
||||
bundle = view.selection_bundle
|
||||
|
||||
# Provide a deterministic fake device_manager with get_bec_signals
|
||||
class _FakeDM:
|
||||
@ -618,27 +617,26 @@ def test_monitor_selection_populate_preview_signals(qtbot, mocked_client, monkey
|
||||
|
||||
monkeypatch.setattr(view.client, "device_manager", _FakeDM())
|
||||
|
||||
initial_count = bundle.device_combo_box.count()
|
||||
initial_count = view.device_combo_box.count()
|
||||
|
||||
bundle._populate_preview_signals()
|
||||
view._populate_preview_signals()
|
||||
|
||||
# Two new entries should have been added
|
||||
assert bundle.device_combo_box.count() == initial_count + 2
|
||||
assert view.device_combo_box.count() == initial_count + 2
|
||||
|
||||
# The first newly added item should carry tuple userData describing the device/signal
|
||||
data = bundle.device_combo_box.itemData(initial_count)
|
||||
data = view.device_combo_box.itemData(initial_count)
|
||||
assert isinstance(data, tuple) and data[0] == "eiger"
|
||||
|
||||
|
||||
def test_monitor_selection_adjust_and_connect(qtbot, mocked_client, monkeypatch):
|
||||
"""
|
||||
Verify that _adjust_and_connect performs the full set‑up:
|
||||
‑ fills the combo‑box with preview signals,
|
||||
‑ reverses their order,
|
||||
‑ and resets the currentText to an empty string.
|
||||
Verify that _adjust_and_connect performs the full set-up:
|
||||
- fills the combobox with preview signals,
|
||||
- reverses their order,
|
||||
- and resets the currentText to an empty string.
|
||||
"""
|
||||
view = create_widget(qtbot, Image, client=mocked_client)
|
||||
bundle = view.selection_bundle
|
||||
|
||||
# Deterministic fake device_manager
|
||||
class _FakeDM:
|
||||
@ -647,14 +645,14 @@ def test_monitor_selection_adjust_and_connect(qtbot, mocked_client, monkeypatch)
|
||||
|
||||
monkeypatch.setattr(view.client, "device_manager", _FakeDM())
|
||||
|
||||
combo = bundle.device_combo_box
|
||||
combo = view.device_combo_box
|
||||
# Start from a clean state
|
||||
combo.clear()
|
||||
combo.addItem("", None)
|
||||
combo.setCurrentText("")
|
||||
|
||||
# Execute the method under test
|
||||
bundle._adjust_and_connect()
|
||||
view._adjust_and_connect()
|
||||
|
||||
# Expect exactly two items: preview label followed by the empty default
|
||||
assert combo.count() == 2
|
||||
|
@ -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 clean‑up.
|
||||
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.")
|
||||
|
@ -272,16 +272,15 @@ def test_motor_map_toolbar_selection(qtbot, mocked_client):
|
||||
mm = create_widget(qtbot, MotorMap, client=mocked_client)
|
||||
|
||||
# Verify toolbar bundle was created during initialization
|
||||
assert hasattr(mm, "motor_selection_bundle")
|
||||
assert mm.motor_selection_bundle is not None
|
||||
motor_selection = mm.toolbar.components.get_action("motor_selection")
|
||||
|
||||
mm.motor_selection_bundle.motor_x.setCurrentText("samx")
|
||||
mm.motor_selection_bundle.motor_y.setCurrentText("samy")
|
||||
motor_selection.motor_x.setCurrentText("samx")
|
||||
motor_selection.motor_y.setCurrentText("samy")
|
||||
|
||||
assert mm.config.x_motor.name == "samx"
|
||||
assert mm.config.y_motor.name == "samy"
|
||||
|
||||
mm.motor_selection_bundle.motor_y.setCurrentText("samz")
|
||||
motor_selection.motor_y.setCurrentText("samz")
|
||||
|
||||
assert mm.config.x_motor.name == "samx"
|
||||
assert mm.config.y_motor.name == "samz"
|
||||
@ -291,9 +290,9 @@ def test_motor_map_settings_dialog(qtbot, mocked_client):
|
||||
"""Test the settings dialog for the motor map."""
|
||||
mm = create_widget(qtbot, MotorMap, client=mocked_client, popups=True)
|
||||
|
||||
assert "popup_bundle" in mm.toolbar.bundles
|
||||
for action_id in mm.toolbar.bundles["popup_bundle"]:
|
||||
assert mm.toolbar.widgets[action_id].action.isVisible() is True
|
||||
assert "axis_popup" in mm.toolbar.bundles
|
||||
for action_ref in mm.toolbar.bundles["axis_popup"].bundle_actions.values():
|
||||
assert action_ref().action.isVisible()
|
||||
|
||||
# set properties to be fetched by dialog
|
||||
mm.map(x_name="samx", y_name="samy")
|
||||
|
@ -244,15 +244,14 @@ def test_selection_toolbar_updates_widget(qtbot, mocked_client):
|
||||
updates the widget properties.
|
||||
"""
|
||||
mw = create_widget(qtbot, MultiWaveform, client=mocked_client)
|
||||
toolbar = mw.monitor_selection_bundle
|
||||
monitor_combo = toolbar.monitor
|
||||
colormap_widget = toolbar.colormap_widget
|
||||
monitor_selection_action = mw.toolbar.components.get_action("monitor_selection")
|
||||
cmap_action = mw.toolbar.components.get_action("color_map")
|
||||
|
||||
monitor_combo.addItem("waveform1d")
|
||||
monitor_combo.setCurrentText("waveform1d")
|
||||
monitor_selection_action.combobox.addItem("waveform1d")
|
||||
monitor_selection_action.combobox.setCurrentText("waveform1d")
|
||||
assert mw.monitor == "waveform1d"
|
||||
|
||||
colormap_widget.colormap = "viridis"
|
||||
cmap_action.widget.colormap = "viridis"
|
||||
assert mw.color_palette == "viridis"
|
||||
|
||||
|
||||
@ -290,11 +289,10 @@ def test_control_panel_opacity_slider_spinbox(qtbot, mocked_client):
|
||||
def test_control_panel_highlight_slider_spinbox(qtbot, mocked_client):
|
||||
"""
|
||||
Test that the slider and spinbox for curve highlighting update
|
||||
the widget’s highlighted_index property, and are disabled if
|
||||
the widget's highlighted_index property, and are disabled if
|
||||
highlight_last_curve is True.
|
||||
"""
|
||||
mw = create_widget(qtbot, MultiWaveform, client=mocked_client)
|
||||
|
||||
slider_index = mw.controls.ui.highlighted_index
|
||||
spinbox_index = mw.controls.ui.spinbox_index
|
||||
checkbox_highlight_last = mw.controls.ui.highlight_last_curve
|
||||
|
@ -265,54 +265,56 @@ def test_ui_mode_popup(qtbot, mocked_client):
|
||||
pb = create_widget(qtbot, PlotBase, client=mocked_client)
|
||||
pb.ui_mode = UIMode.POPUP
|
||||
# The popup bundle should be created and its actions made visible.
|
||||
assert "popup_bundle" in pb.toolbar.bundles
|
||||
for action_id in pb.toolbar.bundles["popup_bundle"]:
|
||||
assert pb.toolbar.widgets[action_id].action.isVisible() is True
|
||||
assert "axis_popup" in pb.toolbar.bundles
|
||||
for action_ref in pb.toolbar.bundles["axis_popup"].bundle_actions.values():
|
||||
assert action_ref().action.isVisible() is True
|
||||
# The side panel should be hidden.
|
||||
assert not pb.side_panel.isVisible()
|
||||
|
||||
|
||||
def test_ui_mode_side(qtbot, mocked_client):
|
||||
"""
|
||||
Test that setting ui_mode to SIDE shows the side panel and ensures any popup actions
|
||||
are hidden.
|
||||
"""
|
||||
pb = create_widget(qtbot, PlotBase, client=mocked_client)
|
||||
pb.ui_mode = UIMode.SIDE
|
||||
# If a popup bundle exists, its actions should be hidden.
|
||||
if "popup_bundle" in pb.toolbar.bundles:
|
||||
for action_id in pb.toolbar.bundles["popup_bundle"]:
|
||||
assert pb.toolbar.widgets[action_id].action.isVisible() is False
|
||||
# Side panels are not properly implemented yet. Once the logic is fixed, we can re-enable this test.
|
||||
# See issue #742
|
||||
# def test_ui_mode_side(qtbot, mocked_client):
|
||||
# """
|
||||
# Test that setting ui_mode to SIDE shows the side panel and ensures any popup actions
|
||||
# are hidden.
|
||||
# """
|
||||
# pb = create_widget(qtbot, PlotBase, client=mocked_client)
|
||||
# pb.ui_mode = UIMode.SIDE
|
||||
# # If a popup bundle exists, its actions should be hidden.
|
||||
# if "axis_popup" in pb.toolbar.bundles:
|
||||
# for action_ref in pb.toolbar.bundles["axis_popup"].bundle_actions.values():
|
||||
# assert action_ref().action.isVisible() is False
|
||||
|
||||
|
||||
def test_enable_popups_property(qtbot, mocked_client):
|
||||
"""
|
||||
Test the enable_popups property: when enabled, ui_mode should be POPUP,
|
||||
and when disabled, ui_mode should change to NONE.
|
||||
"""
|
||||
pb = create_widget(qtbot, PlotBase, client=mocked_client)
|
||||
pb.enable_popups = True
|
||||
assert pb.ui_mode == UIMode.POPUP
|
||||
# The popup bundle actions should be visible.
|
||||
assert "popup_bundle" in pb.toolbar.bundles
|
||||
for action_id in pb.toolbar.bundles["popup_bundle"]:
|
||||
assert pb.toolbar.widgets[action_id].action.isVisible() is True
|
||||
# def test_enable_popups_property(qtbot, mocked_client):
|
||||
# """
|
||||
# Test the enable_popups property: when enabled, ui_mode should be POPUP,
|
||||
# and when disabled, ui_mode should change to NONE.
|
||||
# """
|
||||
# pb = create_widget(qtbot, PlotBase, client=mocked_client)
|
||||
# pb.enable_popups = True
|
||||
# assert pb.ui_mode == UIMode.POPUP
|
||||
# # The popup bundle actions should be visible.
|
||||
# assert "popup_bundle" in pb.toolbar.bundles
|
||||
# for action_id in pb.toolbar.bundles["popup_bundle"]:
|
||||
# assert pb.toolbar.widgets[action_id].action.isVisible() is True
|
||||
|
||||
pb.enable_popups = False
|
||||
assert pb.ui_mode == UIMode.NONE
|
||||
# pb.enable_popups = False
|
||||
# assert pb.ui_mode == UIMode.NONE
|
||||
|
||||
|
||||
def test_enable_side_panel_property(qtbot, mocked_client):
|
||||
"""
|
||||
Test the enable_side_panel property: when enabled, ui_mode should be SIDE,
|
||||
and when disabled, ui_mode should change to NONE.
|
||||
"""
|
||||
pb = create_widget(qtbot, PlotBase, client=mocked_client)
|
||||
pb.enable_side_panel = True
|
||||
assert pb.ui_mode == UIMode.SIDE
|
||||
# def test_enable_side_panel_property(qtbot, mocked_client):
|
||||
# """
|
||||
# Test the enable_side_panel property: when enabled, ui_mode should be SIDE,
|
||||
# and when disabled, ui_mode should change to NONE.
|
||||
# """
|
||||
# pb = create_widget(qtbot, PlotBase, client=mocked_client)
|
||||
# pb.enable_side_panel = True
|
||||
# assert pb.ui_mode == UIMode.SIDE
|
||||
|
||||
pb.enable_side_panel = False
|
||||
assert pb.ui_mode == UIMode.NONE
|
||||
# pb.enable_side_panel = False
|
||||
# assert pb.ui_mode == UIMode.NONE
|
||||
|
||||
|
||||
def test_switching_between_popup_and_side_panel_closes_dialog(qtbot, mocked_client):
|
||||
@ -323,18 +325,19 @@ def test_switching_between_popup_and_side_panel_closes_dialog(qtbot, mocked_clie
|
||||
pb = create_widget(qtbot, PlotBase, client=mocked_client)
|
||||
pb.ui_mode = UIMode.POPUP
|
||||
# Open the axis settings popup.
|
||||
pb.show_axis_settings_popup()
|
||||
pb_connection = pb.toolbar.bundles["axis_popup"].get_connection("plot_base")
|
||||
pb_connection.show_axis_settings_popup()
|
||||
qtbot.wait(100)
|
||||
# The dialog should now exist and be visible.
|
||||
assert pb.axis_settings_dialog is not None
|
||||
assert pb.axis_settings_dialog.isVisible() is True
|
||||
assert pb_connection.axis_settings_dialog is not None
|
||||
assert pb_connection.axis_settings_dialog.isVisible() is True
|
||||
|
||||
# Switch to side panel mode.
|
||||
pb.ui_mode = UIMode.SIDE
|
||||
qtbot.wait(100)
|
||||
# The axis settings dialog should be closed (and reference cleared).
|
||||
|
||||
qtbot.waitUntil(lambda: pb.axis_settings_dialog is None, timeout=5000)
|
||||
qtbot.waitUntil(lambda: pb_connection.axis_settings_dialog is None, timeout=5000)
|
||||
|
||||
|
||||
def test_enable_fps_monitor_property(qtbot, mocked_client):
|
||||
|
@ -136,7 +136,7 @@ def test_add_menu(side_panel_fixture, menu_widget, qtbot):
|
||||
|
||||
assert panel.stack_widget.count() == initial_count + 1
|
||||
# Verify the action is added to the toolbar
|
||||
action = panel.toolbar.widgets.get("test_action")
|
||||
action = panel.toolbar.components.get_action("test_action")
|
||||
assert action is not None
|
||||
assert action.tooltip == "Test Tooltip"
|
||||
assert action.action in panel.toolbar.actions()
|
||||
@ -155,7 +155,7 @@ def test_toggle_action_show_panel(side_panel_fixture, menu_widget, qtbot):
|
||||
)
|
||||
qtbot.wait(100)
|
||||
|
||||
action = panel.toolbar.widgets.get("toggle_action")
|
||||
action = panel.toolbar.components.get_action("toggle_action")
|
||||
assert action is not None
|
||||
|
||||
# Initially, panel should be hidden
|
||||
@ -199,8 +199,8 @@ def test_switch_actions(side_panel_fixture, menu_widget, qtbot):
|
||||
)
|
||||
qtbot.wait(100)
|
||||
|
||||
action1 = panel.toolbar.widgets.get("action1")
|
||||
action2 = panel.toolbar.widgets.get("action2")
|
||||
action1 = panel.toolbar.components.get_action("action1")
|
||||
action2 = panel.toolbar.components.get_action("action2")
|
||||
assert action1 is not None
|
||||
assert action2 is not None
|
||||
|
||||
@ -241,7 +241,7 @@ def test_multiple_add_menu(side_panel_fixture, menu_widget, qtbot):
|
||||
)
|
||||
qtbot.wait(100)
|
||||
assert panel.stack_widget.count() == initial_count + i + 1
|
||||
action = panel.toolbar.widgets.get(f"action{i}")
|
||||
action = panel.toolbar.components.get_action(f"action{i}")
|
||||
assert action is not None
|
||||
assert action.tooltip == f"Tooltip{i}"
|
||||
assert action.action in panel.toolbar.actions()
|
||||
@ -360,7 +360,7 @@ def test_add_multiple_menus(side_panel_fixture, menu_widget, qtbot):
|
||||
)
|
||||
qtbot.wait(100)
|
||||
assert panel.stack_widget.count() == initial_count + i + 1
|
||||
action = panel.toolbar.widgets.get(f"action{i}")
|
||||
action = panel.toolbar.components.get_action(f"action{i}")
|
||||
assert action is not None
|
||||
assert action.tooltip == f"Tooltip{i}"
|
||||
assert action.action in panel.toolbar.actions()
|
||||
|
@ -797,7 +797,7 @@ def test_show_curve_settings_popup(qtbot, mocked_client):
|
||||
"""
|
||||
wf = create_widget(qtbot, Waveform, client=mocked_client)
|
||||
|
||||
curve_action = wf.toolbar.widgets["curve"].action
|
||||
curve_action = wf.toolbar.components.get_action("curve").action
|
||||
assert not curve_action.isChecked(), "Should start unchecked"
|
||||
|
||||
wf.show_curve_settings_popup()
|
||||
@ -807,8 +807,9 @@ def test_show_curve_settings_popup(qtbot, mocked_client):
|
||||
assert curve_action.isChecked()
|
||||
|
||||
# add a new row to the curve tree
|
||||
wf.curve_settings_dialog.widget.curve_manager.toolbar.widgets["add"].action.trigger()
|
||||
wf.curve_settings_dialog.widget.curve_manager.toolbar.widgets["add"].action.trigger()
|
||||
add_action = wf.curve_settings_dialog.widget.curve_manager.toolbar.components.get_action("add")
|
||||
add_action.action.trigger()
|
||||
add_action.action.trigger()
|
||||
qtbot.wait(100)
|
||||
# Check that the new row is added
|
||||
assert wf.curve_settings_dialog.widget.curve_manager.tree.model().rowCount() == 2
|
||||
@ -824,9 +825,9 @@ def test_show_dap_summary_popup(qtbot, mocked_client):
|
||||
"""
|
||||
wf = create_widget(qtbot, Waveform, client=mocked_client, popups=True)
|
||||
|
||||
assert "fit_params" in wf.toolbar.widgets
|
||||
assert wf.toolbar.components.exists("fit_params")
|
||||
|
||||
fit_action = wf.toolbar.widgets["fit_params"].action
|
||||
fit_action = wf.toolbar.components.get_action("fit_params").action
|
||||
assert fit_action.isChecked() is False
|
||||
|
||||
wf.show_dap_summary_popup()
|
||||
|
Reference in New Issue
Block a user