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

feat(advanced_dock_area): created DockAreaWidget base class; profile management through namespaces; dock area variants

This commit is contained in:
2025-11-04 16:59:23 +01:00
parent f7061baf7b
commit 2dfae4d38f
8 changed files with 2758 additions and 615 deletions

View File

@@ -6,16 +6,20 @@ from unittest import mock
from unittest.mock import MagicMock, patch
import pytest
from qtpy.QtCore import QSettings, Qt
from qtpy.QtCore import QSettings, Qt, QTimer
from qtpy.QtGui import QPixmap
from qtpy.QtWidgets import QDialog, QMessageBox
from qtpy.QtWidgets import QDialog, QMessageBox, QWidget
import bec_widgets.widgets.containers.advanced_dock_area.basic_dock_area as basic_dock_module
import bec_widgets.widgets.containers.advanced_dock_area.profile_utils as profile_utils
from bec_widgets.widgets.containers.advanced_dock_area.advanced_dock_area import (
AdvancedDockArea,
DockSettingsDialog,
SaveProfileDialog,
)
from bec_widgets.widgets.containers.advanced_dock_area.basic_dock_area import (
DockAreaWidget,
DockSettingsDialog,
)
from bec_widgets.widgets.containers.advanced_dock_area.profile_utils import (
default_profile_path,
get_profile_info,
@@ -145,13 +149,372 @@ def workspace_manager_target():
return _factory
@pytest.fixture
def basic_dock_area(qtbot, mocked_client):
"""Create a namesake DockAreaWidget without the advanced toolbar."""
widget = DockAreaWidget(client=mocked_client, title="Test Dock Area")
qtbot.addWidget(widget)
qtbot.waitExposed(widget)
yield widget
class _NamespaceProfiles:
"""Helper that routes profile file helpers through a namespace."""
def __init__(self, widget: AdvancedDockArea):
self.namespace = widget.profile_namespace
def open_user(self, name: str):
return open_user_settings(name, namespace=self.namespace)
def open_default(self, name: str):
return open_default_settings(name, namespace=self.namespace)
def user_path(self, name: str) -> str:
return user_profile_path(name, namespace=self.namespace)
def default_path(self, name: str) -> str:
return default_profile_path(name, namespace=self.namespace)
def list_profiles(self) -> list[str]:
return list_profiles(namespace=self.namespace)
def set_quick_select(self, name: str, enabled: bool):
set_quick_select(name, enabled, namespace=self.namespace)
def is_quick_select(self, name: str) -> bool:
return is_quick_select(name, namespace=self.namespace)
def profile_helper(widget: AdvancedDockArea) -> _NamespaceProfiles:
"""Return a helper wired to the widget's profile namespace."""
return _NamespaceProfiles(widget)
class TestBasicDockArea:
"""Focused coverage for the lightweight DockAreaWidget base."""
def test_new_widget_instance_registers_in_maps(self, basic_dock_area):
panel = QWidget(parent=basic_dock_area)
panel.setObjectName("basic_panel")
dock = basic_dock_area.new(panel, return_dock=True)
assert dock.objectName() == "basic_panel"
assert basic_dock_area.dock_map()["basic_panel"] is dock
assert basic_dock_area.widget_map()["basic_panel"] is panel
def test_new_widget_string_creates_widget(self, basic_dock_area, qtbot):
basic_dock_area.new("DarkModeButton")
qtbot.waitUntil(lambda: len(basic_dock_area.dock_list()) > 0, timeout=1000)
assert basic_dock_area.widget_list()
def test_custom_close_handler_invoked(self, basic_dock_area, qtbot):
class CloseAwareWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setObjectName("closable")
self.closed = False
def handle_dock_close(self, dock, widget): # pragma: no cover - exercised via signal
self.closed = True
dock.closeDockWidget()
dock.deleteDockWidget()
widget = CloseAwareWidget(parent=basic_dock_area)
dock = basic_dock_area.new(widget, return_dock=True)
dock.closeRequested.emit()
qtbot.waitUntil(lambda: widget.closed, timeout=1000)
assert widget.closed is True
assert "closable" not in basic_dock_area.dock_map()
def test_attach_all_and_delete_all(self, basic_dock_area):
first = QWidget(parent=basic_dock_area)
first.setObjectName("floating_one")
second = QWidget(parent=basic_dock_area)
second.setObjectName("floating_two")
dock_one = basic_dock_area.new(first, return_dock=True, start_floating=True)
dock_two = basic_dock_area.new(second, return_dock=True, start_floating=True)
assert dock_one.isFloating() and dock_two.isFloating()
basic_dock_area.attach_all()
assert not dock_one.isFloating()
assert not dock_two.isFloating()
basic_dock_area.delete_all()
assert basic_dock_area.dock_list() == []
def test_splitter_weight_coercion_supports_aliases(self, basic_dock_area):
weights = {"default": 0.5, "left": 2, "center": 3, "right": 4}
result = basic_dock_area._coerce_weights(weights, 3, Qt.Orientation.Horizontal)
assert result == [2.0, 3.0, 4.0]
assert basic_dock_area._coerce_weights([0.0], 3, Qt.Orientation.Vertical) == [0.0, 1.0, 1.0]
assert basic_dock_area._coerce_weights([0.0, 0.0], 2, Qt.Orientation.Vertical) == [1.0, 1.0]
def test_splitter_override_keys_are_normalized(self, basic_dock_area):
overrides = {0: [1, 2], (1, 0): [3, 4], "2.1": [5], " / ": [6]}
normalized = basic_dock_area._normalize_override_keys(overrides)
assert normalized == {(0,): [1, 2], (1, 0): [3, 4], (2, 1): [5], (): [6]}
def test_schedule_splitter_weights_sets_sizes(self, basic_dock_area, monkeypatch):
monkeypatch.setattr(QTimer, "singleShot", lambda *_args: _args[-1]())
class DummySplitter:
def __init__(self):
self._children = [object(), object(), object()]
self.sizes = None
self.stretch = []
def count(self):
return len(self._children)
def orientation(self):
return Qt.Orientation.Horizontal
def width(self):
return 300
def height(self):
return 120
def setSizes(self, sizes):
self.sizes = sizes
def setStretchFactor(self, idx, value):
self.stretch.append((idx, value))
splitter = DummySplitter()
basic_dock_area._schedule_splitter_weights(splitter, [1, 2, 1])
assert splitter.sizes == [75, 150, 75]
assert splitter.stretch == [(0, 100), (1, 200), (2, 100)]
def test_apply_splitter_tree_honors_overrides(self, basic_dock_area, monkeypatch):
class DummySplitter:
def __init__(self, orientation, children=None, label="splitter"):
self._orientation = orientation
self._children = list(children or [])
self.label = label
def count(self):
return len(self._children)
def orientation(self):
return self._orientation
def widget(self, idx):
return self._children[idx]
monkeypatch.setattr(basic_dock_module.QtAds, "CDockSplitter", DummySplitter)
leaf = DummySplitter(Qt.Orientation.Horizontal, [], label="leaf")
column_one = DummySplitter(Qt.Orientation.Vertical, [leaf], label="column_one")
column_zero = DummySplitter(Qt.Orientation.Vertical, [], label="column_zero")
root = DummySplitter(Qt.Orientation.Horizontal, [column_zero, column_one], label="root")
calls = []
def fake_schedule(self, splitter, weights):
calls.append((splitter.label, weights))
monkeypatch.setattr(DockAreaWidget, "_schedule_splitter_weights", fake_schedule)
overrides = {(): ["root_override"], (0,): ["column_override"]}
basic_dock_area._apply_splitter_tree(
root, (), horizontal=[1, 2], vertical=[3, 4], overrides=overrides
)
assert calls[0] == ("root", ["root_override"])
assert calls[1] == ("column_zero", ["column_override"])
assert calls[2] == ("column_one", [3, 4])
assert calls[3] == ("leaf", ["column_override"])
def test_set_layout_ratios_normalizes_and_applies(self, basic_dock_area, monkeypatch):
class DummyContainer:
def __init__(self, splitter):
self._splitter = splitter
def rootSplitter(self):
return self._splitter
root_one = object()
root_two = object()
containers = [DummyContainer(root_one), DummyContainer(None), DummyContainer(root_two)]
monkeypatch.setattr(basic_dock_area.dock_manager, "dockContainers", lambda: containers)
calls = []
def fake_apply(self, splitter, path, horizontal, vertical, overrides):
calls.append((splitter, path, horizontal, vertical, overrides))
monkeypatch.setattr(DockAreaWidget, "_apply_splitter_tree", fake_apply)
basic_dock_area.set_layout_ratios(
horizontal=[1, 1, 1], vertical=[2, 3], splitter_overrides={"1/0": [5, 5], "": [9]}
)
assert len(calls) == 2
for splitter, path, horizontal, vertical, overrides in calls:
assert splitter in {root_one, root_two}
assert path == ()
assert horizontal == [1, 1, 1]
assert vertical == [2, 3]
assert overrides == {(): [9], (1, 0): [5, 5]}
def test_show_settings_action_defaults_disabled(self, basic_dock_area):
widget = QWidget(parent=basic_dock_area)
widget.setObjectName("settings_default")
dock = basic_dock_area.new(widget, return_dock=True)
assert dock._dock_preferences.get("show_settings_action") is False
assert not hasattr(dock, "setting_action")
def test_show_settings_action_can_be_enabled(self, basic_dock_area):
widget = QWidget(parent=basic_dock_area)
widget.setObjectName("settings_enabled")
dock = basic_dock_area.new(widget, return_dock=True, show_settings_action=True)
assert dock._dock_preferences.get("show_settings_action") is True
assert hasattr(dock, "setting_action")
assert dock.setting_action.toolTip() == "Dock settings"
def test_collect_splitter_info_describes_children(self, basic_dock_area, monkeypatch):
class DummyDockWidget:
def __init__(self, name):
self._name = name
def objectName(self):
return self._name
class DummyDockArea:
def __init__(self, dock_names):
self._docks = [DummyDockWidget(name) for name in dock_names]
def dockWidgets(self):
return self._docks
class DummySplitter:
def __init__(self, orientation, children=None):
self._orientation = orientation
self._children = list(children or [])
def orientation(self):
return self._orientation
def count(self):
return len(self._children)
def widget(self, idx):
return self._children[idx]
class Spacer:
pass
monkeypatch.setattr(basic_dock_module, "CDockSplitter", DummySplitter)
monkeypatch.setattr(basic_dock_module, "CDockAreaWidget", DummyDockArea)
monkeypatch.setattr(basic_dock_module, "CDockWidget", DummyDockWidget)
nested_splitter = DummySplitter(Qt.Orientation.Horizontal)
dock_area_child = DummyDockArea(["left", "right"])
dock_child = DummyDockWidget("solo")
spacer = Spacer()
root_splitter = DummySplitter(
Qt.Orientation.Vertical, [nested_splitter, dock_area_child, dock_child, spacer]
)
results = []
basic_dock_area._collect_splitter_info(root_splitter, (2,), results, container_index=5)
assert len(results) == 2
root_entry = results[0]
assert root_entry["container"] == 5
assert root_entry["path"] == (2,)
assert root_entry["orientation"] == "vertical"
assert root_entry["children"] == [
{"index": 0, "type": "splitter"},
{"index": 1, "type": "dock_area", "docks": ["left", "right"]},
{"index": 2, "type": "dock", "name": "solo"},
{"index": 3, "type": "Spacer"},
]
nested_entry = results[1]
assert nested_entry["path"] == (2, 0)
assert nested_entry["orientation"] == "horizontal"
def test_describe_layout_aggregates_containers(self, basic_dock_area, monkeypatch):
class DummyContainer:
def __init__(self, splitter):
self._splitter = splitter
def rootSplitter(self):
return self._splitter
containers = [DummyContainer("root0"), DummyContainer(None), DummyContainer("root2")]
monkeypatch.setattr(basic_dock_area.dock_manager, "dockContainers", lambda: containers)
calls = []
def recorder(self, splitter, path, results, container_index):
entry = {"container": container_index, "splitter": splitter, "path": path}
results.append(entry)
calls.append(entry)
monkeypatch.setattr(DockAreaWidget, "_collect_splitter_info", recorder)
info = basic_dock_area.describe_layout()
assert info == calls
assert [entry["splitter"] for entry in info] == ["root0", "root2"]
assert [entry["container"] for entry in info] == [0, 2]
assert all(entry["path"] == () for entry in info)
def test_print_layout_structure_formats_output(self, basic_dock_area, monkeypatch, capsys):
entries = [
{
"container": 1,
"path": (0,),
"orientation": "horizontal",
"children": [
{"index": 0, "type": "dock_area", "docks": ["alpha", "beta"]},
{"index": 1, "type": "dock", "name": "solo"},
{"index": 2, "type": "splitter"},
{"index": 3, "type": "Placeholder"},
],
}
]
monkeypatch.setattr(DockAreaWidget, "describe_layout", lambda self: entries)
basic_dock_area.print_layout_structure()
captured = capsys.readouterr().out.strip().splitlines()
assert captured == [
"container=1 path=(0,) orientation=horizontal -> "
"[0:dock_area[alpha, beta], 1:dock(solo), 2:splitter, 3:Placeholder]"
]
class TestAdvancedDockAreaInit:
"""Test initialization and basic properties."""
def test_init(self, advanced_dock_area):
assert advanced_dock_area is not None
assert isinstance(advanced_dock_area, AdvancedDockArea)
assert advanced_dock_area.mode == "developer"
assert advanced_dock_area.mode == "creator"
assert hasattr(advanced_dock_area, "dock_manager")
assert hasattr(advanced_dock_area, "toolbar")
assert hasattr(advanced_dock_area, "dark_mode_button")
@@ -293,6 +656,29 @@ class TestDockManagement:
assert len(advanced_dock_area.dock_list()) == 0
class TestAdvancedDockSettingsAction:
"""Ensure AdvancedDockArea exposes dock settings actions by default."""
def test_settings_action_installed_by_default(self, advanced_dock_area):
widget = QWidget(parent=advanced_dock_area)
widget.setObjectName("advanced_default_settings")
dock = advanced_dock_area.new(widget, return_dock=True)
assert hasattr(dock, "setting_action")
assert dock.setting_action.toolTip() == "Dock settings"
assert dock._dock_preferences.get("show_settings_action") is True
def test_settings_action_can_be_disabled(self, advanced_dock_area):
widget = QWidget(parent=advanced_dock_area)
widget.setObjectName("advanced_settings_off")
dock = advanced_dock_area.new(widget, return_dock=True, show_settings_action=False)
assert not hasattr(dock, "setting_action")
assert dock._dock_preferences.get("show_settings_action") is False
class TestWorkspaceLocking:
"""Test workspace locking functionality."""
@@ -873,6 +1259,11 @@ class TestWorkSpaceManager:
):
readonly = module_profile_factory("readonly_delete")
list_profiles()
monkeypatch.setattr(
profile_utils,
"get_profile_info",
lambda *a, **k: profile_utils.ProfileInfo(name=readonly, is_read_only=True),
)
info_calls = []
monkeypatch.setattr(
QMessageBox,
@@ -892,19 +1283,20 @@ class TestAdvancedDockAreaRestoreAndDialogs:
def test_restore_user_profile_from_default_confirm_true(self, advanced_dock_area, monkeypatch):
profile_name = "profile_restore_true"
open_default_settings(profile_name).sync()
open_user_settings(profile_name).sync()
helper = profile_helper(advanced_dock_area)
helper.open_default(profile_name).sync()
helper.open_user(profile_name).sync()
advanced_dock_area._current_profile_name = profile_name
advanced_dock_area.isVisible = lambda: False
pix = QPixmap(8, 8)
pix.fill(Qt.red)
monkeypatch.setattr(
"bec_widgets.widgets.containers.advanced_dock_area.advanced_dock_area.load_user_profile_screenshot",
lambda name: pix,
lambda name, namespace=None: pix,
)
monkeypatch.setattr(
"bec_widgets.widgets.containers.advanced_dock_area.advanced_dock_area.load_default_profile_screenshot",
lambda name: pix,
lambda name, namespace=None: pix,
)
monkeypatch.setattr(
"bec_widgets.widgets.containers.advanced_dock_area.advanced_dock_area.RestoreProfileDialog.confirm",
@@ -920,14 +1312,18 @@ class TestAdvancedDockAreaRestoreAndDialogs:
):
advanced_dock_area.restore_user_profile_from_default()
mock_restore.assert_called_once_with(profile_name)
assert mock_restore.call_count == 1
args, kwargs = mock_restore.call_args
assert args == (profile_name,)
assert kwargs.get("namespace") == advanced_dock_area.profile_namespace
mock_delete_all.assert_called_once()
mock_load_profile.assert_called_once_with(profile_name)
def test_restore_user_profile_from_default_confirm_false(self, advanced_dock_area, monkeypatch):
profile_name = "profile_restore_false"
open_default_settings(profile_name).sync()
open_user_settings(profile_name).sync()
helper = profile_helper(advanced_dock_area)
helper.open_default(profile_name).sync()
helper.open_user(profile_name).sync()
advanced_dock_area._current_profile_name = profile_name
advanced_dock_area.isVisible = lambda: False
monkeypatch.setattr(
@@ -960,7 +1356,8 @@ class TestAdvancedDockAreaRestoreAndDialogs:
def test_refresh_workspace_list_with_refresh_profiles(self, advanced_dock_area):
profile_name = "refresh_profile"
open_user_settings(profile_name).sync()
helper = profile_helper(advanced_dock_area)
helper.open_user(profile_name).sync()
advanced_dock_area._current_profile_name = profile_name
combo = advanced_dock_area.toolbar.components.get_action("workspace_combo").widget
combo.refresh_profiles = MagicMock()
@@ -1002,9 +1399,10 @@ class TestAdvancedDockAreaRestoreAndDialogs:
active = "active_profile"
quick = "quick_profile"
open_user_settings(active).sync()
open_user_settings(quick).sync()
set_quick_select(quick, True)
helper = profile_helper(advanced_dock_area)
helper.open_user(active).sync()
helper.open_user(quick).sync()
helper.set_quick_select(quick, True)
combo_stub = ComboStub()
@@ -1027,7 +1425,8 @@ class TestAdvancedDockAreaRestoreAndDialogs:
assert not action.isChecked()
advanced_dock_area._current_profile_name = "manager_profile"
open_user_settings("manager_profile").sync()
helper = profile_helper(advanced_dock_area)
helper.open_user("manager_profile").sync()
advanced_dock_area.show_workspace_manager()
@@ -1175,7 +1574,8 @@ class TestWorkspaceProfileOperations:
"""Test saving profile when read-only profile exists."""
profile_name = module_profile_factory("readonly_profile")
new_profile = f"{profile_name}_custom"
target_path = user_profile_path(new_profile)
helper = profile_helper(advanced_dock_area)
target_path = helper.user_path(new_profile)
if os.path.exists(target_path):
os.remove(target_path)
@@ -1203,9 +1603,10 @@ class TestWorkspaceProfileOperations:
def test_load_profile_with_manifest(self, advanced_dock_area, temp_profile_dir, qtbot):
"""Test loading profile with widget manifest."""
profile_name = "test_load_profile"
helper = profile_helper(advanced_dock_area)
# Create a profile with manifest
settings = open_user_settings(profile_name)
settings = helper.open_user(profile_name)
settings.beginWriteArray("manifest/widgets", 1)
settings.setArrayIndex(0)
settings.setValue("object_name", "test_widget")
@@ -1232,8 +1633,9 @@ class TestWorkspaceProfileOperations:
"""Saving a new profile avoids overwriting the source profile during the switch."""
source_profile = "autosave_source"
new_profile = "autosave_new"
helper = profile_helper(advanced_dock_area)
settings = open_user_settings(source_profile)
settings = helper.open_user(source_profile)
settings.beginWriteArray("manifest/widgets", 1)
settings.setArrayIndex(0)
settings.setValue("object_name", "source_widget")
@@ -1269,8 +1671,8 @@ class TestWorkspaceProfileOperations:
advanced_dock_area.save_profile()
qtbot.wait(500)
source_manifest = read_manifest(open_user_settings(source_profile))
new_manifest = read_manifest(open_user_settings(new_profile))
source_manifest = read_manifest(helper.open_user(source_profile))
new_manifest = read_manifest(helper.open_user(new_profile))
assert len(source_manifest) == 1
assert len(new_manifest) == 2
@@ -1279,9 +1681,10 @@ class TestWorkspaceProfileOperations:
"""Regular profile switches should persist the outgoing layout."""
profile_a = "autosave_keep"
profile_b = "autosave_target"
helper = profile_helper(advanced_dock_area)
for profile in (profile_a, profile_b):
settings = open_user_settings(profile)
settings = helper.open_user(profile)
settings.beginWriteArray("manifest/widgets", 1)
settings.setArrayIndex(0)
settings.setValue("object_name", f"{profile}_widget")
@@ -1300,7 +1703,7 @@ class TestWorkspaceProfileOperations:
advanced_dock_area.load_profile(profile_b)
qtbot.wait(500)
manifest_a = read_manifest(open_user_settings(profile_a))
manifest_a = read_manifest(helper.open_user(profile_a))
assert len(manifest_a) == 2
def test_delete_profile_readonly(
@@ -1308,12 +1711,14 @@ class TestWorkspaceProfileOperations:
):
"""Test deleting bundled profile removes only the writable copy."""
profile_name = module_profile_factory("readonly_profile")
list_profiles() # ensure default and user copies are materialized
settings = open_user_settings(profile_name)
helper = profile_helper(advanced_dock_area)
helper.list_profiles() # ensure default and user copies are materialized
helper.open_default(profile_name).sync()
settings = helper.open_user(profile_name)
settings.setValue("test", "value")
settings.sync()
user_path = user_profile_path(profile_name)
default_path = default_profile_path(profile_name)
user_path = helper.user_path(profile_name)
default_path = helper.default_path(profile_name)
assert os.path.exists(user_path)
assert os.path.exists(default_path)
@@ -1322,27 +1727,34 @@ class TestWorkspaceProfileOperations:
mock_combo.currentText.return_value = profile_name
mock_get_action.return_value.widget = mock_combo
with patch(
"bec_widgets.widgets.containers.advanced_dock_area.advanced_dock_area.QMessageBox.question"
) as mock_question:
mock_question.return_value = QMessageBox.Yes
with (
patch(
"bec_widgets.widgets.containers.advanced_dock_area.advanced_dock_area.QMessageBox.question",
return_value=QMessageBox.Yes,
) as mock_question,
patch(
"bec_widgets.widgets.containers.advanced_dock_area.advanced_dock_area.QMessageBox.information",
return_value=None,
) as mock_info,
):
advanced_dock_area.delete_profile()
mock_question.assert_called_once()
# User copy should be removed, default remains
assert not os.path.exists(user_path)
mock_question.assert_not_called()
mock_info.assert_called_once()
# Read-only profile should remain intact (user + default copies)
assert os.path.exists(user_path)
assert os.path.exists(default_path)
def test_delete_profile_success(self, advanced_dock_area, temp_profile_dir):
"""Test successful profile deletion."""
profile_name = "deletable_profile"
helper = profile_helper(advanced_dock_area)
# Create regular profile
settings = open_user_settings(profile_name)
settings = helper.open_user(profile_name)
settings.setValue("test", "value")
settings.sync()
user_path = user_profile_path(profile_name)
user_path = helper.user_path(profile_name)
assert os.path.exists(user_path)
with patch.object(advanced_dock_area.toolbar.components, "get_action") as mock_get_action:
@@ -1366,8 +1778,9 @@ class TestWorkspaceProfileOperations:
def test_refresh_workspace_list(self, advanced_dock_area, temp_profile_dir):
"""Test refreshing workspace list."""
# Create some profiles
helper = profile_helper(advanced_dock_area)
for name in ["profile1", "profile2"]:
settings = open_user_settings(name)
settings = helper.open_user(name)
settings.setValue("test", "value")
settings.sync()
@@ -1451,15 +1864,18 @@ class TestCleanupAndMisc:
widget = DarkModeButton(parent=advanced_dock_area)
widget.setObjectName("test_widget")
dock = advanced_dock_area._make_dock(widget, closable=True, floatable=True, movable=True)
with patch.object(advanced_dock_area, "_open_dock_settings_dialog") as mock_open_dialog:
dock = advanced_dock_area._make_dock(
widget, closable=True, floatable=True, movable=True
)
# Verify dock has settings action
assert hasattr(dock, "setting_action")
assert dock.setting_action is not None
# Verify dock has settings action
assert hasattr(dock, "setting_action")
assert dock.setting_action is not None
assert dock.setting_action.toolTip() == "Dock settings"
# Verify title bar actions were set
title_bar_actions = dock.titleBarActions()
assert len(title_bar_actions) >= 1
dock.setting_action.trigger()
mock_open_dialog.assert_called_once_with(dock, widget)
class TestModeSwitching:
@@ -1467,7 +1883,7 @@ class TestModeSwitching:
def test_mode_property_setter_valid_modes(self, advanced_dock_area):
"""Test setting valid modes."""
valid_modes = ["plot", "device", "utils", "developer", "user"]
valid_modes = ["plot", "device", "utils", "creator", "user"]
for mode in valid_modes:
advanced_dock_area.mode = mode
@@ -1534,7 +1950,7 @@ class TestToolbarModeBundles:
def test_developer_mode_toolbar_visibility(self, advanced_dock_area):
"""Test toolbar bundle visibility in developer mode."""
advanced_dock_area.mode = "developer"
advanced_dock_area.mode = "creator"
shown_bundles = advanced_dock_area.toolbar.shown_bundles
@@ -1622,7 +2038,7 @@ class TestFlatToolbarActions:
with patch.object(advanced_dock_area, "new") as mock_new:
action = advanced_dock_area.toolbar.components.get_action(action_name).action
action.trigger()
mock_new.assert_called_once_with(widget=widget_type)
mock_new.assert_called_once_with(widget_type)
def test_flat_device_actions_trigger_widget_creation(self, advanced_dock_area):
"""Test flat device actions trigger widget creation."""
@@ -1635,7 +2051,7 @@ class TestFlatToolbarActions:
with patch.object(advanced_dock_area, "new") as mock_new:
action = advanced_dock_area.toolbar.components.get_action(action_name).action
action.trigger()
mock_new.assert_called_once_with(widget=widget_type)
mock_new.assert_called_once_with(widget_type)
def test_flat_utils_actions_trigger_widget_creation(self, advanced_dock_area):
"""Test flat utils actions trigger widget creation."""
@@ -1658,7 +2074,7 @@ class TestFlatToolbarActions:
continue
action.trigger()
mock_new.assert_called_once_with(widget=widget_type)
mock_new.assert_called_once_with(widget_type)
def test_flat_log_panel_action_disabled(self, advanced_dock_area):
"""Test that flat log panel action is disabled."""
@@ -1671,7 +2087,7 @@ class TestModeTransitions:
def test_mode_transition_sequence(self, advanced_dock_area, qtbot):
"""Test sequence of mode transitions."""
modes = ["plot", "device", "utils", "developer", "user"]
modes = ["plot", "device", "utils", "creator", "user"]
for mode in modes:
with qtbot.waitSignal(advanced_dock_area.mode_changed, timeout=1000) as blocker:
@@ -1686,7 +2102,7 @@ class TestModeTransitions:
advanced_dock_area.mode = "plot"
advanced_dock_area.mode = "device"
advanced_dock_area.mode = "utils"
advanced_dock_area.mode = "developer"
advanced_dock_area.mode = "creator"
advanced_dock_area.mode = "user"
# Final state should be consistent
@@ -1758,7 +2174,7 @@ class TestModeProperty:
def test_multiple_mode_changes(self, advanced_dock_area, qtbot):
"""Test multiple rapid mode changes."""
modes = ["plot", "device", "utils", "developer", "user"]
modes = ["plot", "device", "utils", "creator", "user"]
for i, mode in enumerate(modes):
with qtbot.waitSignal(advanced_dock_area.mode_changed, timeout=1000) as blocker: