"""Unit tests for GridAdderCompanion.
Running these tests
-------------------
From the project root::
pytest wolfhece/_builtin_plugins/grid_adder/tests/ -v
Mocking strategy
----------------
The companion needs a ``WolfMapViewer`` instance but tests run **headless**
(no wx.App, no OpenGL context).
``viewer`` fixture
A :class:`~unittest.mock.MagicMock` with ``xmin`` / ``xmax`` set.
It records every call to ``add_object``, ``set_status_text``, etc.
``dialogs`` fixture
A :class:`~unittest.mock.MagicMock` injected as the ``DialogProvider``
so we control what ``ask_float`` returns without any wx dialog appearing.
"""
from __future__ import annotations
from unittest.mock import MagicMock, call, patch
import pytest
# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------
@pytest.fixture
[docs]
def viewer():
v = MagicMock()
v.xmin = 0.0
v.xmax = 1000.0
return v
@pytest.fixture
[docs]
def dialogs():
return MagicMock()
# ---------------------------------------------------------------------------
# Import the companion via importlib to avoid sys.path collisions when
# multiple plugins run in the same pytest session.
# ---------------------------------------------------------------------------
import importlib.util as _ilu
from pathlib import Path as _Path
[docs]
_COMPANION_FILE = _Path(__file__).parent.parent / 'companion.py'
[docs]
_spec = _ilu.spec_from_file_location('_grid_adder_companion', _COMPANION_FILE)
[docs]
_mod = _ilu.module_from_spec(_spec)
_spec.loader.exec_module(_mod) # type: ignore[union-attr]
[docs]
GridAdderCompanion = _mod.GridAdderCompanion
[docs]
def _new_companion(viewer, dialogs):
c = GridAdderCompanion(dialogs=dialogs)
c.proxy.attach(viewer, dialogs=dialogs)
return c
# ===========================================================================
# Tests
# ===========================================================================
[docs]
class TestGridAdderCompanion:
"""Behaviour tests — no wx, no OpenGL required."""
# -- construction -------------------------------------------------------
[docs]
def test_construction(self, viewer, dialogs):
c = _new_companion(viewer, dialogs)
assert c.proxy._viewer is viewer
# -- start / cancel via dialog ------------------------------------------
[docs]
def test_start_calls_add_object_when_confirmed(self, viewer, dialogs):
"""When the user confirms the dialog, add_object must be called once."""
dialogs.ask_float.return_value = 500.0
c = _new_companion(viewer, dialogs)
with patch('wolfhece.pyvertexvectors.Grid') as MockGrid:
mock_grid_instance = MagicMock()
MockGrid.return_value = mock_grid_instance
c.start()
MockGrid.assert_called_once_with(500.0)
viewer.add_object.assert_called_once_with(
'vector', newobj=mock_grid_instance, ToCheck=False, id='Grid'
)
[docs]
def test_start_does_not_call_add_object_when_cancelled(self, viewer, dialogs):
"""When the user cancels the dialog, add_object must NOT be called."""
dialogs.ask_float.return_value = None
c = _new_companion(viewer, dialogs)
c.start()
viewer.add_object.assert_not_called()
[docs]
def test_default_size_passed_to_dialog(self, viewer, dialogs):
"""The dialog must be opened with the default cell size of 1000."""
dialogs.ask_float.return_value = None # cancel immediately
c = _new_companion(viewer, dialogs)
c.start()
args, kwargs = dialogs.ask_float.call_args
assert kwargs.get('default', args[1] if len(args) > 1 else None) == 1000.0
# -- status message -----------------------------------------------------
[docs]
def test_status_set_after_add(self, viewer, dialogs):
"""A status message must be emitted after successfully adding the grid."""
dialogs.ask_float.return_value = 250.0
c = _new_companion(viewer, dialogs)
with patch('wolfhece.pyvertexvectors.Grid'):
c.start()
viewer.set_statusbar_text.assert_called()
# -- menu_build idempotency -------------------------------------------
# -- on_add delegate -----------------------------------------------
[docs]
def test_on_add_delegates_to_start(self, viewer, dialogs):
"""The menu handler _on_add must call _do_add_grid via start()."""
dialogs.ask_float.return_value = None # cancel — we only check the call
c = _new_companion(viewer, dialogs)
c._on_add(event=None)
dialogs.ask_float.assert_called_once()