aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeremy Katz <jeremy@panix.com>2016-08-10 21:41:19 -0700
committerShawn Rutledge <shawn.rutledge@qt.io>2016-12-06 15:41:55 +0000
commit485e9fdf486446fb13012debe10137739b96d8ca (patch)
tree3ce5cc917264fef78ba49d7ded7fbf41e8385928
parentab54d0cab57121055914ff9a750f5ad975fe7525 (diff)
Add touch event support to qmltest
[ChangeLog][QuickTest] Add support for simulating touch events from TestCase. Task-number: QTBUG-23083 Change-Id: Ic045e00a91b8270b6f08d398323e06b576615e79 Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
-rw-r--r--src/imports/testlib/TestCase.qml103
-rw-r--r--src/imports/testlib/main.cpp1
-rw-r--r--src/imports/testlib/toucheventsequence.qdoc110
-rw-r--r--src/qmltest/qmltest.pro2
-rw-r--r--src/qmltest/quicktestevent.cpp93
-rw-r--r--src/qmltest/quicktestevent_p.h24
-rw-r--r--tests/auto/qmltest/events/tst_touch.qml182
7 files changed, 514 insertions, 1 deletions
diff --git a/src/imports/testlib/TestCase.qml b/src/imports/testlib/TestCase.qml
index d22ec7c44f..a874fd4fe2 100644
--- a/src/imports/testlib/TestCase.qml
+++ b/src/imports/testlib/TestCase.qml
@@ -1317,6 +1317,109 @@ Item {
qtest_fail("window not shown", 2)
}
+ /*!
+ \qmlmethod TouchEventSequence TestCase::touchEvent(object item)
+
+ \since 5.9
+
+ Begins a sequence of touch events through a simulated QTouchDevice::TouchScreen.
+ Events are delivered to the window containing \a item.
+
+ The returned object is used to enumerate events to be delivered through a single
+ QTouchEvent. Touches are delivered to the window containing the TestCase unless
+ otherwise specified.
+
+ \code
+ Rectangle {
+ width: 640; height: 480
+
+ MultiPointTouchArea {
+ id: area
+ anchors.fill: parent
+
+ property bool touched: false
+
+ onPressed: touched = true
+ }
+
+ TestCase {
+ name: "ItemTests"
+ when: area.pressed
+ id: test1
+
+ function test_touch() {
+ var touch = touchEvent(area);
+ touch.press(0, area, 10, 10);
+ touch.commit();
+ verify(area.touched);
+ }
+ }
+ }
+ \endcode
+
+ \sa TouchEventSequence::press(), TouchEventSequence::move(), TouchEventSequence::release(), TouchEventSequence::stationary(), TouchEventSequence::commit(), QTouchDevice::TouchScreen
+ */
+
+ function touchEvent(item) {
+ if (!item)
+ qtest_fail("No item given to touchEvent", 1)
+
+ return {
+ _defaultItem: item,
+ _sequence: qtest_events.touchEvent(item),
+
+ press: function (id, target, x, y) {
+ if (!target)
+ target = this._defaultItem;
+ if (id === undefined)
+ qtest_fail("No id given to TouchEventSequence::press", 1);
+ if (x === undefined)
+ x = target.width / 2;
+ if (y === undefined)
+ y = target.height / 2;
+ this._sequence.press(id, target, x, y);
+ return this;
+ },
+
+ move: function (id, target, x, y) {
+ if (!target)
+ target = this._defaultItem;
+ if (id === undefined)
+ qtest_fail("No id given to TouchEventSequence::move", 1);
+ if (x === undefined)
+ x = target.width / 2;
+ if (y === undefined)
+ y = target.height / 2;
+ this._sequence.move(id, target, x, y);
+ return this;
+ },
+
+ stationary: function (id) {
+ if (id === undefined)
+ qtest_fail("No id given to TouchEventSequence::stationary", 1);
+ this._sequence.stationary(id);
+ return this;
+ },
+
+ release: function (id, target, x, y) {
+ if (!target)
+ target = this._defaultItem;
+ if (id === undefined)
+ qtest_fail("No id given to TouchEventSequence::release", 1);
+ if (x === undefined)
+ x = target.width / 2;
+ if (y === undefined)
+ y = target.height / 2;
+ this._sequence.release(id, target, x, y);
+ return this;
+ },
+
+ commit: function () {
+ this._sequence.commit();
+ return this;
+ }
+ };
+ }
// Functions that can be overridden in subclasses for init/cleanup duties.
/*!
diff --git a/src/imports/testlib/main.cpp b/src/imports/testlib/main.cpp
index 4e2bd919e9..3c28000e35 100644
--- a/src/imports/testlib/main.cpp
+++ b/src/imports/testlib/main.cpp
@@ -158,6 +158,7 @@ public:
qmlRegisterType<QuickTestResult, 1>(uri,1,1,"TestResult");
qmlRegisterType<QuickTestEvent>(uri,1,0,"TestEvent");
qmlRegisterType<QuickTestUtil>(uri,1,0,"TestUtil");
+ qmlRegisterType<QQuickTouchEventSequence>();
}
};
diff --git a/src/imports/testlib/toucheventsequence.qdoc b/src/imports/testlib/toucheventsequence.qdoc
new file mode 100644
index 0000000000..f85a1cd4f9
--- /dev/null
+++ b/src/imports/testlib/toucheventsequence.qdoc
@@ -0,0 +1,110 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 Jeremy Katz
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the documentation of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:FDL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Free Documentation License Usage
+** Alternatively, this file may be used under the terms of the GNU Free
+** Documentation License version 1.3 as published by the Free Software
+** Foundation and appearing in the file included in the packaging of
+** this file. Please review the following information to ensure
+** the GNU Free Documentation License version 1.3 requirements
+** will be met: http://www.gnu.org/copyleft/fdl.html.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*!
+ \qmltype TouchEventSequence
+ \inqmlmodule QtTest
+ \ingroup qtquicktest
+ \brief TouchEventSequence is used to build and dispatch touch events
+ for testing.
+
+ \since 5.9
+
+ A TouchEventSequence is created by calling \l [QML] {TestCase::touchEvent()}{TestCase.touchEvent()}.
+ The type can not be directly instantiated. Each method provided by the type returns
+ the same object, allowing chained calls.
+
+ For example:
+ \code
+ touchEvent(item).press(0).commit();
+ \endcode
+ is equivalent to:
+ \code
+ var sequence = touchEvent(item);
+ sequence.press(0);
+ sequence.commit();
+ \endcode
+
+ Events are delivered to the window which contains the item specified in touchEvent.
+
+ \sa TestCase::touchEvent(), QTest::QTouchEventSequence
+*/
+
+/*!
+ \qmlmethod TouchEventSequence TouchEventSequence::press(int touchId, object item, real x = item.width / 2, real y = item.height / 2)
+
+ Creates a new point identified as \a touchId, at the point indicated by \a x and \a y relative to \a item.
+ Further use of the same touch point should maintain the same touchId.
+
+ Item defaults to the value provided via touchEvent().
+ X and y default to the midpoint of the item.
+*/
+
+/*!
+ \qmlmethod TouchEventSequence TouchEventSequence::move(int touchId, object item, real x = item.width / 2, real y = item.height / 2)
+
+ Moves \a touchId to the point indicated by \a x and \a y relative to \a item.
+
+ Item defaults to the value provided via touchEvent().
+ X and y default to the midpoint of the item.
+*/
+
+/*!
+ \qmlmethod TouchEventSequence TouchEventSequence::release(int touchId, object item, real x = item.width / 2, real y = item.height / 2)
+
+ Removes \a touchId at the point indicated by \a x and \a y relative to \a item.
+
+ Item defaults to the value provided via touchEvent().
+ X and y default to the midpoint of the item.
+*/
+
+/*!
+ \qmlmethod TouchEventSequence TouchEventSequence::stationary(int touchId)
+
+ Indicates that \a touchId is present but otherwise unchanged from prior events.
+*/
+
+/*!
+ \qmlmethod TouchEventSequence TouchEventSequence::commit()
+
+ Sends the touch event composed by prior use of press(), move(), release(), and stationary().
+ Following commit's return, the TouchEventSequence can be used to compose a new event.
+
+ \code
+ var sequence = touchEvent(target);
+ // Touch the middle of target with 1 point
+ sequence.press(1);
+ sequence.commit();
+
+ // Begin a new event
+ // Move the point to target's upper left corner
+ sequence.move(1, target, 0, 0);
+ sequence.commit();
+ \endcode
+
+ Commit is automatically invoked when the TouchEventSequence object is destroyed.
+*/
diff --git a/src/qmltest/qmltest.pro b/src/qmltest/qmltest.pro
index 9852861334..d13e162ff4 100644
--- a/src/qmltest/qmltest.pro
+++ b/src/qmltest/qmltest.pro
@@ -2,7 +2,7 @@ TARGET = QtQuickTest
DEFINES += QT_NO_URL_CAST_FROM_STRING QT_NO_FOREACH
QT = core testlib-private
-QT_PRIVATE = quick qml-private gui core-private
+QT_PRIVATE = quick qml-private gui core-private gui-private
# Testlib is only a private dependency, which results in our users not
# inheriting testlibs's MODULE_CONFIG transitively. Make it explicit.
diff --git a/src/qmltest/quicktestevent.cpp b/src/qmltest/quicktestevent.cpp
index 26f0521cdf..4b8ade761f 100644
--- a/src/qmltest/quicktestevent.cpp
+++ b/src/qmltest/quicktestevent.cpp
@@ -42,6 +42,7 @@
#include <QtQml/qqml.h>
#include <QtQuick/qquickitem.h>
#include <QtQuick/qquickwindow.h>
+#include <qpa/qwindowsysteminterface.h>
QT_BEGIN_NAMESPACE
@@ -347,4 +348,96 @@ QWindow *QuickTestEvent::activeWindow()
return eventWindow();
}
+QQuickTouchEventSequence::QQuickTouchEventSequence(QuickTestEvent *testEvent, QObject *item)
+ : QObject(testEvent)
+ , m_sequence(QTest::touchEvent(testEvent->eventWindow(item), testEvent->touchDevice()))
+ , m_testEvent(testEvent)
+{
+}
+
+QObject *QQuickTouchEventSequence::press(int touchId, QObject *item, qreal x, qreal y)
+{
+ QWindow *view = m_testEvent->eventWindow(item);
+ if (view) {
+ QPointF pos(x, y);
+ QQuickItem *quickItem = qobject_cast<QQuickItem *>(item);
+ if (quickItem) {
+ pos = quickItem->mapToScene(pos);
+ }
+ m_sequence.press(touchId, pos.toPoint(), view);
+ }
+ return this;
+}
+
+QObject *QQuickTouchEventSequence::move(int touchId, QObject *item, qreal x, qreal y)
+{
+ QWindow *view = m_testEvent->eventWindow(item);
+ if (view) {
+ QPointF pos(x, y);
+ QQuickItem *quickItem = qobject_cast<QQuickItem *>(item);
+ if (quickItem) {
+ pos = quickItem->mapToScene(pos);
+ }
+ m_sequence.move(touchId, pos.toPoint(), view);
+ }
+ return this;
+}
+
+QObject *QQuickTouchEventSequence::release(int touchId, QObject *item, qreal x, qreal y)
+{
+ QWindow *view = m_testEvent->eventWindow(item);
+ if (view) {
+ QPointF pos(x, y);
+ QQuickItem *quickItem = qobject_cast<QQuickItem *>(item);
+ if (quickItem) {
+ pos = quickItem->mapToScene(pos);
+ }
+ m_sequence.release(touchId, pos.toPoint(), view);
+ }
+ return this;
+}
+
+QObject *QQuickTouchEventSequence::stationary(int touchId)
+{
+ m_sequence.stationary(touchId);
+ return this;
+}
+
+QObject *QQuickTouchEventSequence::commit()
+{
+ m_sequence.commit();
+ return this;
+}
+
+/*!
+ Return a simulated touchscreen, creating one if necessary
+
+ \internal
+*/
+
+QTouchDevice *QuickTestEvent::touchDevice()
+{
+ static QTouchDevice *device(nullptr);
+
+ if (!device) {
+ device = new QTouchDevice;
+ device->setType(QTouchDevice::TouchScreen);
+ QWindowSystemInterface::registerTouchDevice(device);
+ }
+ return device;
+}
+
+/*!
+ Creates a new QQuickTouchEventSequence.
+
+ If valid, \a item determines the QWindow that touch events are sent to.
+ Test code should use touchEvent() from the QML TestCase type.
+
+ \internal
+*/
+QQuickTouchEventSequence *QuickTestEvent::touchEvent(QObject *item)
+{
+ return new QQuickTouchEventSequence(this, item);
+}
+
QT_END_NAMESPACE
diff --git a/src/qmltest/quicktestevent_p.h b/src/qmltest/quicktestevent_p.h
index 1adf8f3317..c37d03ad0f 100644
--- a/src/qmltest/quicktestevent_p.h
+++ b/src/qmltest/quicktestevent_p.h
@@ -54,8 +54,28 @@
#include <QtQuickTest/quicktestglobal.h>
#include <QtCore/qobject.h>
#include <QtGui/QWindow>
+#include <QtTest/qtesttouch.h>
+
QT_BEGIN_NAMESPACE
+class QuickTestEvent;
+class Q_QUICK_TEST_EXPORT QQuickTouchEventSequence : public QObject
+{
+ Q_OBJECT
+public:
+ explicit QQuickTouchEventSequence(QuickTestEvent *testEvent, QObject *item = nullptr);
+public slots:
+ QObject* press(int touchId, QObject *item, qreal x, qreal y);
+ QObject* move(int touchId, QObject *item, qreal x, qreal y);
+ QObject* release(int touchId, QObject *item, qreal x, qreal y);
+ QObject* stationary(int touchId);
+ QObject* commit();
+
+private:
+ QTest::QTouchEventSequence m_sequence;
+ QuickTestEvent * const m_testEvent;
+};
+
class Q_QUICK_TEST_EXPORT QuickTestEvent : public QObject
{
Q_OBJECT
@@ -91,9 +111,13 @@ public Q_SLOTS:
int modifiers, int xDelta, int yDelta, int delay);
#endif
+ QQuickTouchEventSequence *touchEvent(QObject *item = nullptr);
private:
QWindow *eventWindow(QObject *item = 0);
QWindow *activeWindow();
+ QTouchDevice *touchDevice();
+
+ friend class QQuickTouchEventSequence;
};
QT_END_NAMESPACE
diff --git a/tests/auto/qmltest/events/tst_touch.qml b/tests/auto/qmltest/events/tst_touch.qml
new file mode 100644
index 0000000000..5b209a6d0b
--- /dev/null
+++ b/tests/auto/qmltest/events/tst_touch.qml
@@ -0,0 +1,182 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 Jeremy Katz
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+import QtQuick.Window 2.0
+import QtTest 1.0
+
+MultiPointTouchArea {
+ id: touchArea
+ width: 100
+ height: 100
+
+ SignalSpy {
+ id: touchUpdatedSpy
+ target: touchArea
+ signalName: "touchUpdated"
+ }
+
+ SignalSpy {
+ id: interiorSpy
+ target: interior
+ signalName: "touchUpdated"
+ }
+
+ MultiPointTouchArea {
+ id: interior
+ width: parent.width / 2
+ height: parent.height
+ anchors.right: parent.right
+ }
+
+ Window {
+ width: 100; height: 100
+
+ SignalSpy {
+ id: subWindowSpy
+ target: subWindowTouchArea
+ signalName: "touchUpdated"
+ }
+
+ MultiPointTouchArea {
+ id: subWindowTouchArea
+ anchors.fill: parent
+ }
+ }
+
+ TestCase {
+ when: windowShown
+ name: "touch"
+
+ function comparePoint(point, id, x, y) {
+ var retval = true;
+ var pointId = point.pointId & 0xFFFFFF; //strip device identifier
+ if (pointId !== id) {
+ warn("Unexpected pointId: " + pointId + ". Expected " + id);
+ retval = false;
+ }
+ if (point.x !== x) {
+ warn("Unexpected x: " + point.x + ". Expected " + x);
+ retval = false;
+ }
+ if (point.y !== y) {
+ warn("Unexpected y: " + point.y + ". Expected " + y);
+ retval = false;
+ }
+ return retval;
+ }
+
+ function cleanup() {
+ touchUpdatedSpy.clear();
+ interiorSpy.clear();
+ subWindowSpy.clear();
+ }
+
+ function test_secondWindow() {
+ var first = 1;
+ var sequence = touchEvent(subWindowTouchArea);
+ sequence.press(first, 0, 0, 0);
+ sequence.commit();
+ sequence.release(first, subWindowTouchArea, 0, 0)
+ sequence.commit();
+ compare(subWindowSpy.count, 2);
+ var touchPoint = subWindowSpy.signalArguments[0][0][0];
+ verify(comparePoint(touchPoint, first, 0, 0));
+ }
+
+ function initTestCase() {
+ waitForRendering(touchArea) // when: windowShown may be insufficient
+ }
+
+ function test_childMapping() {
+ var sequence = touchEvent(touchArea);
+
+ var first = 1;
+ // Test mapping touches to a child item
+ sequence.press(first, interior, 0, 0);
+ sequence.commit();
+
+ // Map touches to the parent at the same point
+ sequence.move(first, touchArea, interior.x, interior.y);
+ sequence.commit();
+
+ sequence.release(first, touchArea, interior.x, interior.y);
+ sequence.commit();
+
+ compare(interiorSpy.count, 3);
+ verify(comparePoint(interiorSpy.signalArguments[0][0][0], first, 0, 0));
+ verify(comparePoint(interiorSpy.signalArguments[1][0][0], first, 0, 0));
+ }
+
+ function test_fullSequence() {
+ var sequence = touchEvent(touchArea);
+ verify(sequence);
+
+ var first = 1;
+ var second = 2;
+
+ sequence.press(first, null, 0, 0);
+ sequence.commit();
+ compare(touchUpdatedSpy.count, 1);
+ var touchPoints = touchUpdatedSpy.signalArguments[0][0];
+ compare(touchPoints.length, 1);
+ verify(comparePoint(touchPoints[0], first, 0, 0));
+
+ sequence.stationary(first);
+ sequence.press(second, null, 1, 0);
+ sequence.commit();
+ compare(touchUpdatedSpy.count, 2);
+ touchPoints = touchUpdatedSpy.signalArguments[1][0];
+ compare(touchPoints.length, 2);
+ verify(comparePoint(touchPoints[0], first, 0, 0));
+ verify(comparePoint(touchPoints[1], second, 1, 0));
+
+ sequence.release(first);
+ sequence.move(second, null, 1, 1);
+ sequence.commit();
+ compare(touchUpdatedSpy.count, 3);
+ touchPoints = touchUpdatedSpy.signalArguments[2][0];
+ compare(touchPoints.length, 1);
+ verify(comparePoint(touchPoints[0], second, 1, 1));
+
+ sequence.release(second, null, 0, 1);
+ sequence.commit();
+ compare(touchUpdatedSpy.count, 4);
+ touchPoints = touchUpdatedSpy.signalArguments[3][0];
+ compare(touchPoints.length, 0);
+ }
+
+ function test_simpleChain() {
+ var first = 1;
+ touchEvent(touchArea).press(first).commit().release(first).commit();
+ compare(touchUpdatedSpy.count, 2);
+ var touchPoint = touchUpdatedSpy.signalArguments[0][0][0];
+ verify(comparePoint(touchPoint, first, touchArea.width / 2, touchArea.height / 2));
+ }
+ }
+}