otko/tests/gui/test_plot_properties.py
smillmorel bd365be77d feat: Options → Plot Properties dialog with live preview and persistence
Adds a table-driven dialog over RenderStyle.STYLE_FIELDS (colour swatches,
an opacity spin and a label font size), reachable from Options. Edits
preview immediately on every canvas, Cancel restores the style the dialog
opened with, and only OK persists — as JSON under QSettings plot/props,
reloaded on the next launch. Both canvases gain set_style(); the dialog
deliberately exposes plot_style() rather than style() so QWidget.style()
keeps its Qt meaning.
2026-09-16 19:31:38 -04:00

131 lines
4.5 KiB
Python

"""GUI tests for Options → Plot Properties.
Covers the dialog itself plus MainWindow's live-apply / persist / revert
wiring. QSettings are injected so nothing touches real user state.
"""
from __future__ import annotations
import pytest
pytest.importorskip("PySide6")
pytest.importorskip("pyvistaqt")
from PySide6.QtCore import QObject, QSettings, Signal
from PySide6.QtWidgets import QDialog
def _settings(tmp_path): # type: ignore[no-untyped-def]
return QSettings(str(tmp_path / "otko.ini"), QSettings.Format.IniFormat)
def _window(tmp_path, qtbot): # type: ignore[no-untyped-def]
from otko.views.main_window import MainWindow
window = MainWindow(settings=_settings(tmp_path))
qtbot.addWidget(window)
return window
def test_default_style_is_the_documented_palette(tmp_path, qtbot) -> None: # type: ignore[no-untyped-def]
from otko.views.canvas3d.style import RenderStyle
window = _window(tmp_path, qtbot)
assert window._style == RenderStyle()
def test_set_plot_style_reaches_both_canvases_and_persists(tmp_path, qtbot) -> None: # type: ignore[no-untyped-def]
window = _window(tmp_path, qtbot)
window.swap_canvas_backend("plotly") # build the second canvas too
window.set_plot_style(
window._style.with_updates(element_beam_color="#ff0000", label_font_size=20)
)
assert set(window._canvases) == {"pyvista", "plotly"}
for canvas in window._canvases.values():
assert canvas._style.element_beam_color == "#ff0000"
assert canvas._style.label_font_size == 20
# A fresh window sharing the settings file reloads the customisation.
from otko.views.main_window import MainWindow
fresh = MainWindow(settings=_settings(tmp_path))
qtbot.addWidget(fresh)
assert fresh._style.element_beam_color == "#ff0000"
assert fresh._style.label_font_size == 20
def test_preview_does_not_persist(tmp_path, qtbot) -> None: # type: ignore[no-untyped-def]
window = _window(tmp_path, qtbot)
window.set_plot_style(window._style.with_updates(node_color="#010203"), persist=False)
assert window._canvas._style.node_color == "#010203"
assert window._settings.value("plot/props") is None
def test_cancel_reverts_the_style(tmp_path, qtbot, monkeypatch) -> None: # type: ignore[no-untyped-def]
window = _window(tmp_path, qtbot)
original = window._style
preview = original.with_updates(node_color="#010203")
class _RejectingDialog(QObject):
changed = Signal(object)
def __init__(self, style: object, parent: QObject | None = None) -> None:
super().__init__(parent)
self._style = preview
def exec(self) -> QDialog.DialogCode:
self.changed.emit(preview) # live preview happens...
return QDialog.DialogCode.Rejected # ...but the user cancels
def plot_style(self): # type: ignore[no-untyped-def]
return self._style
monkeypatch.setattr("otko.views.dialogs.PlotPropertiesDialog", _RejectingDialog)
window._on_plot_properties()
assert window._style == original
assert window._canvas._style.node_color == original.node_color
assert window._settings.value("plot/props") is None
def test_accept_applies_and_persists(tmp_path, qtbot, monkeypatch) -> None: # type: ignore[no-untyped-def]
window = _window(tmp_path, qtbot)
preview = window._style.with_updates(node_color="#010203")
class _AcceptingDialog(QObject):
changed = Signal(object)
def __init__(self, style: object, parent: QObject | None = None) -> None:
super().__init__(parent)
self._style = preview
def exec(self) -> QDialog.DialogCode:
return QDialog.DialogCode.Accepted
def plot_style(self): # type: ignore[no-untyped-def]
return self._style
monkeypatch.setattr("otko.views.dialogs.PlotPropertiesDialog", _AcceptingDialog)
window._on_plot_properties()
assert window._style.node_color == "#010203"
assert window._canvas._style.node_color == "#010203"
assert "#010203" in str(window._settings.value("plot/props"))
def test_dialog_edits_and_reset(tmp_path, qtbot) -> None: # type: ignore[no-untyped-def]
from otko.views.canvas3d.style import RenderStyle
from otko.views.dialogs import PlotPropertiesDialog
dialog = PlotPropertiesDialog(RenderStyle())
qtbot.addWidget(dialog)
assert dialog.plot_style() == RenderStyle()
dialog._colors["node_color"].set_color("#abcdef")
assert dialog.plot_style().node_color == "#abcdef"
dialog._reset()
assert dialog.plot_style() == RenderStyle()