diff options
author | Jeremy Katz <jeremy@panix.com> | 2016-08-10 21:41:19 -0700 |
---|---|---|
committer | Shawn Rutledge <shawn.rutledge@qt.io> | 2016-12-06 15:41:55 +0000 |
commit | 485e9fdf486446fb13012debe10137739b96d8ca (patch) | |
tree | 3ce5cc917264fef78ba49d7ded7fbf41e8385928 | |
parent | ab54d0cab57121055914ff9a750f5ad975fe7525 (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.qml | 103 | ||||
-rw-r--r-- | src/imports/testlib/main.cpp | 1 | ||||
-rw-r--r-- | src/imports/testlib/toucheventsequence.qdoc | 110 | ||||
-rw-r--r-- | src/qmltest/qmltest.pro | 2 | ||||
-rw-r--r-- | src/qmltest/quicktestevent.cpp | 93 | ||||
-rw-r--r-- | src/qmltest/quicktestevent_p.h | 24 | ||||
-rw-r--r-- | tests/auto/qmltest/events/tst_touch.qml | 182 |
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)); + } + } +} |