Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test drag and drop behavior in PyQt?

Tags:

testing

qt

pyqt

I want to unittest drag and drop for our widgets. At the moment, I instantiate a QDragEnterEvent, but this is discouraged with a big warning on the Qt documentation, because it relies on the Qt library internal state. In fact, I get segfaults that appear to be due to a violation of this Warning.

Given this premise, how can one test drag and drop behavior?

like image 560
Stefano Borini Avatar asked Jun 10 '14 15:06

Stefano Borini


1 Answers

If using Unix we can use QTest, however to get a cross-platform solution, we can implement a solution where we circumvent Qt.

Using QTest

Although the Qt documentation for drag and drop says that it will not block the main event loop, a closer look at QDrag.exec will reveal that this is not true for Windows.

The call to QTest.mousePress causes the test to block until the mouse is physically moved by the user.

I got around this in Linux by using a timer to schedule the mouse move and release:

def testDragAndDrop(self):
    QtCore.QTimer.singleShot(100, self.dropIt)
    QtTest.QTest.mousePress(dragFromWidget, QtCore.Qt.LeftButton)

    # check for desired behaviour after drop
    assert something

def dropIt(self):
    QtTest.QTest.mouseMove(dropToWidget)
    QtTest.QTest.mouseRelease(dropToWidget, QtCore.Qt.LeftButton, delay=15)

For this solution, it is necessary to include a delay in the mouseRelease call, and to have called show on your widget.

Note that I have verified this works using pyqt4 and Python 2.7 on Fedora 20

Cross-Platform

You can use the mouse manipulation methods from the PyUserInput package. Put the mouse interaction in separate thread to avoid the locking up of the Qt main event loop. We can do this since we are not using Qt at all in our mouse control. Make sure that you have called show on the widgets you are dragging to/from.

from __future__ import division
import sys, time, threading
import numpy as np

from PyQt4 import QtGui, QtCore, QtTest
from pymouse import PyMouse
...

def mouseDrag(source, dest, rate=1000):
    """Simulate a mouse visible mouse drag from source to dest, rate is pixels/second"""
    mouse = PyMouse()
    mouse.press(*source)

    # smooth move from source to dest
    npoints = int(np.sqrt((dest[0]-source[0])**2 + (dest[1]-source[1])**2 ) / (rate/1000))
    for i in range(npoints):
        x = int(source[0] + ((dest[0]-source[0])/npoints)*i)
        y = int(source[1] + ((dest[1]-source[1])/npoints)*i)
        mouse.move(x,y)
        time.sleep(0.001)

    mouse.release(*dest)

def center(widget):
    midpoint = QtCore.QPoint(widget.width()/2, widget.height()/2)
    return widget.mapToGlobal(midpoint)

def testDragAndDrop(self):
    # grab the center of the widgets
    fromPos = center(dragFromWidget)
    toPos = center(dropToWidget)

    dragThread = threading.Thread(target=mouseDrag, args=((fromPos.x(),fromPos.y()), (toPos.x(), toPos.y())))
    dragThread.start()
    # cannot join, use non-blocking wait
    while dragThread.is_alive():
        QtTest.QTest.qWait(1000)

    # check that the drop had the desired effect
    assert dropToWidget.hasItemCount() > 0

Note I have tested this using PyQt4 and Python 2.7 on Fedora and Windows 7

like image 53
hackyday Avatar answered Sep 26 '22 05:09

hackyday