Source code for test_companion

"""Unit tests for MyPluginCompanion.

This file is copied verbatim (with ``MyPluginCompanion`` replaced by your
class name) when you create a new plugin with *New plugin from template…*.

Quick-start
-----------
Run from the project root::

    pytest <your_plugin_dir>/tests/ -v

Mocking strategy
----------------
The companion needs a ``WolfMapViewer`` at construction time, but tests must
work **without a display** (CI / headless servers).

Two fixtures are provided:

``viewer``
    A :class:`~unittest.mock.MagicMock` that satisfies every attribute and
    method call.  We explicitly set ``xmin`` / ``xmax`` so that helper
    methods that compute sizes relative to the viewport return sensible
    values instead of zero.

``gl_mock``
    A fake ``OpenGL.GL`` module injected into ``sys.modules`` via
    ``monkeypatch``.  Without it, any call to ``_paint()`` would fail with an
    ``ImportError``.  The fixture exposes the mock so you can assert on
    specific OpenGL calls::

        c._paint(viewer)
        gl_mock.glVertex2f.assert_called()

Key codes
---------
Import ``KeyboardSnapshot`` and pass integer key codes.  Common values::

    _WXK_ESCAPE = 27
    _WXK_RETURN = 13
"""
from __future__ import annotations

import sys
import types
from unittest.mock import MagicMock

import pytest

from wolfhece._viewer_plugin_handlers import KeyboardSnapshot, MouseContext

# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------
[docs] _WXK_ESCAPE = 27
[docs] _WXK_RETURN = 13
# --------------------------------------------------------------------------- # Fixtures # --------------------------------------------------------------------------- @pytest.fixture
[docs] def viewer(): """Minimal WolfMapViewer stand-in (no wx, no display required).""" v = MagicMock() v.xmin = 0.0 v.xmax = 1000.0 return v
@pytest.fixture
[docs] def gl_mock(monkeypatch): """Inject a headless OpenGL.GL module so paint methods do not crash.""" gl = types.ModuleType('OpenGL.GL') gl.glBegin = MagicMock() gl.glEnd = MagicMock() gl.glVertex2f = MagicMock() gl.glColor4f = MagicMock() gl.glLineWidth = MagicMock() gl.glPointSize = MagicMock() gl.GL_LINES = 1 gl.GL_LINE_STRIP = 3 gl.GL_LINE_LOOP = 2 gl.GL_POINTS = 0 opengl_pkg = types.ModuleType('OpenGL') opengl_pkg.GL = gl monkeypatch.setitem(sys.modules, 'OpenGL', opengl_pkg) monkeypatch.setitem(sys.modules, 'OpenGL.GL', gl) return gl
# --------------------------------------------------------------------------- # Helpers # ---------------------------------------------------------------------------
[docs] def _ctx(x: float = 0.0, y: float = 0.0) -> MouseContext: """Build a right-click context at world coordinates (x, y).""" return MouseContext(x=x, y=y, x_snap=x, y_snap=y, x_pixel=0, y_pixel=0)
[docs] def _kb(key_code: int, ctrl: bool = False) -> KeyboardSnapshot: """Build a keyboard event.""" return KeyboardSnapshot(key_code=key_code, ctrl=ctrl)
# --------------------------------------------------------------------------- # Import the class under test 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('_template_companion', _COMPANION_FILE)
[docs] _mod = _ilu.module_from_spec(_spec)
_spec.loader.exec_module(_mod) # type: ignore[union-attr]
[docs] MyPluginCompanion = _mod.MyPluginCompanion
# =========================================================================== # Tests — add your own below # ===========================================================================
[docs] class TestMyPluginCompanion: """Behaviour tests — pure Python, no wx, no display.""" # -- construction -------------------------------------------------------
[docs] def test_instantiation(self, viewer): """The companion can be created without errors.""" c = MyPluginCompanion(viewer) assert c is not None
# -- start --------------------------------------------------------------
[docs] def test_start_registers_action(self, viewer): c = MyPluginCompanion(viewer) # Register the action directly (menu_build requires a wx.App) c._register_action(c._action_id('run'), rdown=c._rdown) assert len(c._registered_action_ids) > 0
[docs] def test_start_calls_viewer(self, viewer): c = MyPluginCompanion(viewer) c.start() viewer.start_action.assert_called()
# -- keyboard -----------------------------------------------------------
[docs] def test_esc_stops_action(self, viewer): c = MyPluginCompanion(viewer) c.start() consumed = c._key(viewer, _kb(_WXK_ESCAPE)) assert consumed is True # stop() → _end_action() → viewer.end_action() viewer.end_action.assert_called()
[docs] def test_unknown_key_returns_false(self, viewer): c = MyPluginCompanion(viewer) c.start() consumed = c._key(viewer, _kb(ord('X'))) assert consumed is False
# -- paint --------------------------------------------------------------
[docs] def test_paint_does_not_crash(self, viewer, gl_mock): c = MyPluginCompanion(viewer) c.start() c._paint(viewer) # must not raise
# -- TODO: add tests for your own logic below --------------------------- # Example: # # def test_rdown_stores_point(self, viewer): # c = MyPluginCompanion(viewer) # c.start() # c._rdown(viewer, _ctx(10.0, 20.0)) # assert ... # check your state here