aboutsummaryrefslogtreecommitdiffstats
path: root/src/imports/testlib
diff options
context:
space:
mode:
Diffstat (limited to 'src/imports/testlib')
-rw-r--r--src/imports/testlib/TestCase.qml258
-rw-r--r--src/imports/testlib/main.cpp1
-rw-r--r--src/imports/testlib/qmldir1
-rw-r--r--src/imports/testlib/toucheventsequence.qdoc110
4 files changed, 370 insertions, 0 deletions
diff --git a/src/imports/testlib/TestCase.qml b/src/imports/testlib/TestCase.qml
index d22ec7c44f..18c70e1169 100644
--- a/src/imports/testlib/TestCase.qml
+++ b/src/imports/testlib/TestCase.qml
@@ -206,6 +206,59 @@ import Qt.test.qtestroot 1.0
will fail. Use the \l when and windowShown properties to track
when the main window has been shown.
+ \section1 Managing Dynamically Created Test Objects
+
+ A typical pattern with QML tests is to
+ \l {Dynamic QML Object Creation from JavaScript}{dynamically create}
+ an item and then destroy it at the end of the test function:
+
+ \code
+ TestCase {
+ id: testCase
+ name: "MyTest"
+ when: windowShown
+
+ function test_click() {
+ var item = Qt.createQmlObject("import QtQuick 2.0; Item {}", testCase);
+ verify(item);
+
+ // Test item...
+
+ item.destroy();
+ }
+ }
+ \endcode
+
+ The problem with this pattern is that any failures in the test function
+ will cause the call to \c item.destroy() to be skipped, leaving the item
+ hanging around in the scene until the test case has finished. This can
+ result in interference with future tests; for example, by blocking input
+ events or producing unrelated debug output that makes it difficult to
+ follow the code's execution.
+
+ By calling \l createTemporaryQmlObject() instead, the object is guaranteed
+ to be destroyed at the end of the test function:
+
+ \code
+ TestCase {
+ id: testCase
+ name: "MyTest"
+ when: windowShown
+
+ function test_click() {
+ var item = createTemporaryQmlObject("import QtQuick 2.0; Item {}", testCase);
+ verify(item);
+
+ // Test item...
+
+ // Don't need to worry about destroying "item" here.
+ }
+ }
+ \endcode
+
+ For objects that are created via the \l {Component::}{createObject()} function
+ of \l Component, the \l createTemporaryObject() function can be used.
+
\sa {QtTest::SignalSpy}{SignalSpy}, {Qt Quick Test Reference Documentation}
*/
@@ -358,6 +411,8 @@ Item {
/*! \internal */
property var qtest_events: qtest_events_normal
TestEvent { id: qtest_events_normal }
+ /*! \internal */
+ property var qtest_temporaryObjects: []
/*!
\qmlmethod TestCase::fail(message = "")
@@ -461,6 +516,105 @@ Item {
throw new Error("QtQuickTest::fail")
}
+ /*!
+ \since 5.9
+ \qmlmethod object TestCase::createTemporaryQmlObject(string qml, object parent, string filePath)
+
+ This function dynamically creates a QML object from the given \a qml
+ string with the specified \a parent. The returned object will be
+ destroyed (if it was not already) after \l cleanup() has finished
+ executing, meaning that objects created with this function are
+ guaranteed to be destroyed after each test, regardless of whether or
+ not the tests fail.
+
+ If there was an error while creating the object, \c null will be
+ returned.
+
+ If \a filePath is specified, it will be used for error reporting for
+ the created object.
+
+ This function calls
+ \l {QtQml::Qt::createQmlObject()}{Qt.createQmlObject()} internally.
+
+ \sa {Managing Dynamically Created Test Objects}
+ */
+ function createTemporaryQmlObject(qml, parent, filePath) {
+ if (typeof qml !== "string") {
+ qtest_results.fail("First argument must be a string of QML; actual type is " + typeof qml,
+ util.callerFile(), util.callerLine());
+ throw new Error("QtQuickTest::fail");
+ }
+
+ if (!parent || typeof parent !== "object") {
+ qtest_results.fail("Second argument must be a valid parent object; actual type is " + typeof parent,
+ util.callerFile(), util.callerLine());
+ throw new Error("QtQuickTest::fail");
+ }
+
+ if (filePath !== undefined && typeof filePath !== "string") {
+ qtest_results.fail("Third argument must be a file path string; actual type is " + typeof filePath,
+ util.callerFile(), util.callerLine());
+ throw new Error("QtQuickTest::fail");
+ }
+
+ var object = Qt.createQmlObject(qml, parent, filePath);
+ qtest_temporaryObjects.push(object);
+ return object;
+ }
+
+ /*!
+ \since 5.9
+ \qmlmethod object TestCase::createTemporaryObject(Component component, object parent, object properties)
+
+ This function dynamically creates a QML object from the given
+ \a component with the specified optional \a parent and \a properties.
+ The returned object will be destroyed (if it was not already) after
+ \l cleanup() has finished executing, meaning that objects created with
+ this function are guaranteed to be destroyed after each test,
+ regardless of whether or not the tests fail.
+
+ If there was an error while creating the object, \c null will be
+ returned.
+
+ This function calls
+ \l {QtQml::Component::createObject()}{component.createObject()}
+ internally.
+
+ \sa {Managing Dynamically Created Test Objects}
+ */
+ function createTemporaryObject(component, parent, properties) {
+ if (typeof component !== "object") {
+ qtest_results.fail("First argument must be a Component; actual type is " + typeof component,
+ util.callerFile(), util.callerLine());
+ throw new Error("QtQuickTest::fail");
+ }
+
+ if (properties && typeof properties !== "object") {
+ qtest_results.fail("Third argument must be an object; actual type is " + typeof properties,
+ util.callerFile(), util.callerLine());
+ throw new Error("QtQuickTest::fail");
+ }
+
+ var object = component.createObject(parent, properties ? properties : ({}));
+ qtest_temporaryObjects.push(object);
+ return object;
+ }
+
+ /*!
+ \internal
+
+ Destroys all temporary objects that still exist.
+ */
+ function qtest_destroyTemporaryObjects() {
+ for (var i = 0; i < qtest_temporaryObjects.length; ++i) {
+ var temporaryObject = qtest_temporaryObjects[i];
+ // ### the typeof check can be removed when QTBUG-57749 is fixed
+ if (temporaryObject && typeof temporaryObject.destroy === "function")
+ temporaryObject.destroy();
+ }
+ qtest_temporaryObjects = [];
+ }
+
/*! \internal */
// Determine what is o.
// Discussions and reference: http://philrathe.com/articles/equiv
@@ -1317,6 +1471,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.
/*!
@@ -1389,6 +1646,7 @@ Item {
qtest_runInternal(prop, arg)
qtest_results.finishTestData()
qtest_runInternal("cleanup")
+ qtest_destroyTemporaryObjects()
qtest_results.finishTestDataCleanup()
// wait(0) will call processEvents() so objects marked for deletion
// in the test function will be deleted.
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/qmldir b/src/imports/testlib/qmldir
index 9da8ebb4be..e5757f6a88 100644
--- a/src/imports/testlib/qmldir
+++ b/src/imports/testlib/qmldir
@@ -3,4 +3,5 @@ plugin qmltestplugin
classname QTestQmlModule
typeinfo plugins.qmltypes
TestCase 1.0 TestCase.qml
+TestCase 1.2 TestCase.qml
SignalSpy 1.0 SignalSpy.qml
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.
+*/