1
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-12-30 02:31:20 +01:00
Files
bec_widgets/tests/unit_tests/test_advanced_dock_area.py

1045 lines
39 KiB
Python

# pylint: disable=missing-function-docstring, missing-module-docstring, unused-import
import os
import tempfile
from unittest import mock
from unittest.mock import MagicMock, patch
import pytest
from qtpy.QtCore import QSettings
from qtpy.QtWidgets import QDialog, QMessageBox
from bec_widgets.widgets.containers.advanced_dock_area.advanced_dock_area import (
AdvancedDockArea,
DockSettingsDialog,
SaveProfileDialog,
)
from bec_widgets.widgets.containers.advanced_dock_area.profile_utils import (
is_profile_readonly,
list_profiles,
open_settings,
profile_path,
read_manifest,
set_profile_readonly,
write_manifest,
)
from .client_mocks import mocked_client
@pytest.fixture
def advanced_dock_area(qtbot, mocked_client):
"""Create an AdvancedDockArea instance for testing."""
widget = AdvancedDockArea(client=mocked_client)
qtbot.addWidget(widget)
qtbot.waitExposed(widget)
yield widget
@pytest.fixture
def temp_profile_dir():
"""Create a temporary directory for profile testing."""
with tempfile.TemporaryDirectory() as temp_dir:
with patch.dict(os.environ, {"BECWIDGETS_PROFILE_DIR": temp_dir}):
yield temp_dir
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 hasattr(advanced_dock_area, "dock_manager")
assert hasattr(advanced_dock_area, "toolbar")
assert hasattr(advanced_dock_area, "dark_mode_button")
assert hasattr(advanced_dock_area, "state_manager")
def test_rpc_and_plugin_flags(self):
assert AdvancedDockArea.RPC is True
assert AdvancedDockArea.PLUGIN is False
def test_user_access_list(self):
expected_methods = [
"new",
"widget_map",
"widget_list",
"lock_workspace",
"attach_all",
"delete_all",
]
for method in expected_methods:
assert method in AdvancedDockArea.USER_ACCESS
class TestDockManagement:
"""Test dock creation, management, and manipulation."""
def test_new_widget_string(self, advanced_dock_area, qtbot):
"""Test creating a new widget from string."""
initial_count = len(advanced_dock_area.dock_list())
# Create a widget by string name
widget = advanced_dock_area.new("Waveform")
# Wait for the dock to be created (since it's async)
qtbot.wait(200)
# Check that dock was actually created
assert len(advanced_dock_area.dock_list()) == initial_count + 1
# Check widget was returned
assert widget is not None
assert hasattr(widget, "name_established")
def test_new_widget_instance(self, advanced_dock_area, qtbot):
"""Test creating dock with existing widget instance."""
from bec_widgets.widgets.plots.waveform.waveform import Waveform
initial_count = len(advanced_dock_area.dock_list())
# Create widget instance
widget_instance = Waveform(parent=advanced_dock_area, client=advanced_dock_area.client)
widget_instance.setObjectName("test_widget")
# Add it to dock area
result = advanced_dock_area.new(widget_instance)
# Should return the same instance
assert result == widget_instance
qtbot.wait(200)
assert len(advanced_dock_area.dock_list()) == initial_count + 1
def test_dock_map(self, advanced_dock_area, qtbot):
"""Test dock_map returns correct mapping."""
# Initially empty
dock_map = advanced_dock_area.dock_map()
assert isinstance(dock_map, dict)
initial_count = len(dock_map)
# Create a widget
advanced_dock_area.new("Waveform")
qtbot.wait(200)
# Check dock map updated
new_dock_map = advanced_dock_area.dock_map()
assert len(new_dock_map) == initial_count + 1
def test_dock_list(self, advanced_dock_area, qtbot):
"""Test dock_list returns list of docks."""
dock_list = advanced_dock_area.dock_list()
assert isinstance(dock_list, list)
initial_count = len(dock_list)
# Create a widget
advanced_dock_area.new("Waveform")
qtbot.wait(200)
# Check dock list updated
new_dock_list = advanced_dock_area.dock_list()
assert len(new_dock_list) == initial_count + 1
def test_widget_map(self, advanced_dock_area, qtbot):
"""Test widget_map returns widget mapping."""
widget_map = advanced_dock_area.widget_map()
assert isinstance(widget_map, dict)
initial_count = len(widget_map)
# Create a widget
advanced_dock_area.new("DarkModeButton")
qtbot.wait(200)
# Check widget map updated
new_widget_map = advanced_dock_area.widget_map()
assert len(new_widget_map) == initial_count + 1
def test_widget_list(self, advanced_dock_area, qtbot):
"""Test widget_list returns list of widgets."""
widget_list = advanced_dock_area.widget_list()
assert isinstance(widget_list, list)
initial_count = len(widget_list)
# Create a widget
advanced_dock_area.new("DarkModeButton")
qtbot.wait(200)
# Check widget list updated
new_widget_list = advanced_dock_area.widget_list()
assert len(new_widget_list) == initial_count + 1
def test_delete_all(self, advanced_dock_area, qtbot):
"""Test delete_all functionality."""
# Create multiple widgets
advanced_dock_area.new("DarkModeButton")
advanced_dock_area.new("DarkModeButton")
# Wait for docks to be created
qtbot.wait(200)
initial_count = len(advanced_dock_area.dock_list())
assert initial_count >= 2
# Delete all
advanced_dock_area.delete_all()
# Wait for deletion to complete
qtbot.wait(200)
# Should have no docks
assert len(advanced_dock_area.dock_list()) == 0
class TestWorkspaceLocking:
"""Test workspace locking functionality."""
def test_lock_workspace_property_getter(self, advanced_dock_area):
"""Test lock_workspace property getter."""
# Initially unlocked
assert advanced_dock_area.lock_workspace is False
# Set locked state directly
advanced_dock_area._locked = True
assert advanced_dock_area.lock_workspace is True
def test_lock_workspace_property_setter(self, advanced_dock_area, qtbot):
"""Test lock_workspace property setter."""
# Create a dock first
advanced_dock_area.new("DarkModeButton")
qtbot.wait(200)
# Initially unlocked
assert advanced_dock_area.lock_workspace is False
# Lock workspace
advanced_dock_area.lock_workspace = True
assert advanced_dock_area._locked is True
assert advanced_dock_area.lock_workspace is True
# Unlock workspace
advanced_dock_area.lock_workspace = False
assert advanced_dock_area._locked is False
assert advanced_dock_area.lock_workspace is False
class TestDeveloperMode:
"""Test developer mode functionality."""
def test_developer_mode_toggle(self, advanced_dock_area):
"""Test developer mode toggle functionality."""
# Check initial state
initial_editable = advanced_dock_area._editable
# Toggle developer mode
advanced_dock_area._on_developer_mode_toggled(True)
assert advanced_dock_area._editable is True
assert advanced_dock_area.lock_workspace is False
advanced_dock_area._on_developer_mode_toggled(False)
assert advanced_dock_area._editable is False
assert advanced_dock_area.lock_workspace is True
def test_set_editable(self, advanced_dock_area):
"""Test _set_editable functionality."""
# Test setting editable to True
advanced_dock_area._set_editable(True)
assert advanced_dock_area.lock_workspace is False
assert advanced_dock_area._editable is True
# Test setting editable to False
advanced_dock_area._set_editable(False)
assert advanced_dock_area.lock_workspace is True
assert advanced_dock_area._editable is False
class TestToolbarFunctionality:
"""Test toolbar setup and functionality."""
def test_toolbar_setup(self, advanced_dock_area):
"""Test toolbar is properly set up."""
assert hasattr(advanced_dock_area, "toolbar")
assert hasattr(advanced_dock_area, "_ACTION_MAPPINGS")
# Check that action mappings are properly set
assert "menu_plots" in advanced_dock_area._ACTION_MAPPINGS
assert "menu_devices" in advanced_dock_area._ACTION_MAPPINGS
assert "menu_utils" in advanced_dock_area._ACTION_MAPPINGS
def test_toolbar_plot_actions(self, advanced_dock_area):
"""Test plot toolbar actions trigger widget creation."""
plot_actions = [
"waveform",
"scatter_waveform",
"multi_waveform",
"image",
"motor_map",
"heatmap",
]
for action_name in plot_actions:
with patch.object(advanced_dock_area, "new") as mock_new:
menu_plots = advanced_dock_area.toolbar.components.get_action("menu_plots")
action = menu_plots.actions[action_name].action
# Get the expected widget type from the action mappings
widget_type = advanced_dock_area._ACTION_MAPPINGS["menu_plots"][action_name][2]
action.trigger()
mock_new.assert_called_once_with(widget=widget_type)
def test_toolbar_device_actions(self, advanced_dock_area):
"""Test device toolbar actions trigger widget creation."""
device_actions = ["scan_control", "positioner_box"]
for action_name in device_actions:
with patch.object(advanced_dock_area, "new") as mock_new:
menu_devices = advanced_dock_area.toolbar.components.get_action("menu_devices")
action = menu_devices.actions[action_name].action
# Get the expected widget type from the action mappings
widget_type = advanced_dock_area._ACTION_MAPPINGS["menu_devices"][action_name][2]
action.trigger()
mock_new.assert_called_once_with(widget=widget_type)
def test_toolbar_utils_actions(self, advanced_dock_area):
"""Test utils toolbar actions trigger widget creation."""
utils_actions = ["queue", "vs_code", "status", "progress_bar", "sbb_monitor"]
for action_name in utils_actions:
with patch.object(advanced_dock_area, "new") as mock_new:
menu_utils = advanced_dock_area.toolbar.components.get_action("menu_utils")
action = menu_utils.actions[action_name].action
# Skip log_panel as it's disabled
if action_name == "log_panel":
assert not action.isEnabled()
continue
# Get the expected widget type from the action mappings
widget_type = advanced_dock_area._ACTION_MAPPINGS["menu_utils"][action_name][2]
action.trigger()
mock_new.assert_called_once_with(widget=widget_type)
def test_attach_all_action(self, advanced_dock_area, qtbot):
"""Test attach_all toolbar action."""
# Create floating docks
advanced_dock_area.new("DarkModeButton", start_floating=True)
advanced_dock_area.new("DarkModeButton", start_floating=True)
qtbot.wait(200)
initial_floating = len(advanced_dock_area.dock_manager.floatingWidgets())
# Trigger attach all action
action = advanced_dock_area.toolbar.components.get_action("attach_all").action
action.trigger()
# Wait a bit for the operation
qtbot.wait(200)
# Should have fewer or same floating widgets
final_floating = len(advanced_dock_area.dock_manager.floatingWidgets())
assert final_floating <= initial_floating
def test_screenshot_action(self, advanced_dock_area, tmpdir):
"""Test screenshot toolbar action."""
# Create a test screenshot file path in tmpdir
screenshot_path = tmpdir.join("test_screenshot.png")
# Mock the QFileDialog.getSaveFileName to return a test filename
with mock.patch("bec_widgets.utils.bec_widget.QFileDialog.getSaveFileName") as mock_dialog:
mock_dialog.return_value = (str(screenshot_path), "PNG Files (*.png)")
# Mock the screenshot.save method
with mock.patch.object(advanced_dock_area, "grab") as mock_grab:
mock_screenshot = mock.MagicMock()
mock_grab.return_value = mock_screenshot
# Trigger the screenshot action
action = advanced_dock_area.toolbar.components.get_action("screenshot").action
action.trigger()
# Verify the dialog was called
mock_dialog.assert_called_once()
# Verify grab was called
mock_grab.assert_called_once()
# Verify save was called with the filename
mock_screenshot.save.assert_called_once_with(str(screenshot_path))
class TestDockSettingsDialog:
"""Test dock settings dialog functionality."""
def test_dock_settings_dialog_init(self, advanced_dock_area):
"""Test DockSettingsDialog initialization."""
from bec_widgets.widgets.utility.visual.dark_mode_button.dark_mode_button import (
DarkModeButton,
)
# Create a real widget
mock_widget = DarkModeButton(parent=advanced_dock_area)
dialog = DockSettingsDialog(advanced_dock_area, mock_widget)
assert dialog.windowTitle() == "Dock Settings"
assert dialog.isModal()
assert hasattr(dialog, "prop_editor")
def test_open_dock_settings_dialog(self, advanced_dock_area, qtbot):
"""Test opening dock settings dialog."""
from bec_widgets.widgets.utility.visual.dark_mode_button.dark_mode_button import (
DarkModeButton,
)
# Create real widget and dock
widget = DarkModeButton(parent=advanced_dock_area)
widget.setObjectName("test_widget")
# Create a real dock
dock = advanced_dock_area._make_dock(widget, closable=True, floatable=True, movable=True)
# Mock dialog exec to avoid blocking
with patch.object(DockSettingsDialog, "exec") as mock_exec:
mock_exec.return_value = QDialog.Accepted
# Call the method
advanced_dock_area._open_dock_settings_dialog(dock, widget)
# Verify dialog was created and exec called
mock_exec.assert_called_once()
class TestSaveProfileDialog:
"""Test save profile dialog functionality."""
def test_save_profile_dialog_init(self, qtbot):
"""Test SaveProfileDialog initialization."""
dialog = SaveProfileDialog(None, "test_profile")
qtbot.addWidget(dialog)
assert dialog.windowTitle() == "Save Workspace Profile"
assert dialog.isModal()
assert dialog.name_edit.text() == "test_profile"
assert hasattr(dialog, "readonly_checkbox")
def test_save_profile_dialog_get_values(self, qtbot):
"""Test getting values from SaveProfileDialog."""
dialog = SaveProfileDialog(None)
qtbot.addWidget(dialog)
dialog.name_edit.setText("my_profile")
dialog.readonly_checkbox.setChecked(True)
assert dialog.get_profile_name() == "my_profile"
assert dialog.is_readonly() is True
def test_save_button_enabled_state(self, qtbot):
"""Test save button is enabled/disabled based on name input."""
dialog = SaveProfileDialog(None)
qtbot.addWidget(dialog)
# Initially should be disabled (empty name)
assert not dialog.save_btn.isEnabled()
# Should be enabled when name is entered
dialog.name_edit.setText("test")
assert dialog.save_btn.isEnabled()
# Should be disabled when name is cleared
dialog.name_edit.setText("")
assert not dialog.save_btn.isEnabled()
class TestProfileManagement:
"""Test profile management functionality."""
def test_profile_path(self, temp_profile_dir):
"""Test profile path generation."""
path = profile_path("test_profile")
expected = os.path.join(temp_profile_dir, "test_profile.ini")
assert path == expected
def test_open_settings(self, temp_profile_dir):
"""Test opening settings for a profile."""
settings = open_settings("test_profile")
assert isinstance(settings, QSettings)
def test_list_profiles_empty(self, temp_profile_dir):
"""Test listing profiles when directory is empty."""
profiles = list_profiles()
assert profiles == []
def test_list_profiles_with_files(self, temp_profile_dir):
"""Test listing profiles with existing files."""
# Create some test profile files
profile_names = ["profile1", "profile2", "profile3"]
for name in profile_names:
settings = open_settings(name)
settings.setValue("test", "value")
settings.sync()
profiles = list_profiles()
assert sorted(profiles) == sorted(profile_names)
def test_readonly_profile_operations(self, temp_profile_dir):
"""Test read-only profile functionality."""
profile_name = "readonly_profile"
# Initially should not be read-only
assert not is_profile_readonly(profile_name)
# Set as read-only
set_profile_readonly(profile_name, True)
assert is_profile_readonly(profile_name)
# Unset read-only
set_profile_readonly(profile_name, False)
assert not is_profile_readonly(profile_name)
def test_write_and_read_manifest(self, temp_profile_dir, advanced_dock_area, qtbot):
"""Test writing and reading dock manifest."""
settings = open_settings("test_manifest")
# Create real docks
advanced_dock_area.new("DarkModeButton")
advanced_dock_area.new("DarkModeButton")
advanced_dock_area.new("DarkModeButton")
# Wait for docks to be created
qtbot.wait(1000)
docks = advanced_dock_area.dock_list()
# Write manifest
write_manifest(settings, docks)
settings.sync()
# Read manifest
items = read_manifest(settings)
assert len(items) >= 3
for item in items:
assert "object_name" in item
assert "widget_class" in item
assert "closable" in item
assert "floatable" in item
assert "movable" in item
class TestWorkspaceProfileOperations:
"""Test workspace profile save/load/delete operations."""
def test_save_profile_readonly_conflict(self, advanced_dock_area, temp_profile_dir):
"""Test saving profile when read-only profile exists."""
profile_name = "readonly_profile"
# Create a read-only profile
set_profile_readonly(profile_name, True)
settings = open_settings(profile_name)
settings.setValue("test", "value")
settings.sync()
with patch(
"bec_widgets.widgets.containers.advanced_dock_area.advanced_dock_area.SaveProfileDialog"
) as mock_dialog_class:
mock_dialog = MagicMock()
mock_dialog.exec.return_value = QDialog.Accepted
mock_dialog.get_profile_name.return_value = profile_name
mock_dialog.is_readonly.return_value = False
mock_dialog_class.return_value = mock_dialog
with patch(
"bec_widgets.widgets.containers.advanced_dock_area.advanced_dock_area.QMessageBox.warning"
) as mock_warning:
mock_warning.return_value = QMessageBox.No
advanced_dock_area.save_profile()
mock_warning.assert_called_once()
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"
# Create a profile with manifest
settings = open_settings(profile_name)
settings.beginWriteArray("manifest/widgets", 1)
settings.setArrayIndex(0)
settings.setValue("object_name", "test_widget")
settings.setValue("widget_class", "DarkModeButton")
settings.setValue("closable", True)
settings.setValue("floatable", True)
settings.setValue("movable", True)
settings.endArray()
settings.sync()
initial_count = len(advanced_dock_area.widget_map())
# Load profile
advanced_dock_area.load_profile(profile_name)
# Wait for widget to be created
qtbot.wait(1000)
# Check widget was created
widget_map = advanced_dock_area.widget_map()
assert "test_widget" in widget_map
def test_delete_profile_readonly(self, advanced_dock_area, temp_profile_dir):
"""Test deleting read-only profile shows warning."""
profile_name = "readonly_profile"
# Create read-only profile
set_profile_readonly(profile_name, True)
settings = open_settings(profile_name)
settings.setValue("test", "value")
settings.sync()
with patch.object(advanced_dock_area.toolbar.components, "get_action") as mock_get_action:
mock_combo = MagicMock()
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.warning"
) as mock_warning:
advanced_dock_area.delete_profile()
mock_warning.assert_called_once()
# Profile should still exist
assert os.path.exists(profile_path(profile_name))
def test_delete_profile_success(self, advanced_dock_area, temp_profile_dir):
"""Test successful profile deletion."""
profile_name = "deletable_profile"
# Create regular profile
settings = open_settings(profile_name)
settings.setValue("test", "value")
settings.sync()
with patch.object(advanced_dock_area.toolbar.components, "get_action") as mock_get_action:
mock_combo = MagicMock()
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.object(advanced_dock_area, "_refresh_workspace_list") as mock_refresh:
advanced_dock_area.delete_profile()
mock_question.assert_called_once()
mock_refresh.assert_called_once()
# Profile should be deleted
assert not os.path.exists(profile_path(profile_name))
def test_refresh_workspace_list(self, advanced_dock_area, temp_profile_dir):
"""Test refreshing workspace list."""
# Create some profiles
for name in ["profile1", "profile2"]:
settings = open_settings(name)
settings.setValue("test", "value")
settings.sync()
with patch.object(advanced_dock_area.toolbar.components, "get_action") as mock_get_action:
mock_combo = MagicMock()
mock_combo.refresh_profiles = MagicMock()
mock_get_action.return_value.widget = mock_combo
advanced_dock_area._refresh_workspace_list()
mock_combo.refresh_profiles.assert_called_once()
class TestCleanupAndMisc:
"""Test cleanup and miscellaneous functionality."""
def test_delete_dock(self, advanced_dock_area, qtbot):
"""Test _delete_dock functionality."""
from bec_widgets.widgets.utility.visual.dark_mode_button.dark_mode_button import (
DarkModeButton,
)
# Create a real widget and dock
widget = DarkModeButton(parent=advanced_dock_area)
widget.setObjectName("test_widget")
dock = advanced_dock_area._make_dock(widget, closable=True, floatable=True, movable=True)
initial_count = len(advanced_dock_area.dock_list())
# Delete the dock
advanced_dock_area._delete_dock(dock)
# Wait for deletion to complete
qtbot.wait(200)
# Verify dock was removed
assert len(advanced_dock_area.dock_list()) == initial_count - 1
def test_apply_dock_lock(self, advanced_dock_area, qtbot):
"""Test _apply_dock_lock functionality."""
# Create a dock first
advanced_dock_area.new("DarkModeButton")
qtbot.wait(200)
# Test locking
advanced_dock_area._apply_dock_lock(True)
# No assertion needed - just verify it doesn't crash
# Test unlocking
advanced_dock_area._apply_dock_lock(False)
# No assertion needed - just verify it doesn't crash
def test_make_dock(self, advanced_dock_area):
"""Test _make_dock functionality."""
from bec_widgets.widgets.utility.visual.dark_mode_button.dark_mode_button import (
DarkModeButton,
)
# Create a real widget
widget = DarkModeButton(parent=advanced_dock_area)
widget.setObjectName("test_widget")
initial_count = len(advanced_dock_area.dock_list())
# Create dock
dock = advanced_dock_area._make_dock(widget, closable=True, floatable=True, movable=True)
# Verify dock was created
assert dock is not None
assert len(advanced_dock_area.dock_list()) == initial_count + 1
assert dock.widget() == widget
def test_install_dock_settings_action(self, advanced_dock_area):
"""Test _install_dock_settings_action functionality."""
from bec_widgets.widgets.utility.visual.dark_mode_button.dark_mode_button import (
DarkModeButton,
)
# Create real widget and dock
widget = DarkModeButton(parent=advanced_dock_area)
widget.setObjectName("test_widget")
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 title bar actions were set
title_bar_actions = dock.titleBarActions()
assert len(title_bar_actions) >= 1
class TestModeSwitching:
"""Test mode switching functionality."""
def test_mode_property_setter_valid_modes(self, advanced_dock_area):
"""Test setting valid modes."""
valid_modes = ["plot", "device", "utils", "developer", "user"]
for mode in valid_modes:
advanced_dock_area.mode = mode
assert advanced_dock_area.mode == mode
def test_mode_changed_signal_emission(self, advanced_dock_area, qtbot):
"""Test that mode_changed signal is emitted when mode changes."""
# Set up signal spy
with qtbot.waitSignal(advanced_dock_area.mode_changed, timeout=1000) as blocker:
advanced_dock_area.mode = "plot"
# Check signal was emitted with correct argument
assert blocker.args == ["plot"]
class TestToolbarModeBundles:
"""Test toolbar bundle creation and visibility for different modes."""
def test_flat_bundles_created(self, advanced_dock_area):
"""Test that flat bundles are created during toolbar setup."""
# Check that flat bundles exist
assert "flat_plots" in advanced_dock_area.toolbar.bundles
assert "flat_devices" in advanced_dock_area.toolbar.bundles
assert "flat_utils" in advanced_dock_area.toolbar.bundles
def test_plot_mode_toolbar_visibility(self, advanced_dock_area):
"""Test toolbar bundle visibility in plot mode."""
advanced_dock_area.mode = "plot"
# Should show only flat_plots bundle (and essential bundles in real implementation)
shown_bundles = advanced_dock_area.toolbar.shown_bundles
assert "flat_plots" in shown_bundles
# Should not show other flat bundles
assert "flat_devices" not in shown_bundles
assert "flat_utils" not in shown_bundles
# Should not show menu bundles
assert "menu_plots" not in shown_bundles
assert "menu_devices" not in shown_bundles
assert "menu_utils" not in shown_bundles
def test_device_mode_toolbar_visibility(self, advanced_dock_area):
"""Test toolbar bundle visibility in device mode."""
advanced_dock_area.mode = "device"
shown_bundles = advanced_dock_area.toolbar.shown_bundles
assert "flat_devices" in shown_bundles
# Should not show other flat bundles
assert "flat_plots" not in shown_bundles
assert "flat_utils" not in shown_bundles
def test_utils_mode_toolbar_visibility(self, advanced_dock_area):
"""Test toolbar bundle visibility in utils mode."""
advanced_dock_area.mode = "utils"
shown_bundles = advanced_dock_area.toolbar.shown_bundles
assert "flat_utils" in shown_bundles
# Should not show other flat bundles
assert "flat_plots" not in shown_bundles
assert "flat_devices" not in shown_bundles
def test_developer_mode_toolbar_visibility(self, advanced_dock_area):
"""Test toolbar bundle visibility in developer mode."""
advanced_dock_area.mode = "developer"
shown_bundles = advanced_dock_area.toolbar.shown_bundles
# Should show menu bundles
assert "menu_plots" in shown_bundles
assert "menu_devices" in shown_bundles
assert "menu_utils" in shown_bundles
# Should show essential bundles
assert "spacer_bundle" in shown_bundles
assert "workspace" in shown_bundles
assert "dock_actions" in shown_bundles
def test_user_mode_toolbar_visibility(self, advanced_dock_area):
"""Test toolbar bundle visibility in user mode."""
advanced_dock_area.mode = "user"
shown_bundles = advanced_dock_area.toolbar.shown_bundles
# Should show only essential bundles
assert "spacer_bundle" in shown_bundles
assert "workspace" in shown_bundles
assert "dock_actions" in shown_bundles
# Should not show any widget creation bundles
assert "menu_plots" not in shown_bundles
assert "menu_devices" not in shown_bundles
assert "menu_utils" not in shown_bundles
assert "flat_plots" not in shown_bundles
assert "flat_devices" not in shown_bundles
assert "flat_utils" not in shown_bundles
class TestFlatToolbarActions:
"""Test flat toolbar actions functionality."""
def test_flat_plot_actions_created(self, advanced_dock_area):
"""Test that flat plot actions are created."""
plot_actions = [
"flat_waveform",
"flat_scatter_waveform",
"flat_multi_waveform",
"flat_image",
"flat_motor_map",
"flat_heatmap",
]
for action_name in plot_actions:
assert advanced_dock_area.toolbar.components.exists(action_name)
def test_flat_device_actions_created(self, advanced_dock_area):
"""Test that flat device actions are created."""
device_actions = ["flat_scan_control", "flat_positioner_box"]
for action_name in device_actions:
assert advanced_dock_area.toolbar.components.exists(action_name)
def test_flat_utils_actions_created(self, advanced_dock_area):
"""Test that flat utils actions are created."""
utils_actions = [
"flat_queue",
"flat_vs_code",
"flat_status",
"flat_progress_bar",
"flat_log_panel",
"flat_sbb_monitor",
]
for action_name in utils_actions:
assert advanced_dock_area.toolbar.components.exists(action_name)
def test_flat_plot_actions_trigger_widget_creation(self, advanced_dock_area):
"""Test flat plot actions trigger widget creation."""
plot_action_mapping = {
"flat_waveform": "Waveform",
"flat_scatter_waveform": "ScatterWaveform",
"flat_multi_waveform": "MultiWaveform",
"flat_image": "Image",
"flat_motor_map": "MotorMap",
"flat_heatmap": "Heatmap",
}
for action_name, widget_type in plot_action_mapping.items():
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)
def test_flat_device_actions_trigger_widget_creation(self, advanced_dock_area):
"""Test flat device actions trigger widget creation."""
device_action_mapping = {
"flat_scan_control": "ScanControl",
"flat_positioner_box": "PositionerBox",
}
for action_name, widget_type in device_action_mapping.items():
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)
def test_flat_utils_actions_trigger_widget_creation(self, advanced_dock_area):
"""Test flat utils actions trigger widget creation."""
utils_action_mapping = {
"flat_queue": "BECQueue",
"flat_vs_code": "VSCodeEditor",
"flat_status": "BECStatusBox",
"flat_progress_bar": "RingProgressBar",
"flat_sbb_monitor": "SBBMonitor",
}
for action_name, widget_type in utils_action_mapping.items():
with patch.object(advanced_dock_area, "new") as mock_new:
action = advanced_dock_area.toolbar.components.get_action(action_name).action
# Skip log_panel as it's disabled
if action_name == "flat_log_panel":
assert not action.isEnabled()
continue
action.trigger()
mock_new.assert_called_once_with(widget=widget_type)
def test_flat_log_panel_action_disabled(self, advanced_dock_area):
"""Test that flat log panel action is disabled."""
action = advanced_dock_area.toolbar.components.get_action("flat_log_panel").action
assert not action.isEnabled()
class TestModeTransitions:
"""Test mode transitions and state consistency."""
def test_mode_transition_sequence(self, advanced_dock_area, qtbot):
"""Test sequence of mode transitions."""
modes = ["plot", "device", "utils", "developer", "user"]
for mode in modes:
with qtbot.waitSignal(advanced_dock_area.mode_changed, timeout=1000) as blocker:
advanced_dock_area.mode = mode
assert advanced_dock_area.mode == mode
assert blocker.args == [mode]
def test_mode_consistency_after_multiple_changes(self, advanced_dock_area):
"""Test mode consistency after multiple rapid changes."""
# Rapidly change modes
advanced_dock_area.mode = "plot"
advanced_dock_area.mode = "device"
advanced_dock_area.mode = "utils"
advanced_dock_area.mode = "developer"
advanced_dock_area.mode = "user"
# Final state should be consistent
assert advanced_dock_area.mode == "user"
# Toolbar should show correct bundles for user mode
shown_bundles = advanced_dock_area.toolbar.shown_bundles
assert "spacer_bundle" in shown_bundles
assert "workspace" in shown_bundles
assert "dock_actions" in shown_bundles
def test_toolbar_refresh_on_mode_change(self, advanced_dock_area):
"""Test that toolbar is properly refreshed when mode changes."""
initial_bundles = set(advanced_dock_area.toolbar.shown_bundles)
# Change to a different mode
advanced_dock_area.mode = "plot"
plot_bundles = set(advanced_dock_area.toolbar.shown_bundles)
# Bundles should be different
assert initial_bundles != plot_bundles
assert "flat_plots" in plot_bundles
def test_mode_switching_preserves_existing_docks(self, advanced_dock_area, qtbot):
"""Test that mode switching doesn't affect existing docked widgets."""
# Create some widgets
advanced_dock_area.new("DarkModeButton")
advanced_dock_area.new("DarkModeButton")
qtbot.wait(200)
initial_dock_count = len(advanced_dock_area.dock_list())
initial_widget_count = len(advanced_dock_area.widget_list())
# Switch modes
advanced_dock_area.mode = "plot"
advanced_dock_area.mode = "device"
advanced_dock_area.mode = "user"
# Dock and widget counts should remain the same
assert len(advanced_dock_area.dock_list()) == initial_dock_count
assert len(advanced_dock_area.widget_list()) == initial_widget_count
class TestModeProperty:
"""Test mode property getter and setter behavior."""
def test_mode_property_getter(self, advanced_dock_area):
"""Test mode property getter returns correct value."""
# Set internal mode directly and test getter
advanced_dock_area._mode = "plot"
assert advanced_dock_area.mode == "plot"
advanced_dock_area._mode = "device"
assert advanced_dock_area.mode == "device"
def test_mode_property_setter_updates_internal_state(self, advanced_dock_area):
"""Test mode property setter updates internal state."""
advanced_dock_area.mode = "plot"
assert advanced_dock_area._mode == "plot"
advanced_dock_area.mode = "utils"
assert advanced_dock_area._mode == "utils"
def test_mode_property_setter_triggers_toolbar_update(self, advanced_dock_area):
"""Test mode property setter triggers toolbar update."""
with patch.object(advanced_dock_area.toolbar, "show_bundles") as mock_show_bundles:
advanced_dock_area.mode = "plot"
mock_show_bundles.assert_called_once()
def test_multiple_mode_changes(self, advanced_dock_area, qtbot):
"""Test multiple rapid mode changes."""
modes = ["plot", "device", "utils", "developer", "user"]
for i, mode in enumerate(modes):
with qtbot.waitSignal(advanced_dock_area.mode_changed, timeout=1000) as blocker:
advanced_dock_area.mode = mode
assert advanced_dock_area.mode == mode
assert blocker.args == [mode]