diff options
-rw-r--r-- | src/imports/testlib/TestCase.qml | 160 | ||||
-rw-r--r-- | src/imports/testlib/qmldir | 1 | ||||
-rw-r--r-- | tests/auto/qmltest/selftests/tst_createTemporaryObject.qml | 113 |
3 files changed, 274 insertions, 0 deletions
diff --git a/src/imports/testlib/TestCase.qml b/src/imports/testlib/TestCase.qml index a874fd4fe2..ad7533c550 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,110 @@ 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 \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 (!parent && parent !== null) { + qtest_results.fail("Second argument must be a parent object or null; actual type is " + typeof parent, + 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]; + if (temporaryObject) + temporaryObject.destroy(); + } + qtest_temporaryObjects = []; + } + /*! \internal */ // Determine what is o. // Discussions and reference: http://philrathe.com/articles/equiv @@ -1492,6 +1651,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/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/tests/auto/qmltest/selftests/tst_createTemporaryObject.qml b/tests/auto/qmltest/selftests/tst_createTemporaryObject.qml new file mode 100644 index 0000000000..c4912c7388 --- /dev/null +++ b/tests/auto/qmltest/selftests/tst_createTemporaryObject.qml @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** 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.8 +import QtQuick.Window 2.2 +import QtTest 1.2 + +TestCase { + id: testCase + name: "tst_createTemporaryObject" + width: 100 + height: 100 + when: windowShown + + property var createdObjectNames: [] + + function verifyNoChildren() { + for (var i = 0; i < createdObjectNames.length; ++i) { + verify(!findChild(testCase, createdObjectNames[i])); + } + } + + function init() { + // The items are destroyed after cleanup(), so we check here after every test, + // and once for the last test in cleanupTestCase(). + verifyNoChildren(); + } + + function cleanupTestCase() { + verifyNoChildren(); + } + + function test_fromQml_data() { + return [ + { tag: "QtObject", qml: "import QtQml 2.0; QtObject {}" }, + { tag: "Item", qml: "import QtQuick 2.0; Item {}" }, + ]; + } + + function test_fromQml(data) { + var object = createTemporaryQmlObject(data.qml, testCase); + verify(object); + + object.objectName = data.tag + "FromQml"; + compare(findChild(testCase, object.objectName), object); + + createdObjectNames.push(object.objectName); + } + + Component { + id: objectComponent + + QtObject {} + } + + Component { + id: itemComponent + + Item {} + } + + Component { + id: windowComponent + + Window {} + } + + function test_fromComponent_data() { + return [ + { tag: "QtObject", component: objectComponent }, + { tag: "Item", component: itemComponent }, + { tag: "Window", component: windowComponent }, + ]; + } + + function test_fromComponent(data) { + var object = createTemporaryObject(data.component, testCase); + verify(object); + + object.objectName = data.tag + "FromComponent"; + compare(findChild(testCase, object.objectName), object); + + if (object.hasOwnProperty("contentItem")) + object.contentItem.objectName = "WindowContentItemFromComponent"; + + createdObjectNames.push(object.objectName); + } +} |