"""GUI tests for the live canvas-backend switch (PyVista ⇄ Plotly). QSettings are injected per-test so a swap never leaks into the real user configuration or the next test. """ from __future__ import annotations import pytest pytest.importorskip("PySide6") pytest.importorskip("pyvistaqt") pytest.importorskip("plotly") from PySide6.QtCore import QSettings def _make_window(tmp_path, qtbot): # type: ignore[no-untyped-def] from otko.views.main_window import MainWindow settings = QSettings(str(tmp_path / "otko.ini"), QSettings.Format.IniFormat) window = MainWindow(settings=settings) qtbot.addWidget(window) return window @pytest.mark.gui def test_default_backend_is_pyvista(tmp_path, qtbot) -> None: # type: ignore[no-untyped-def] from otko.views.canvas3d import ModelCanvas window = _make_window(tmp_path, qtbot) assert window.canvas_backend() == "pyvista" assert isinstance(window._canvas, ModelCanvas) assert window._diagram_renderer is not None @pytest.mark.gui def test_switch_to_plotly_preserves_selection(tmp_path, qtbot) -> None: # type: ignore[no-untyped-def] from otko.views.canvas_plotly import PlotlyCanvas window = _make_window(tmp_path, qtbot) window._vm.new_project() window._canvas.selection.select_node(2) window.swap_canvas_backend("plotly") assert window.canvas_backend() == "plotly" assert isinstance(window._canvas, PlotlyCanvas) # One shared SelectionState: the tree/table/properties stay bound. assert window._canvas.selection is window._selection assert window._canvas.selection.nodes == frozenset({2}) # Both canvases live in the stack; the PyVista one was not destroyed. assert window._canvas_stack.count() == 2 @pytest.mark.gui def test_plotly_gates_pyvista_only_features(tmp_path, qtbot) -> None: # type: ignore[no-untyped-def] window = _make_window(tmp_path, qtbot) assert window._canvas.capabilities.diagrams is True window.swap_canvas_backend("plotly") caps = window._canvas.capabilities assert caps.diagrams is False assert caps.animation_export is False assert caps.extrusions is True and caps.labels is True window._refresh_action_enablement() assert window._act_show_force_diagram.isEnabled() is False assert window._act_export_th_animation.isEnabled() is False @pytest.mark.gui def test_swapping_back_reuses_the_same_canvases(tmp_path, qtbot) -> None: # type: ignore[no-untyped-def] window = _make_window(tmp_path, qtbot) pyvista_canvas = window._canvas window.swap_canvas_backend("plotly") plotly_canvas = window._canvas window.swap_canvas_backend("pyvista") assert window._canvas is pyvista_canvas assert window._diagram_renderer is not None # No duplicate widgets accumulate across repeated swaps. window.swap_canvas_backend("plotly") assert window._canvas is plotly_canvas assert window._diagram_renderer is None # diagrams are PyVista-only assert window._canvas_stack.count() == 2 @pytest.mark.gui def test_backend_choice_is_persisted(tmp_path, qtbot) -> None: # type: ignore[no-untyped-def] window = _make_window(tmp_path, qtbot) window.swap_canvas_backend("plotly") assert window._settings.value("canvas/backend") == "plotly" # A fresh window with the same settings boots straight into Plotly. from otko.views.canvas_plotly import PlotlyCanvas from otko.views.main_window import MainWindow settings = QSettings(str(tmp_path / "otko.ini"), QSettings.Format.IniFormat) second = MainWindow(settings=settings) qtbot.addWidget(second) assert second.canvas_backend() == "plotly" assert isinstance(second._canvas, PlotlyCanvas) @pytest.mark.gui def test_working_plane_survives_the_swap(tmp_path, qtbot) -> None: # type: ignore[no-untyped-def] window = _make_window(tmp_path, qtbot) window._vm.open(_example("basic_truss")) window._canvas.set_working_plane("XY", 0.0) window.swap_canvas_backend("plotly") assert window._canvas.working_plane_type() == "XY" assert window._canvas.working_plane_offset() == 0.0 def _example(name: str) -> str: from pathlib import Path return str(Path(__file__).resolve().parents[2] / "examples" / f"{name}.osmodel")