1""" 2Basic Qt testing framework 3========================== 4""" 5import unittest 6import gc 7from typing import Callable, Any 8 9from AnyQt.QtWidgets import QApplication, QWidget 10from AnyQt.QtCore import ( 11 QCoreApplication, QTimer, QStandardPaths, QPoint, Qt, QMimeData 12) 13from AnyQt.QtGui import ( 14 QMouseEvent, QDragEnterEvent, QDropEvent, QDragMoveEvent, QDragLeaveEvent, 15 QContextMenuEvent 16) 17from AnyQt.QtTest import QTest 18from AnyQt.QtCore import PYQT_VERSION 19 20DEFAULT_TIMEOUT = 50 21 22 23class QCoreAppTestCase(unittest.TestCase): 24 _AppClass = QCoreApplication 25 app = None # type: QCoreApplication 26 27 __appdomain = "" 28 __appname = "" 29 30 @classmethod 31 def setUpClass(cls): 32 super(QCoreAppTestCase, cls).setUpClass() 33 QStandardPaths.setTestModeEnabled(True) 34 app = cls._AppClass.instance() 35 if app is None: 36 app = cls._AppClass([]) 37 cls.app = app 38 cls.__appname = cls.app.applicationName() 39 cls.__appdomain = cls.app.organizationDomain() 40 cls.app.setApplicationName("orangecanvas.testing") 41 cls.app.setOrganizationDomain("biolab.si") 42 43 def setUp(self): 44 super(QCoreAppTestCase, self).setUp() 45 46 def tearDown(self): 47 super(QCoreAppTestCase, self).tearDown() 48 49 @classmethod 50 def tearDownClass(cls): 51 gc.collect() 52 cls.app.setApplicationName(cls.__appname) 53 cls.app.setOrganizationDomain(cls.__appdomain) 54 cls.app.sendPostedEvents(None, 0) 55 # Keep app instance alive between tests with PyQt5 5.14.0 and later 56 if PYQT_VERSION <= 0x050e00: 57 cls.app = None 58 super(QCoreAppTestCase, cls).tearDownClass() 59 QStandardPaths.setTestModeEnabled(False) 60 61 @classmethod 62 def qWait(cls, timeout=DEFAULT_TIMEOUT): 63 QTest.qWait(timeout) 64 65 @classmethod 66 def singleShot(cls, timeout: int, slot: 'Callable[[], Any]'): 67 QTimer.singleShot(timeout, slot) 68 69 70class QAppTestCase(QCoreAppTestCase): 71 _AppClass = QApplication 72 app = None # type: QApplication 73 74 75def mouseMove(widget, buttons, modifier=Qt.NoModifier, pos=QPoint(), delay=-1): 76 # type: (QWidget, Qt.MouseButtons, Qt.KeyboardModifier, QPoint, int) -> None 77 """ 78 Like QTest.mouseMove, but with `buttons` and `modifier` parameters. 79 80 Parameters 81 ---------- 82 widget : QWidget 83 buttons: Qt.MouseButtons 84 modifier : Qt.KeyboardModifiers 85 pos : QPoint 86 delay : int 87 """ 88 if pos.isNull(): 89 pos = widget.rect().center() 90 me = QMouseEvent( 91 QMouseEvent.MouseMove, pos, widget.mapToGlobal(pos), Qt.NoButton, 92 buttons, modifier 93 ) 94 if delay > 0: 95 QTest.qWait(delay) 96 97 QCoreApplication.sendEvent(widget, me) 98 99 100def contextMenu(widget: QWidget, pos: QPoint, delay=-1) -> None: 101 """ 102 Simulates a contextMenuEvent on the widget. 103 """ 104 ev = QContextMenuEvent( 105 QContextMenuEvent.Mouse, pos, widget.mapToGlobal(pos) 106 ) 107 if delay > 0: 108 QTest.qWait(delay) 109 QCoreApplication.sendEvent(widget, ev) 110 111 112def dragDrop( 113 widget: QWidget, mime: QMimeData, pos: QPoint = QPoint(), 114 action=Qt.CopyAction, buttons=Qt.LeftButton, modifiers=Qt.NoModifier 115) -> bool: 116 """ 117 Simulate a drag/drop interaction on the `widget`. 118 119 A `QDragEnterEvent`, `QDragMoveEvent` and `QDropEvent` are created and 120 dispatched to the `widget`. However if any of the `QDragEnterEvent` or 121 `QDragMoveEvent` are not accepted, a `QDragLeaveEvent` is dispatched 122 to 'reset' the widget state before this function returns `False` 123 124 Parameters 125 ---------- 126 widget: QWidget 127 The target widget. 128 mime: QMimeData 129 The mime data associated with the drag/drop. 130 pos: QPoint 131 Position of the drop 132 action: Qt.DropActions 133 Type of acceptable drop actions 134 buttons: Qt.MouseButtons: 135 Pressed mouse buttons. 136 modifiers: Qt.KeyboardModifiers 137 Pressed keyboard modifiers. 138 139 Returns 140 ------- 141 state: bool 142 Were the events accepted. 143 144 See Also 145 -------- 146 QDragEnterEvent, QDropEvent 147 """ 148 if pos.isNull(): 149 pos = widget.rect().center() 150 151 ev = QDragEnterEvent(pos, action, mime, buttons, modifiers) 152 ev.setAccepted(False) 153 QApplication.sendEvent(widget, ev) 154 155 ev = QDragMoveEvent(pos, action, mime, buttons, modifiers) 156 ev.setAccepted(False) 157 QApplication.sendEvent(widget, ev) 158 159 if not ev.isAccepted(): 160 QApplication.sendEvent(widget, QDragLeaveEvent()) 161 return False 162 163 ev = QDropEvent(pos, action, mime, buttons, modifiers) 164 ev.setAccepted(False) 165 QApplication.sendEvent(widget, ev) 166 return ev.isAccepted() 167 168 169def dragEnterLeave( 170 widget: QWidget, mime: QMimeData, pos=QPoint(), 171 action=Qt.CopyAction, buttons=Qt.LeftButton, modifiers=Qt.NoModifier 172) -> None: 173 """ 174 Simulate a drag/move/leave interaction on the `widget`. 175 176 A QDragEnterEvent, QDragMoveEvent and a QDragLeaveEvent are created 177 and dispatched to the widget. 178 """ 179 if pos.isNull(): 180 pos = widget.rect().center() 181 182 ev = QDragEnterEvent(pos, action, mime, buttons, modifiers) 183 ev.setAccepted(False) 184 QApplication.sendEvent(widget, ev) 185 186 ev = QDragMoveEvent( 187 pos, action, mime, buttons, modifiers, QDragMoveEvent.DragMove 188 ) 189 ev.setAccepted(False) 190 QApplication.sendEvent(widget, ev) 191 192 ev = QDragLeaveEvent() 193 ev.setAccepted(False) 194 QApplication.sendEvent(widget, ev) 195 return 196