Skip to content

Commit 2b9f263

Browse files
committed
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.
1 parent bff64cc commit 2b9f263

File tree

5 files changed

+235
-231
lines changed

5 files changed

+235
-231
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
``testing.widgets.mock_event`` and ``testing.widgets.do_event``
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
... are deprecated. Directly construct Event objects (typically `.MouseEvent`
4+
or `.KeyEvent`) and pass them to ``canvas.callbacks.process()`` instead.

lib/matplotlib/backend_bases.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,6 +1411,23 @@ def __init__(self, name, canvas, x, y, button=None, key=None,
14111411
self.step = step
14121412
self.dblclick = dblclick
14131413

1414+
@classmethod
1415+
def _from_ax_coords(cls, name, ax, xy, *args, **kwargs):
1416+
"""
1417+
Generate a synthetic event at a given axes coordinate.
1418+
1419+
This method is intended for creating events during testing. The event
1420+
can be emitted by calling its ``_process()`` method.
1421+
1422+
args and kwargs are mapped to `.MouseEvent.__init__` parameters,
1423+
starting with `button`.
1424+
"""
1425+
x, y = ax.transData.transform(xy)
1426+
event = cls(name, ax.figure.canvas, x, y, *args, **kwargs)
1427+
event.inaxes = ax
1428+
event.xdata, event.ydata = xy # Force exact xy to avoid fp roundtrip issues.
1429+
return event
1430+
14141431
def __str__(self):
14151432
return (f"{self.name}: "
14161433
f"xy=({self.x}, {self.y}) xydata=({self.xdata}, {self.ydata}) "
@@ -1503,6 +1520,22 @@ def __init__(self, name, canvas, key, x=0, y=0, guiEvent=None):
15031520
super().__init__(name, canvas, x, y, guiEvent=guiEvent)
15041521
self.key = key
15051522

1523+
@classmethod
1524+
def _from_ax_coords(cls, name, ax, xy, key, *args, **kwargs):
1525+
"""
1526+
Generate a synthetic event at a given axes coordinate.
1527+
1528+
This method is intended for creating events during testing. The event
1529+
can be emitted by calling its ``_process()`` method.
1530+
"""
1531+
# Separate from MouseEvent._from_ax_coords instead of being defined in the base
1532+
# class, due to different parameter order in the constructor signature.
1533+
x, y = ax.transData.transform(xy)
1534+
event = cls(name, ax.figure.canvas, key, x, y, *args, **kwargs)
1535+
event.inaxes = ax
1536+
event.xdata, event.ydata = xy # Force exact xy to avoid fp roundtrip issues.
1537+
return event
1538+
15061539

15071540
# Default callback for key events.
15081541
def _key_handler(event):

lib/matplotlib/testing/widgets.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
from unittest import mock
1010

11+
from matplotlib import _api
12+
from matplotlib.backend_bases import MouseEvent, KeyEvent
1113
import matplotlib.pyplot as plt
1214

1315

@@ -24,6 +26,7 @@ def noop(*args, **kwargs):
2426
pass
2527

2628

29+
@_api.deprecated("3.11", alternative="MouseEvent or KeyEvent")
2730
def mock_event(ax, button=1, xdata=0, ydata=0, key=None, step=1):
2831
r"""
2932
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):
6568
return event
6669

6770

71+
@_api.deprecated("3.11", alternative="callbacks.process(event)")
6872
def do_event(tool, etype, button=1, xdata=0, ydata=0, key=None, step=1):
6973
"""
7074
Trigger an event on the given tool.
@@ -105,15 +109,12 @@ def click_and_drag(tool, start, end, key=None):
105109
An optional key that is pressed during the whole operation
106110
(see also `.KeyEvent`).
107111
"""
108-
if key is not None:
109-
# Press key
110-
do_event(tool, 'on_key_press', xdata=start[0], ydata=start[1],
111-
button=1, key=key)
112+
ax = tool.ax
113+
if key is not None: # Press key
114+
KeyEvent._from_ax_coords("key_press_event", ax, start, key)._process()
112115
# Click, move, and release mouse
113-
do_event(tool, 'press', xdata=start[0], ydata=start[1], button=1)
114-
do_event(tool, 'onmove', xdata=end[0], ydata=end[1], button=1)
115-
do_event(tool, 'release', xdata=end[0], ydata=end[1], button=1)
116-
if key is not None:
117-
# Release key
118-
do_event(tool, 'on_key_release', xdata=end[0], ydata=end[1],
119-
button=1, key=key)
116+
MouseEvent._from_ax_coords("button_press_event", ax, start, 1)._process()
117+
MouseEvent._from_ax_coords("motion_notify_event", ax, end, 1)._process()
118+
MouseEvent._from_ax_coords("button_release_event", ax, end, 1)._process()
119+
if key is not None: # Release key
120+
KeyEvent._from_ax_coords("key_release_event", ax, end, key)._process()

0 commit comments

Comments
 (0)