From 081ac6411bc32758d889959d55c1d28096c04a6b Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 1 May 2025 12:41:48 +0200 Subject: [PATCH] Trigger events via standard callbacks in widget testing. Sending actual events through the whole event processing pipeline is a more complete test, reveals a few minor issues (see changes below), and avoids being linked to the rather nonstandard widget method names ("press" or "_click"?). The coordinates in the "move first vertex after completing the polygon" subtest of test_polygon_selector(draw_bounding_box=True) were altered because the original coordinates would actually not work in a real case, as the mouse-drag would actually also trigger the polygon-rescaling behavior. The coordinates in test_rectangle_{drag,resize} were altered because for the original coordinates, the click_and_drag would actually be ignore()d due to starting (just) outside of the axes. --- .../deprecations/29993-AL.rst | 4 + lib/matplotlib/backend_bases.py | 20 + lib/matplotlib/testing/widgets.py | 23 +- lib/matplotlib/tests/test_widgets.py | 378 ++++++++---------- lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 28 +- 5 files changed, 222 insertions(+), 231 deletions(-) create mode 100644 doc/api/next_api_changes/deprecations/29993-AL.rst diff --git a/doc/api/next_api_changes/deprecations/29993-AL.rst b/doc/api/next_api_changes/deprecations/29993-AL.rst new file mode 100644 index 000000000000..9104fd669325 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/29993-AL.rst @@ -0,0 +1,4 @@ +``testing.widgets.mock_event`` and ``testing.widgets.do_event`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +... are deprecated. Directly construct Event objects (typically `.MouseEvent` +or `.KeyEvent`) and pass them to ``canvas.callbacks.process()`` instead. diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 2158990f578a..ebcf18dec2ff 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -1422,6 +1422,15 @@ def __init__(self, name, canvas, x, y, button=None, key=None, self.step = step self.dblclick = dblclick + @classmethod + def _from_ax_coords(cls, name, ax, xy, *args, **kwargs): + """Generate a synthetic event at a given axes coordinate.""" + x, y = ax.transData.transform(xy) + event = cls(name, ax.figure.canvas, x, y, *args, **kwargs) + event.inaxes = ax + event.xdata, event.ydata = xy # Force exact xy to avoid fp roundtrip issues. + return event + def __str__(self): return (f"{self.name}: " f"xy=({self.x}, {self.y}) xydata=({self.xdata}, {self.ydata}) " @@ -1514,6 +1523,17 @@ def __init__(self, name, canvas, key, x=0, y=0, guiEvent=None): super().__init__(name, canvas, x, y, guiEvent=guiEvent) self.key = key + @classmethod + def _from_ax_coords(cls, name, ax, xy, key, *args, **kwargs): + """Generate a synthetic event at a given axes coordinate.""" + # Separate from MouseEvent._from_ax_coords instead of being defined in the base + # class, due to different parameter order in the constructor signature. + x, y = ax.transData.transform(xy) + event = cls(name, ax.figure.canvas, key, x, y, *args, **kwargs) + event.inaxes = ax + event.xdata, event.ydata = xy # Force exact xy to avoid fp roundtrip issues. + return event + # Default callback for key events. def _key_handler(event): diff --git a/lib/matplotlib/testing/widgets.py b/lib/matplotlib/testing/widgets.py index 3962567aa7c0..c528ffb2537c 100644 --- a/lib/matplotlib/testing/widgets.py +++ b/lib/matplotlib/testing/widgets.py @@ -8,6 +8,8 @@ from unittest import mock +from matplotlib import _api +from matplotlib.backend_bases import MouseEvent, KeyEvent import matplotlib.pyplot as plt @@ -24,6 +26,7 @@ def noop(*args, **kwargs): pass +@_api.deprecated("3.11", alternative="MouseEvent or KeyEvent") def mock_event(ax, button=1, xdata=0, ydata=0, key=None, step=1): r""" Create a mock event that can stand in for `.Event` and its subclasses. @@ -65,6 +68,7 @@ def mock_event(ax, button=1, xdata=0, ydata=0, key=None, step=1): return event +@_api.deprecated("3.11", alternative="callbacks.process(event)") def do_event(tool, etype, button=1, xdata=0, ydata=0, key=None, step=1): """ Trigger an event on the given tool. @@ -105,15 +109,12 @@ def click_and_drag(tool, start, end, key=None): An optional key that is pressed during the whole operation (see also `.KeyEvent`). """ - if key is not None: - # Press key - do_event(tool, 'on_key_press', xdata=start[0], ydata=start[1], - button=1, key=key) + ax = tool.ax + if key is not None: # Press key + KeyEvent._from_ax_coords("key_press_event", ax, start, key)._process() # Click, move, and release mouse - do_event(tool, 'press', xdata=start[0], ydata=start[1], button=1) - do_event(tool, 'onmove', xdata=end[0], ydata=end[1], button=1) - do_event(tool, 'release', xdata=end[0], ydata=end[1], button=1) - if key is not None: - # Release key - do_event(tool, 'on_key_release', xdata=end[0], ydata=end[1], - button=1, key=key) + MouseEvent._from_ax_coords("button_press_event", ax, start, 1)._process() + MouseEvent._from_ax_coords("motion_notify_event", ax, end, 1)._process() + MouseEvent._from_ax_coords("button_release_event", ax, end, 1)._process() + if key is not None: # Release key + KeyEvent._from_ax_coords("key_release_event", ax, end, key)._process() diff --git a/lib/matplotlib/tests/test_widgets.py b/lib/matplotlib/tests/test_widgets.py index 808863fd6a94..667499e38ccc 100644 --- a/lib/matplotlib/tests/test_widgets.py +++ b/lib/matplotlib/tests/test_widgets.py @@ -3,13 +3,12 @@ import operator from unittest import mock -from matplotlib.backend_bases import MouseEvent +from matplotlib.backend_bases import KeyEvent, MouseEvent import matplotlib.colors as mcolors import matplotlib.widgets as widgets import matplotlib.pyplot as plt from matplotlib.testing.decorators import check_figures_equal, image_comparison -from matplotlib.testing.widgets import (click_and_drag, do_event, get_ax, - mock_event, noop) +from matplotlib.testing.widgets import click_and_drag, get_ax, noop import numpy as np from numpy.testing import assert_allclose @@ -71,11 +70,10 @@ def test_rectangle_selector(ax, kwargs): onselect = mock.Mock(spec=noop, return_value=None) tool = widgets.RectangleSelector(ax, onselect=onselect, **kwargs) - do_event(tool, 'press', xdata=100, ydata=100, button=1) - do_event(tool, 'onmove', xdata=199, ydata=199, button=1) - + MouseEvent._from_ax_coords("button_press_event", ax, (100, 100), 1)._process() + MouseEvent._from_ax_coords("motion_notify_event", ax, (199, 199), 1)._process() # purposely drag outside of axis for release - do_event(tool, 'release', xdata=250, ydata=250, button=1) + MouseEvent._from_ax_coords("button_release_event", ax, (250, 250), 1)._process() if kwargs.get('drawtype', None) not in ['line', 'none']: assert_allclose(tool.geometry, @@ -137,7 +135,7 @@ def test_rectangle_drag(ax, drag_from_anywhere, new_center): tool = widgets.RectangleSelector(ax, interactive=True, drag_from_anywhere=drag_from_anywhere) # Create rectangle - click_and_drag(tool, start=(0, 10), end=(100, 120)) + click_and_drag(tool, start=(10, 10), end=(90, 120)) assert tool.center == (50, 65) # Drag inside rectangle, but away from centre handle # @@ -178,8 +176,8 @@ def test_rectangle_selector_set_props_handle_props(ax): def test_rectangle_resize(ax): tool = widgets.RectangleSelector(ax, interactive=True) # Create rectangle - click_and_drag(tool, start=(0, 10), end=(100, 120)) - assert tool.extents == (0.0, 100.0, 10.0, 120.0) + click_and_drag(tool, start=(10, 10), end=(100, 120)) + assert tool.extents == (10.0, 100.0, 10.0, 120.0) # resize NE handle extents = tool.extents @@ -446,11 +444,11 @@ def test_rectangle_rotate(ax, selector_class): assert len(tool._state) == 0 # Rotate anticlockwise using top-right corner - do_event(tool, 'on_key_press', key='r') + KeyEvent("key_press_event", ax.figure.canvas, "r")._process() assert tool._state == {'rotate'} assert len(tool._state) == 1 click_and_drag(tool, start=(130, 140), end=(120, 145)) - do_event(tool, 'on_key_press', key='r') + KeyEvent("key_press_event", ax.figure.canvas, "r")._process() assert len(tool._state) == 0 # Extents shouldn't change (as shape of rectangle hasn't changed) assert tool.extents == (100, 130, 100, 140) @@ -636,10 +634,10 @@ def test_span_selector(ax, orientation, onmove_callback, kwargs): tax = ax.twinx() tool = widgets.SpanSelector(ax, onselect, orientation, **kwargs) - do_event(tool, 'press', xdata=100, ydata=100, button=1) + MouseEvent._from_ax_coords("button_press_event", ax, (100, 100), 1)._process() # move outside of axis - do_event(tool, 'onmove', xdata=199, ydata=199, button=1) - do_event(tool, 'release', xdata=250, ydata=250, button=1) + MouseEvent._from_ax_coords("motion_notify_event", ax, (199, 199), 1)._process() + MouseEvent._from_ax_coords("button_release_event", ax, (250, 250), 1)._process() onselect.assert_called_once_with(100, 199) if onmove_callback: @@ -783,7 +781,7 @@ def test_selector_clear(ax, selector): click_and_drag(tool, start=(130, 130), end=(130, 130)) assert tool._selection_completed - do_event(tool, 'on_key_press', key='escape') + KeyEvent("key_press_event", ax.figure.canvas, "escape")._process() assert not tool._selection_completed @@ -905,10 +903,8 @@ def mean(vmin, vmax): # Add span selector and check that the line is draw after it was updated # by the callback - press_data = [1, 2] - move_data = [2, 2] - do_event(span, 'press', xdata=press_data[0], ydata=press_data[1], button=1) - do_event(span, 'onmove', xdata=move_data[0], ydata=move_data[1], button=1) + MouseEvent._from_ax_coords("button_press_event", ax, (1, 2), 1)._process() + MouseEvent._from_ax_coords("motion_notify_event", ax, (2, 2), 1)._process() assert span._get_animated_artists() == (ln, ln2) assert ln.stale is False assert ln2.stale @@ -918,16 +914,12 @@ def mean(vmin, vmax): # Change span selector and check that the line is drawn/updated after its # value was updated by the callback - press_data = [4, 0] - move_data = [5, 2] - release_data = [5, 2] - do_event(span, 'press', xdata=press_data[0], ydata=press_data[1], button=1) - do_event(span, 'onmove', xdata=move_data[0], ydata=move_data[1], button=1) + MouseEvent._from_ax_coords("button_press_event", ax, (4, 0), 1)._process() + MouseEvent._from_ax_coords("motion_notify_event", ax, (5, 2), 1)._process() assert ln.stale is False assert ln2.stale assert_allclose(ln2.get_ydata(), -0.9424150707548072) - do_event(span, 'release', xdata=release_data[0], - ydata=release_data[1], button=1) + MouseEvent._from_ax_coords("button_release_event", ax, (5, 2), 1)._process() assert ln2.stale is False @@ -988,9 +980,9 @@ def test_lasso_selector(ax, kwargs): onselect = mock.Mock(spec=noop, return_value=None) tool = widgets.LassoSelector(ax, onselect=onselect, **kwargs) - do_event(tool, 'press', xdata=100, ydata=100, button=1) - do_event(tool, 'onmove', xdata=125, ydata=125, button=1) - do_event(tool, 'release', xdata=150, ydata=150, button=1) + MouseEvent._from_ax_coords("button_press_event", ax, (100, 100), 1)._process() + MouseEvent._from_ax_coords("motion_notify_event", ax, (125, 125), 1)._process() + MouseEvent._from_ax_coords("button_release_event", ax, (150, 150), 1)._process() onselect.assert_called_once_with([(100, 100), (125, 125), (150, 150)]) @@ -1066,7 +1058,7 @@ def test_TextBox(ax, toolbar): assert tool.text == '' - do_event(tool, '_click') + MouseEvent._from_ax_coords("button_press_event", ax, (.5, .5), 1)._process() tool.set_val('x**2') @@ -1078,9 +1070,9 @@ def test_TextBox(ax, toolbar): assert submit_event.call_count == 2 - do_event(tool, '_click', xdata=.5, ydata=.5) # Ensure the click is in the axes. - do_event(tool, '_keypress', key='+') - do_event(tool, '_keypress', key='5') + MouseEvent._from_ax_coords("button_press_event", ax, (.5, .5), 1)._process() + KeyEvent("key_press_event", ax.figure.canvas, "+")._process() + KeyEvent("key_press_event", ax.figure.canvas, "5")._process() assert text_change_event.call_count == 3 @@ -1343,162 +1335,160 @@ def test_range_slider_same_init_values(orientation): assert_allclose(box.get_points().flatten()[idx], [0, 0.25, 0, 0.75]) -def check_polygon_selector(event_sequence, expected_result, selections_count, - **kwargs): +def check_polygon_selector(events, expected, selections_count, **kwargs): """ Helper function to test Polygon Selector. Parameters ---------- - event_sequence : list of tuples (etype, dict()) - A sequence of events to perform. The sequence is a list of tuples - where the first element of the tuple is an etype (e.g., 'onmove', - 'press', etc.), and the second element of the tuple is a dictionary of - the arguments for the event (e.g., xdata=5, key='shift', etc.). - expected_result : list of vertices (xdata, ydata) - The list of vertices that are expected to result from the event - sequence. + events : list[MouseEvent] + A sequence of events to perform. + expected : list of vertices (xdata, ydata) + The list of vertices expected to result from the event sequence. selections_count : int Wait for the tool to call its `onselect` function `selections_count` - times, before comparing the result to the `expected_result` + times, before comparing the result to the `expected` **kwargs Keyword arguments are passed to PolygonSelector. """ - ax = get_ax() - onselect = mock.Mock(spec=noop, return_value=None) + ax = events[0].canvas.figure.axes[0] tool = widgets.PolygonSelector(ax, onselect=onselect, **kwargs) - for (etype, event_args) in event_sequence: - do_event(tool, etype, **event_args) + for event in events: + event._process() assert onselect.call_count == selections_count - assert onselect.call_args == ((expected_result, ), {}) + assert onselect.call_args == ((expected, ), {}) -def polygon_place_vertex(xdata, ydata): - return [('onmove', dict(xdata=xdata, ydata=ydata)), - ('press', dict(xdata=xdata, ydata=ydata)), - ('release', dict(xdata=xdata, ydata=ydata))] +def polygon_place_vertex(ax, xy): + return [ + MouseEvent._from_ax_coords("motion_notify_event", ax, xy), + MouseEvent._from_ax_coords("button_press_event", ax, xy, 1), + MouseEvent._from_ax_coords("button_release_event", ax, xy, 1), + ] -def polygon_remove_vertex(xdata, ydata): - return [('onmove', dict(xdata=xdata, ydata=ydata)), - ('press', dict(xdata=xdata, ydata=ydata, button=3)), - ('release', dict(xdata=xdata, ydata=ydata, button=3))] +def polygon_remove_vertex(ax, xy): + return [ + MouseEvent._from_ax_coords("motion_notify_event", ax, xy), + MouseEvent._from_ax_coords("button_press_event", ax, xy, 3), + MouseEvent._from_ax_coords("button_release_event", ax, xy, 3), + ] @pytest.mark.parametrize('draw_bounding_box', [False, True]) -def test_polygon_selector(draw_bounding_box): +def test_polygon_selector(ax, draw_bounding_box): check_selector = functools.partial( check_polygon_selector, draw_bounding_box=draw_bounding_box) # Simple polygon expected_result = [(50, 50), (150, 50), (50, 150)] event_sequence = [ - *polygon_place_vertex(50, 50), - *polygon_place_vertex(150, 50), - *polygon_place_vertex(50, 150), - *polygon_place_vertex(50, 50), + *polygon_place_vertex(ax, (50, 50)), + *polygon_place_vertex(ax, (150, 50)), + *polygon_place_vertex(ax, (50, 150)), + *polygon_place_vertex(ax, (50, 50)), ] check_selector(event_sequence, expected_result, 1) # Move first vertex before completing the polygon. expected_result = [(75, 50), (150, 50), (50, 150)] event_sequence = [ - *polygon_place_vertex(50, 50), - *polygon_place_vertex(150, 50), - ('on_key_press', dict(key='control')), - ('onmove', dict(xdata=50, ydata=50)), - ('press', dict(xdata=50, ydata=50)), - ('onmove', dict(xdata=75, ydata=50)), - ('release', dict(xdata=75, ydata=50)), - ('on_key_release', dict(key='control')), - *polygon_place_vertex(50, 150), - *polygon_place_vertex(75, 50), + *polygon_place_vertex(ax, (50, 50)), + *polygon_place_vertex(ax, (150, 50)), + KeyEvent("key_press_event", ax.figure.canvas, "control"), + MouseEvent._from_ax_coords("motion_notify_event", ax, (50, 50)), + MouseEvent._from_ax_coords("button_press_event", ax, (50, 50), 1), + MouseEvent._from_ax_coords("motion_notify_event", ax, (75, 50)), + MouseEvent._from_ax_coords("button_release_event", ax, (75, 50), 1), + KeyEvent("key_release_event", ax.figure.canvas, "control"), + *polygon_place_vertex(ax, (50, 150)), + *polygon_place_vertex(ax, (75, 50)), ] check_selector(event_sequence, expected_result, 1) # Move first two vertices at once before completing the polygon. expected_result = [(50, 75), (150, 75), (50, 150)] event_sequence = [ - *polygon_place_vertex(50, 50), - *polygon_place_vertex(150, 50), - ('on_key_press', dict(key='shift')), - ('onmove', dict(xdata=100, ydata=100)), - ('press', dict(xdata=100, ydata=100)), - ('onmove', dict(xdata=100, ydata=125)), - ('release', dict(xdata=100, ydata=125)), - ('on_key_release', dict(key='shift')), - *polygon_place_vertex(50, 150), - *polygon_place_vertex(50, 75), + *polygon_place_vertex(ax, (50, 50)), + *polygon_place_vertex(ax, (150, 50)), + KeyEvent("key_press_event", ax.figure.canvas, "shift"), + MouseEvent._from_ax_coords("motion_notify_event", ax, (100, 100)), + MouseEvent._from_ax_coords("button_press_event", ax, (100, 100), 1), + MouseEvent._from_ax_coords("motion_notify_event", ax, (100, 125)), + MouseEvent._from_ax_coords("button_release_event", ax, (100, 125), 1), + KeyEvent("key_release_event", ax.figure.canvas, "shift"), + *polygon_place_vertex(ax, (50, 150)), + *polygon_place_vertex(ax, (50, 75)), ] check_selector(event_sequence, expected_result, 1) # Move first vertex after completing the polygon. - expected_result = [(75, 50), (150, 50), (50, 150)] + expected_result = [(85, 50), (150, 50), (50, 150)] event_sequence = [ - *polygon_place_vertex(50, 50), - *polygon_place_vertex(150, 50), - *polygon_place_vertex(50, 150), - *polygon_place_vertex(50, 50), - ('onmove', dict(xdata=50, ydata=50)), - ('press', dict(xdata=50, ydata=50)), - ('onmove', dict(xdata=75, ydata=50)), - ('release', dict(xdata=75, ydata=50)), + *polygon_place_vertex(ax, (60, 50)), + *polygon_place_vertex(ax, (150, 50)), + *polygon_place_vertex(ax, (50, 150)), + *polygon_place_vertex(ax, (60, 50)), + MouseEvent._from_ax_coords("motion_notify_event", ax, (60, 50)), + MouseEvent._from_ax_coords("button_press_event", ax, (60, 50), 1), + MouseEvent._from_ax_coords("motion_notify_event", ax, (85, 50)), + MouseEvent._from_ax_coords("button_release_event", ax, (85, 50), 1), ] check_selector(event_sequence, expected_result, 2) # Move all vertices after completing the polygon. expected_result = [(75, 75), (175, 75), (75, 175)] event_sequence = [ - *polygon_place_vertex(50, 50), - *polygon_place_vertex(150, 50), - *polygon_place_vertex(50, 150), - *polygon_place_vertex(50, 50), - ('on_key_press', dict(key='shift')), - ('onmove', dict(xdata=100, ydata=100)), - ('press', dict(xdata=100, ydata=100)), - ('onmove', dict(xdata=125, ydata=125)), - ('release', dict(xdata=125, ydata=125)), - ('on_key_release', dict(key='shift')), + *polygon_place_vertex(ax, (50, 50)), + *polygon_place_vertex(ax, (150, 50)), + *polygon_place_vertex(ax, (50, 150)), + *polygon_place_vertex(ax, (50, 50)), + KeyEvent("key_press_event", ax.figure.canvas, "shift"), + MouseEvent._from_ax_coords("motion_notify_event", ax, (100, 100)), + MouseEvent._from_ax_coords("button_press_event", ax, (100, 100), 1), + MouseEvent._from_ax_coords("motion_notify_event", ax, (125, 125)), + MouseEvent._from_ax_coords("button_release_event", ax, (125, 125), 1), + KeyEvent("key_release_event", ax.figure.canvas, "shift"), ] check_selector(event_sequence, expected_result, 2) # Try to move a vertex and move all before placing any vertices. expected_result = [(50, 50), (150, 50), (50, 150)] event_sequence = [ - ('on_key_press', dict(key='control')), - ('onmove', dict(xdata=100, ydata=100)), - ('press', dict(xdata=100, ydata=100)), - ('onmove', dict(xdata=125, ydata=125)), - ('release', dict(xdata=125, ydata=125)), - ('on_key_release', dict(key='control')), - ('on_key_press', dict(key='shift')), - ('onmove', dict(xdata=100, ydata=100)), - ('press', dict(xdata=100, ydata=100)), - ('onmove', dict(xdata=125, ydata=125)), - ('release', dict(xdata=125, ydata=125)), - ('on_key_release', dict(key='shift')), - *polygon_place_vertex(50, 50), - *polygon_place_vertex(150, 50), - *polygon_place_vertex(50, 150), - *polygon_place_vertex(50, 50), + KeyEvent("key_press_event", ax.figure.canvas, "control"), + MouseEvent._from_ax_coords("motion_notify_event", ax, (100, 100)), + MouseEvent._from_ax_coords("button_press_event", ax, (100, 100), 1), + MouseEvent._from_ax_coords("motion_notify_event", ax, (125, 125)), + MouseEvent._from_ax_coords("button_release_event", ax, (125, 125), 1), + KeyEvent("key_release_event", ax.figure.canvas, "control"), + KeyEvent("key_press_event", ax.figure.canvas, "shift"), + MouseEvent._from_ax_coords("motion_notify_event", ax, (100, 100)), + MouseEvent._from_ax_coords("button_press_event", ax, (100, 100), 1), + MouseEvent._from_ax_coords("motion_notify_event", ax, (125, 125)), + MouseEvent._from_ax_coords("button_release_event", ax, (125, 125), 1), + KeyEvent("key_release_event", ax.figure.canvas, "shift"), + *polygon_place_vertex(ax, (50, 50)), + *polygon_place_vertex(ax, (150, 50)), + *polygon_place_vertex(ax, (50, 150)), + *polygon_place_vertex(ax, (50, 50)), ] check_selector(event_sequence, expected_result, 1) # Try to place vertex out-of-bounds, then reset, and start a new polygon. expected_result = [(50, 50), (150, 50), (50, 150)] event_sequence = [ - *polygon_place_vertex(50, 50), - *polygon_place_vertex(250, 50), - ('on_key_press', dict(key='escape')), - ('on_key_release', dict(key='escape')), - *polygon_place_vertex(50, 50), - *polygon_place_vertex(150, 50), - *polygon_place_vertex(50, 150), - *polygon_place_vertex(50, 50), + *polygon_place_vertex(ax, (50, 50)), + *polygon_place_vertex(ax, (250, 50)), + KeyEvent("key_press_event", ax.figure.canvas, "escape"), + KeyEvent("key_release_event", ax.figure.canvas, "escape"), + *polygon_place_vertex(ax, (50, 50)), + *polygon_place_vertex(ax, (150, 50)), + *polygon_place_vertex(ax, (50, 150)), + *polygon_place_vertex(ax, (50, 50)), ] check_selector(event_sequence, expected_result, 1) @@ -1510,15 +1500,13 @@ def test_polygon_selector_set_props_handle_props(ax, draw_bounding_box): handle_props=dict(alpha=0.5), draw_bounding_box=draw_bounding_box) - event_sequence = [ - *polygon_place_vertex(50, 50), - *polygon_place_vertex(150, 50), - *polygon_place_vertex(50, 150), - *polygon_place_vertex(50, 50), - ] - - for (etype, event_args) in event_sequence: - do_event(tool, etype, **event_args) + for event in [ + *polygon_place_vertex(ax, (50, 50)), + *polygon_place_vertex(ax, (150, 50)), + *polygon_place_vertex(ax, (50, 150)), + *polygon_place_vertex(ax, (50, 50)), + ]: + event._process() artist = tool._selection_artist assert artist.get_color() == 'b' @@ -1549,17 +1537,17 @@ def test_rect_visibility(fig_test, fig_ref): # Change the order that the extra point is inserted in @pytest.mark.parametrize('idx', [1, 2, 3]) @pytest.mark.parametrize('draw_bounding_box', [False, True]) -def test_polygon_selector_remove(idx, draw_bounding_box): +def test_polygon_selector_remove(ax, idx, draw_bounding_box): verts = [(50, 50), (150, 50), (50, 150)] - event_sequence = [polygon_place_vertex(*verts[0]), - polygon_place_vertex(*verts[1]), - polygon_place_vertex(*verts[2]), + event_sequence = [polygon_place_vertex(ax, verts[0]), + polygon_place_vertex(ax, verts[1]), + polygon_place_vertex(ax, verts[2]), # Finish the polygon - polygon_place_vertex(*verts[0])] + polygon_place_vertex(ax, verts[0])] # Add an extra point - event_sequence.insert(idx, polygon_place_vertex(200, 200)) + event_sequence.insert(idx, polygon_place_vertex(ax, (200, 200))) # Remove the extra point - event_sequence.append(polygon_remove_vertex(200, 200)) + event_sequence.append(polygon_remove_vertex(ax, (200, 200))) # Flatten list of lists event_sequence = functools.reduce(operator.iadd, event_sequence, []) check_polygon_selector(event_sequence, verts, 2, @@ -1567,14 +1555,14 @@ def test_polygon_selector_remove(idx, draw_bounding_box): @pytest.mark.parametrize('draw_bounding_box', [False, True]) -def test_polygon_selector_remove_first_point(draw_bounding_box): +def test_polygon_selector_remove_first_point(ax, draw_bounding_box): verts = [(50, 50), (150, 50), (50, 150)] event_sequence = [ - *polygon_place_vertex(*verts[0]), - *polygon_place_vertex(*verts[1]), - *polygon_place_vertex(*verts[2]), - *polygon_place_vertex(*verts[0]), - *polygon_remove_vertex(*verts[0]), + *polygon_place_vertex(ax, verts[0]), + *polygon_place_vertex(ax, verts[1]), + *polygon_place_vertex(ax, verts[2]), + *polygon_place_vertex(ax, verts[0]), + *polygon_remove_vertex(ax, verts[0]), ] check_polygon_selector(event_sequence, verts[1:], 2, draw_bounding_box=draw_bounding_box) @@ -1584,20 +1572,20 @@ def test_polygon_selector_remove_first_point(draw_bounding_box): def test_polygon_selector_redraw(ax, draw_bounding_box): verts = [(50, 50), (150, 50), (50, 150)] event_sequence = [ - *polygon_place_vertex(*verts[0]), - *polygon_place_vertex(*verts[1]), - *polygon_place_vertex(*verts[2]), - *polygon_place_vertex(*verts[0]), + *polygon_place_vertex(ax, verts[0]), + *polygon_place_vertex(ax, verts[1]), + *polygon_place_vertex(ax, verts[2]), + *polygon_place_vertex(ax, verts[0]), # Polygon completed, now remove first two verts. - *polygon_remove_vertex(*verts[1]), - *polygon_remove_vertex(*verts[2]), + *polygon_remove_vertex(ax, verts[1]), + *polygon_remove_vertex(ax, verts[2]), # At this point the tool should be reset so we can add more vertices. - *polygon_place_vertex(*verts[1]), + *polygon_place_vertex(ax, verts[1]), ] tool = widgets.PolygonSelector(ax, draw_bounding_box=draw_bounding_box) - for (etype, event_args) in event_sequence: - do_event(tool, etype, **event_args) + for event in event_sequence: + event._process() # After removing two verts, only one remains, and the # selector should be automatically reset assert tool.verts == verts[0:2] @@ -1615,14 +1603,13 @@ def test_polygon_selector_verts_setter(fig_test, fig_ref, draw_bounding_box): ax_ref = fig_ref.add_subplot() tool_ref = widgets.PolygonSelector(ax_ref, draw_bounding_box=draw_bounding_box) - event_sequence = [ - *polygon_place_vertex(*verts[0]), - *polygon_place_vertex(*verts[1]), - *polygon_place_vertex(*verts[2]), - *polygon_place_vertex(*verts[0]), - ] - for (etype, event_args) in event_sequence: - do_event(tool_ref, etype, **event_args) + for event in [ + *polygon_place_vertex(ax_ref, verts[0]), + *polygon_place_vertex(ax_ref, verts[1]), + *polygon_place_vertex(ax_ref, verts[2]), + *polygon_place_vertex(ax_ref, verts[0]), + ]: + event._process() def test_polygon_selector_box(ax): @@ -1630,40 +1617,29 @@ def test_polygon_selector_box(ax): ax.set(xlim=(-10, 50), ylim=(-10, 50)) verts = [(20, 0), (0, 20), (20, 40), (40, 20)] event_sequence = [ - *polygon_place_vertex(*verts[0]), - *polygon_place_vertex(*verts[1]), - *polygon_place_vertex(*verts[2]), - *polygon_place_vertex(*verts[3]), - *polygon_place_vertex(*verts[0]), + *polygon_place_vertex(ax, verts[0]), + *polygon_place_vertex(ax, verts[1]), + *polygon_place_vertex(ax, verts[2]), + *polygon_place_vertex(ax, verts[3]), + *polygon_place_vertex(ax, verts[0]), ] # Create selector tool = widgets.PolygonSelector(ax, draw_bounding_box=True) - for (etype, event_args) in event_sequence: - do_event(tool, etype, **event_args) - - # In order to trigger the correct callbacks, trigger events on the canvas - # instead of the individual tools - t = ax.transData - canvas = ax.get_figure(root=True).canvas + for event in event_sequence: + event._process() # Scale to half size using the top right corner of the bounding box - MouseEvent( - "button_press_event", canvas, *t.transform((40, 40)), 1)._process() - MouseEvent( - "motion_notify_event", canvas, *t.transform((20, 20)))._process() - MouseEvent( - "button_release_event", canvas, *t.transform((20, 20)), 1)._process() + MouseEvent._from_ax_coords("button_press_event", ax, (40, 40), 1)._process() + MouseEvent._from_ax_coords("motion_notify_event", ax, (20, 20))._process() + MouseEvent._from_ax_coords("button_release_event", ax, (20, 20), 1)._process() np.testing.assert_allclose( tool.verts, [(10, 0), (0, 10), (10, 20), (20, 10)]) # Move using the center of the bounding box - MouseEvent( - "button_press_event", canvas, *t.transform((10, 10)), 1)._process() - MouseEvent( - "motion_notify_event", canvas, *t.transform((30, 30)))._process() - MouseEvent( - "button_release_event", canvas, *t.transform((30, 30)), 1)._process() + MouseEvent._from_ax_coords("button_press_event", ax, (10, 10), 1)._process() + MouseEvent._from_ax_coords("motion_notify_event", ax, (30, 30))._process() + MouseEvent._from_ax_coords("button_release_event", ax, (30, 30), 1)._process() np.testing.assert_allclose( tool.verts, [(30, 20), (20, 30), (30, 40), (40, 30)]) @@ -1671,10 +1647,8 @@ def test_polygon_selector_box(ax): np.testing.assert_allclose( tool._box.extents, (20.0, 40.0, 20.0, 40.0)) - MouseEvent( - "button_press_event", canvas, *t.transform((30, 20)), 3)._process() - MouseEvent( - "button_release_event", canvas, *t.transform((30, 20)), 3)._process() + MouseEvent._from_ax_coords("button_press_event", ax, (30, 20), 3)._process() + MouseEvent._from_ax_coords("button_release_event", ax, (30, 20), 3)._process() np.testing.assert_allclose( tool.verts, [(20, 30), (30, 40), (40, 30)]) np.testing.assert_allclose( @@ -1687,9 +1661,9 @@ def test_polygon_selector_clear_method(ax): for result in ([(50, 50), (150, 50), (50, 150), (50, 50)], [(50, 50), (100, 50), (50, 150), (50, 50)]): - for x, y in result: - for etype, event_args in polygon_place_vertex(x, y): - do_event(tool, etype, **event_args) + for xy in result: + for event in polygon_place_vertex(ax, xy): + event._process() artist = tool._selection_artist @@ -1720,11 +1694,7 @@ def test_MultiCursor(horizOn, vertOn): assert len(multi.vlines) == 2 assert len(multi.hlines) == 2 - # mock a motion_notify_event - # Can't use `do_event` as that helper requires the widget - # to have a single .ax attribute. - event = mock_event(ax1, xdata=.5, ydata=.25) - multi.onmove(event) + MouseEvent._from_ax_coords("motion_notify_event", ax1, (.5, .25))._process() # force a draw + draw event to exercise clear fig.canvas.draw() @@ -1742,8 +1712,7 @@ def test_MultiCursor(horizOn, vertOn): # After toggling settings, the opposite lines should be visible after move. multi.horizOn = not multi.horizOn multi.vertOn = not multi.vertOn - event = mock_event(ax1, xdata=.5, ydata=.25) - multi.onmove(event) + MouseEvent._from_ax_coords("motion_notify_event", ax1, (.5, .25))._process() assert len([line for line in multi.vlines if line.get_visible()]) == ( 0 if vertOn else 2) assert len([line for line in multi.hlines if line.get_visible()]) == ( @@ -1751,8 +1720,7 @@ def test_MultiCursor(horizOn, vertOn): # test a move event in an Axes not part of the MultiCursor # the lines in ax1 and ax2 should not have moved. - event = mock_event(ax3, xdata=.75, ydata=.75) - multi.onmove(event) + MouseEvent._from_ax_coords("motion_notify_event", ax3, (.75, .75))._process() for l in multi.vlines: assert l.get_xdata() == (.5, .5) for l in multi.hlines: diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index 79c7baba9bd1..6c17daebd271 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -13,7 +13,6 @@ from matplotlib import cm from matplotlib import colors as mcolors, patches as mpatch from matplotlib.testing.decorators import image_comparison, check_figures_equal -from matplotlib.testing.widgets import mock_event from matplotlib.collections import LineCollection, PolyCollection from matplotlib.patches import Circle, PathPatch from matplotlib.path import Path @@ -2012,11 +2011,11 @@ def test_rotate(style): ax.figure.canvas.draw() # drag mouse to change orientation - ax._button_press( - mock_event(ax, button=MouseButton.LEFT, xdata=0, ydata=0)) - ax._on_move( - mock_event(ax, button=MouseButton.LEFT, - xdata=s*dx*ax._pseudo_w, ydata=s*dy*ax._pseudo_h)) + MouseEvent._from_ax_coords( + "button_press_event", ax, (0, 0), MouseButton.LEFT)._process() + MouseEvent._from_ax_coords( + "motion_notify_event", ax, (s*dx*ax._pseudo_w, s*dy*ax._pseudo_h), + MouseButton.LEFT)._process() ax.figure.canvas.draw() c = np.sqrt(3)/2 @@ -2076,10 +2075,10 @@ def convert_lim(dmin, dmax): z_center0, z_range0 = convert_lim(*ax.get_zlim3d()) # move mouse diagonally to pan along all axis. - ax._button_press( - mock_event(ax, button=MouseButton.MIDDLE, xdata=0, ydata=0)) - ax._on_move( - mock_event(ax, button=MouseButton.MIDDLE, xdata=1, ydata=1)) + MouseEvent._from_ax_coords( + "button_press_event", ax, (0, 0), MouseButton.MIDDLE)._process() + MouseEvent._from_ax_coords( + "motion_notify_event", ax, (1, 1), MouseButton.MIDDLE)._process() x_center, x_range = convert_lim(*ax.get_xlim3d()) y_center, y_range = convert_lim(*ax.get_ylim3d()) @@ -2553,11 +2552,10 @@ def test_on_move_vertical_axis(vertical_axis: str) -> None: ax.get_figure().canvas.draw() proj_before = ax.get_proj() - event_click = mock_event(ax, button=MouseButton.LEFT, xdata=0, ydata=1) - ax._button_press(event_click) - - event_move = mock_event(ax, button=MouseButton.LEFT, xdata=0.5, ydata=0.8) - ax._on_move(event_move) + MouseEvent._from_ax_coords( + "button_press_event", ax, (0, 1), MouseButton.LEFT)._process() + MouseEvent._from_ax_coords( + "motion_notify_event", ax, (.5, .8), MouseButton.LEFT)._process() assert ax._axis_names.index(vertical_axis) == ax._vertical_axis