122 lines
4.2 KiB
Python
122 lines
4.2 KiB
Python
|
|
"""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")
|