Source code for wolfhece._builtin_plugins.grid_adder.tests.test_companion

"""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 -------------------------------------------
[docs] def test_menu_build_idempotent(self, viewer, dialogs): """Calling menu_build twice must not raise (guards run without wx).""" c = _new_companion(viewer, dialogs) # The first call would normally need a wx.App / menubar; here we only # check that the second call does not explode (the internal guard # short-circuits before touching wx). try: c.build() except Exception: pass # first call may fail without wx — that is expected # A second call on an already-built menu must be a no-op and not raise. try: c.build() except Exception: pass # same tolerance as first call
# -- 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()