From 18d9def351847493211d87b4ebc9a43622698bf8 Mon Sep 17 00:00:00 2001 From: "Hendry, Adam" Date: Sun, 5 Jun 2022 20:22:53 -0700 Subject: [PATCH] fix(mousepress): replace `QTest` mouse actions `QTest` mouse actions `mouseClick` and `mousePress` do not release the mouse, causing `qtbot` to fail tests. Replace with `QtGui` mouse events. Fixes Issue #428 --- .gitignore | 4 ++ src/pytestqt/qt_compat.py | 3 +- src/pytestqt/qtbot.py | 108 +++++++++++++++++++++++++++++--------- tests/test_basics.py | 11 ++-- 4 files changed, 95 insertions(+), 31 deletions(-) diff --git a/.gitignore b/.gitignore index 2e797d0e..55800433 100644 --- a/.gitignore +++ b/.gitignore @@ -11,8 +11,12 @@ src/pytest_qt.egg-info .coverage /.cache /.venv +.venv/ /.eggs /.pytest_cache # auto-generated by setuptools_scm /src/pytestqt/_version.py + +# Ignore vscode +.vscode/ diff --git a/src/pytestqt/qt_compat.py b/src/pytestqt/qt_compat.py index 18be36fe..d41cb551 100644 --- a/src/pytestqt/qt_compat.py +++ b/src/pytestqt/qt_compat.py @@ -9,12 +9,11 @@ """ -from collections import namedtuple import os +from collections import namedtuple import pytest - VersionTuple = namedtuple("VersionTuple", "qt_api, qt_api_version, runtime, compiled") diff --git a/src/pytestqt/qtbot.py b/src/pytestqt/qtbot.py index 2e306583..02ea744c 100644 --- a/src/pytestqt/qtbot.py +++ b/src/pytestqt/qtbot.py @@ -1,16 +1,16 @@ import contextlib -import weakref import warnings +import weakref from pytestqt.exceptions import TimeoutError from pytestqt.qt_compat import qt_api from pytestqt.wait_signal import ( - SignalBlocker, - MultiSignalBlocker, - SignalEmittedSpy, - SignalEmittedError, CallbackBlocker, CallbackCalledTwiceError, + MultiSignalBlocker, + SignalBlocker, + SignalEmittedError, + SignalEmittedSpy, ) @@ -636,25 +636,85 @@ def keyToAscii(key): raise NotImplementedError("This method isn't available on PyQt5.") qt_api.QtTest.QTest.keyToAscii(key) - @staticmethod - def mouseClick(*args, **kwargs): - qt_api.QtTest.QTest.mouseClick(*args, **kwargs) - - @staticmethod - def mouseDClick(*args, **kwargs): - qt_api.QtTest.QTest.mouseDClick(*args, **kwargs) - - @staticmethod - def mouseMove(*args, **kwargs): - qt_api.QtTest.QTest.mouseMove(*args, **kwargs) - - @staticmethod - def mousePress(*args, **kwargs): - qt_api.QtTest.QTest.mousePress(*args, **kwargs) - - @staticmethod - def mouseRelease(*args, **kwargs): - qt_api.QtTest.QTest.mouseRelease(*args, **kwargs) + def mouseClick(self, widget, button, pos=None, modifiers=None): + if pos is None: + pos = widget.rect().center() + self.mouseMove(widget, pos) + self.mousePress(widget, button, pos, modifiers) + self.mouseRelease(widget, button, pos, modifiers) + + def mouseDClick(self, widget, button, pos=None, modifiers=None): + if pos is None: + pos = widget.rect().center() + self.mouseClick(widget, button, pos, modifiers) + self.mouseClick(widget, button, pos, modifiers) + + def mouseDrag(self, widget, button, pos1, pos2, modifiers=None): + self.mouseMove(widget, pos1) + self.mousePress(widget, pos1, button, modifiers) + self.mouseMove(widget, pos2, button, modifiers) + self.mouseRelease(widget, pos2, button, modifiers) + + def mouseMove(self, widget, pos, modifiers=None): + if isinstance(widget, qt_api.QtWidgets.QGraphicsView): + widget = widget.viewport() + if modifiers is None: + modifiers = qt_api.QtCore.Qt.KeyboardModifier.NoModifier + if isinstance(pos, qt_api.QtCore.QPoint): + pos = qt_api.QtCore.QPointF(pos) # PyQt6 requires `QPointF` + buttons = qt_api.QtCore.Qt.MouseButton.NoButton + # `QMouseEvent` does not accept keyword arguments. Results in `TypeError: + # not enough arguments`. Use positional instead. + event = qt_api.QtGui.QMouseEvent( + qt_api.QtCore.QEvent.Type.MouseMove, + pos, + qt_api.QtCore.Qt.MouseButton.NoButton, + buttons, + modifiers, + ) + qt_api.QtWidgets.QApplication.sendEvent(widget, event) + + def mousePress(self, widget, button, pos=None, modifiers=None): + if isinstance(widget, qt_api.QtWidgets.QGraphicsView): + widget = widget.viewport() + if modifiers is None: + modifiers = qt_api.QtCore.Qt.KeyboardModifier.NoModifier + if pos is None: + pos = widget.rect().center() + if isinstance(pos, qt_api.QtCore.QPoint): + pos = qt_api.QtCore.QPointF(pos) # PyQt6 requires `QPointF` + buttons = qt_api.QtCore.Qt.MouseButton.NoButton + # `QMouseEvent` does not accept keyword arguments. Results in `TypeError: + # not enough arguments`. Use positional instead. + event = qt_api.QtGui.QMouseEvent( + qt_api.QtCore.QEvent.Type.MouseButtonPress, + pos, + button, + buttons, + modifiers, + ) + qt_api.QtWidgets.QApplication.sendEvent(widget, event) + + def mouseRelease(self, widget, button, pos=None, modifiers=None): + if isinstance(widget, qt_api.QtWidgets.QGraphicsView): + widget = widget.viewport() + if modifiers is None: + modifiers = qt_api.QtCore.Qt.KeyboardModifier.NoModifier + if pos is None: + pos = widget.rect().center() + if isinstance(pos, qt_api.QtCore.QPoint): + pos = qt_api.QtCore.QPointF(pos) # PyQt6 requires `QPointF` + buttons = qt_api.QtCore.Qt.MouseButton.NoButton + # `QMouseEvent` does not accept keyword arguments. Results in `TypeError: + # not enough arguments`. Use positional instead. + event = qt_api.QtGui.QMouseEvent( + qt_api.QtCore.QEvent.Type.MouseButtonRelease, + pos, + button, + buttons, + modifiers, + ) + qt_api.QtWidgets.QApplication.sendEvent(widget, event) # provide easy access to exceptions to qtbot fixtures diff --git a/tests/test_basics.py b/tests/test_basics.py index 37d7cad4..43503605 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -1,7 +1,6 @@ import weakref import pytest - from pytestqt import qt_compat from pytestqt.qt_compat import qt_api @@ -165,7 +164,9 @@ def extract(mouse_event): event_recorder.registerEvent(qt_api.QtGui.QMouseEvent, extract) - qtbot.mousePress(event_recorder, qt_api.QtCore.Qt.MouseButton.LeftButton) + qtbot.mousePress( + widget=event_recorder, button=qt_api.QtCore.Qt.MouseButton.LeftButton + ) assert event_recorder.event_data == ( qt_api.QtCore.QEvent.Type.MouseButtonPress, qt_api.QtCore.Qt.MouseButton.LeftButton, @@ -173,9 +174,9 @@ def extract(mouse_event): ) qtbot.mousePress( - event_recorder, - qt_api.QtCore.Qt.MouseButton.RightButton, - qt_api.QtCore.Qt.KeyboardModifier.AltModifier, + widget=event_recorder, + button=qt_api.QtCore.Qt.MouseButton.RightButton, + modifiers=qt_api.QtCore.Qt.KeyboardModifier.AltModifier, ) assert event_recorder.event_data == ( qt_api.QtCore.QEvent.Type.MouseButtonPress,