Files
bec_widgets/tests/unit_tests/test_workspace_tabs.py
T

270 lines
8.5 KiB
Python

# pylint: disable=missing-function-docstring, missing-module-docstring, redefined-outer-name
import pytest
from qtpy.QtWidgets import QInputDialog, QMessageBox
from bec_widgets.tests.client_mocks import mocked_client
from bec_widgets.widgets.containers.dock_area.profile_utils import (
get_open_workspaces,
list_profiles,
set_open_workspaces,
)
from bec_widgets.widgets.containers.dock_area.workspace_tabs import (
UNTITLED_WORKSPACE,
WorkspaceTabs,
)
NAMESPACE = "tabs_test"
@pytest.fixture(autouse=True)
def isolate_profile_storage(tmp_path, monkeypatch):
root = tmp_path / "profiles_root"
root.mkdir(parents=True, exist_ok=True)
monkeypatch.setenv("BECWIDGETS_PROFILE_DIR", str(root))
yield
@pytest.fixture
def make_tabs(qtbot, mocked_client):
created = []
def _make(**kwargs):
tabs = WorkspaceTabs(profile_namespace=NAMESPACE, client=mocked_client, **kwargs)
qtbot.addWidget(tabs)
tabs.show()
qtbot.waitExposed(tabs)
created.append(tabs)
return tabs
yield _make
for tabs in created:
tabs.cleanup()
def _save_profile(tabs, name):
"""Save the current tab under *name* without dialogs."""
tabs.current_dock_area().save_profile(name, show_dialog=False)
def test_starts_with_one_untitled_workspace(make_tabs):
tabs = make_tabs()
assert tabs.count() == 1
assert tabs.tabText(0) == UNTITLED_WORKSPACE
assert tabs.current_dock_area() is not None
assert tabs.workspace_names() == [None]
def test_new_workspace_adds_and_switches_tab(make_tabs):
tabs = make_tabs()
first = tabs.current_dock_area()
tabs.new_workspace()
assert tabs.count() == 2
assert tabs.currentIndex() == 1
second = tabs.current_dock_area()
assert second is not first
assert tabs.dock_areas() == [first, second]
def test_each_tab_keeps_its_own_layout(make_tabs):
tabs = make_tabs()
tabs.current_dock_area().new("RingProgressBar")
tabs.new_workspace()
assert tabs.current_dock_area().dock_list() == []
tabs.setCurrentIndex(0)
assert len(tabs.current_dock_area().dock_list()) == 1
def test_saving_names_the_tab_and_persists_open_tabs(make_tabs):
tabs = make_tabs()
_save_profile(tabs, "alignment")
assert tabs.tabText(0) == "alignment"
tabs.new_workspace()
_save_profile(tabs, "scans")
assert get_open_workspaces(NAMESPACE) == (["alignment", "scans"], "scans")
tabs.setCurrentIndex(0)
assert get_open_workspaces(NAMESPACE) == (["alignment", "scans"], "alignment")
def test_unsaved_tabs_are_not_persisted(make_tabs):
tabs = make_tabs()
_save_profile(tabs, "alignment")
tabs.new_workspace()
assert get_open_workspaces(NAMESPACE) == (["alignment"], None)
def test_restores_open_tabs_lazily(make_tabs):
tabs = make_tabs()
tabs.current_dock_area().new("RingProgressBar")
_save_profile(tabs, "alignment")
tabs.new_workspace()
_save_profile(tabs, "scans")
tabs.setCurrentIndex(0)
tabs.cleanup()
restored = make_tabs()
assert [restored.tabText(i) for i in range(restored.count())] == ["alignment", "scans"]
assert restored.currentIndex() == 0
# Only the active tab builds its dock area at startup
assert [page.is_materialized for page in restored.pages()] == [True, False]
assert len(restored.current_dock_area().dock_list()) == 1
restored.setCurrentIndex(1)
assert restored.pages()[1].is_materialized
assert restored.current_dock_area()._current_profile_name == "scans"
def test_restore_skips_missing_profiles(make_tabs):
set_open_workspaces(["gone", "also_gone"], "gone", namespace=NAMESPACE)
tabs = make_tabs()
assert tabs.count() == 1
assert tabs.workspace_names() == [None]
def test_restore_is_scoped_to_known_profiles(make_tabs):
tabs = make_tabs()
_save_profile(tabs, "alignment")
tabs.cleanup()
set_open_workspaces(["alignment", "gone"], "gone", namespace=NAMESPACE)
restored = make_tabs()
assert restored.workspace_names() == ["alignment"]
def test_open_workspace_switches_to_existing_tab(make_tabs):
tabs = make_tabs()
_save_profile(tabs, "alignment")
tabs.new_workspace()
tabs.open_workspace("alignment")
assert tabs.count() == 2
assert tabs.currentIndex() == 0
def test_open_workspace_opens_saved_profile_in_new_tab(make_tabs):
tabs = make_tabs()
tabs.current_dock_area().new("RingProgressBar")
_save_profile(tabs, "alignment")
tabs.new_workspace()
_save_profile(tabs, "scans")
tabs.close_workspace(0)
assert tabs.workspace_names() == ["scans"]
tabs.open_workspace("alignment")
assert tabs.workspace_names() == ["scans", "alignment"]
assert len(tabs.current_dock_area().dock_list()) == 1
def test_loading_profile_open_in_other_tab_switches_instead(make_tabs):
tabs = make_tabs()
_save_profile(tabs, "alignment")
tabs.new_workspace()
_save_profile(tabs, "scans")
tabs.current_dock_area().load_profile("alignment")
assert tabs.currentIndex() == 0
assert tabs.workspace_names() == ["alignment", "scans"]
def test_saving_under_name_open_in_other_tab_is_refused(make_tabs):
tabs = make_tabs()
_save_profile(tabs, "alignment")
tabs.new_workspace()
_save_profile(tabs, "alignment")
assert tabs.workspace_names() == ["alignment", None]
def test_close_workspace_keeps_profile_on_disk(make_tabs):
tabs = make_tabs()
_save_profile(tabs, "alignment")
tabs.new_workspace()
_save_profile(tabs, "scans")
assert tabs.close_workspace(0)
assert tabs.workspace_names() == ["scans"]
assert "alignment" in list_profiles(NAMESPACE)
assert get_open_workspaces(NAMESPACE) == (["scans"], "scans")
def test_closing_last_tab_leaves_empty_workspace(make_tabs):
tabs = make_tabs()
_save_profile(tabs, "alignment")
tabs.close_workspace(0)
assert tabs.count() == 1
assert tabs.workspace_names() == [None]
assert "alignment" in list_profiles(NAMESPACE)
def test_close_unsaved_workspace_with_widgets_asks_first(make_tabs, monkeypatch):
tabs = make_tabs()
tabs.current_dock_area().new("RingProgressBar")
tabs.new_workspace()
monkeypatch.setattr(
QMessageBox, "question", lambda *args, **kwargs: QMessageBox.StandardButton.No
)
assert not tabs.close_workspace(0)
assert tabs.count() == 2
monkeypatch.setattr(
QMessageBox, "question", lambda *args, **kwargs: QMessageBox.StandardButton.Yes
)
assert tabs.close_workspace(0)
assert tabs.count() == 1
def test_rename_saves_under_new_name_and_keeps_old_profile(make_tabs):
tabs = make_tabs()
_save_profile(tabs, "alignment")
assert tabs.rename_workspace(0, "alignment_v2")
assert tabs.tabText(0) == "alignment_v2"
profiles = list_profiles(NAMESPACE)
assert "alignment" in profiles and "alignment_v2" in profiles
assert get_open_workspaces(NAMESPACE) == (["alignment_v2"], "alignment_v2")
def test_rename_untitled_workspace_saves_it(make_tabs, monkeypatch):
tabs = make_tabs()
monkeypatch.setattr(QInputDialog, "getText", lambda *args, **kwargs: ("beamtime", True))
assert tabs.rename_workspace()
assert tabs.tabText(0) == "beamtime"
assert "beamtime" in list_profiles(NAMESPACE)
def test_rename_to_name_open_in_other_tab_is_refused(make_tabs, monkeypatch):
tabs = make_tabs()
_save_profile(tabs, "alignment")
tabs.new_workspace()
infos = []
monkeypatch.setattr(QMessageBox, "information", lambda *args, **kwargs: infos.append(args))
assert not tabs.rename_workspace(1, "alignment")
assert infos
assert tabs.workspace_names() == ["alignment", None]
def test_rename_over_existing_saved_profile_asks_first(make_tabs, monkeypatch):
tabs = make_tabs()
_save_profile(tabs, "alignment")
tabs.close_workspace(0)
monkeypatch.setattr(
QMessageBox, "question", lambda *args, **kwargs: QMessageBox.StandardButton.No
)
assert not tabs.rename_workspace(0, "alignment")
assert tabs.workspace_names() == [None]
def test_moving_tabs_updates_persisted_order(make_tabs):
tabs = make_tabs()
_save_profile(tabs, "alignment")
tabs.new_workspace()
_save_profile(tabs, "scans")
tabs.tabBar().moveTab(1, 0)
assert get_open_workspaces(NAMESPACE)[0] == ["scans", "alignment"]
def test_open_menu_lists_saved_profiles(make_tabs):
tabs = make_tabs()
_save_profile(tabs, "alignment")
tabs._populate_open_menu()
texts = [action.text() for action in tabs._open_menu.actions()]
assert "New Workspace" in texts
assert "alignment" in texts