| /**************************************************************************** |
| ** |
| ** 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$ |
| ** |
| ****************************************************************************/ |
| #include <qtest.h> |
| |
| #include <QSignalSpy> |
| |
| #include <QtQml/QQmlContext> |
| #include <QtQml/qqmlengine.h> |
| #include <QtQml/qqmlcomponent.h> |
| #include <QtQml/qqmlincubator.h> |
| #include <QtQuick/qquickview.h> |
| #include <private/qquickloader_p.h> |
| #include <private/qquickwindowmodule_p.h> |
| #include "testhttpserver.h" |
| #include "../../shared/util.h" |
| #include "../shared/geometrytestutil.h" |
| #include <QQmlApplicationEngine> |
| |
| Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests") |
| |
| class SlowComponent : public QQmlComponent |
| { |
| Q_OBJECT |
| public: |
| SlowComponent() { |
| QTest::qSleep(500); |
| } |
| }; |
| |
| class PeriodicIncubationController : public QObject, |
| public QQmlIncubationController |
| { |
| public: |
| PeriodicIncubationController() {} |
| |
| void start() { startTimer(20); } |
| |
| bool incubated = false; |
| |
| protected: |
| virtual void timerEvent(QTimerEvent *) { |
| incubateFor(15); |
| } |
| |
| virtual void incubatingObjectCountChanged(int count) { |
| if (count) |
| incubated = true; |
| } |
| }; |
| |
| class tst_QQuickLoader : public QQmlDataTest |
| |
| { |
| Q_OBJECT |
| public: |
| tst_QQuickLoader(); |
| |
| private slots: |
| void sourceOrComponent(); |
| void sourceOrComponent_data(); |
| void clear(); |
| void urlToComponent(); |
| void componentToUrl(); |
| void anchoredLoader(); |
| void sizeLoaderToItem(); |
| void sizeItemToLoader(); |
| void noResize(); |
| void networkRequestUrl(); |
| void failNetworkRequest(); |
| void networkComponent(); |
| void active(); |
| void initialPropertyValues_data(); |
| void initialPropertyValues(); |
| void initialPropertyValuesBinding(); |
| void initialPropertyValuesError_data(); |
| void initialPropertyValuesError(); |
| |
| void deleteComponentCrash(); |
| void nonItem(); |
| void vmeErrors(); |
| void creationContext(); |
| void QTBUG_16928(); |
| void implicitSize(); |
| void QTBUG_17114(); |
| void asynchronous_data(); |
| void asynchronous(); |
| void asynchronous_clear(); |
| void simultaneousSyncAsync(); |
| void asyncToSync1(); |
| void asyncToSync2(); |
| void loadedSignal(); |
| |
| void parented(); |
| void sizeBound(); |
| void QTBUG_30183(); |
| void transientWindow(); |
| void nestedTransientWindow(); |
| |
| void sourceComponentGarbageCollection(); |
| |
| void bindings(); |
| void parentErrors(); |
| |
| void rootContext(); |
| void sourceURLKeepComponent(); |
| |
| void statusChangeOnlyEmittedOnce(); |
| }; |
| |
| Q_DECLARE_METATYPE(QList<QQmlError>) |
| |
| tst_QQuickLoader::tst_QQuickLoader() |
| { |
| qmlRegisterType<SlowComponent>("LoaderTest", 1, 0, "SlowComponent"); |
| qRegisterMetaType<QList<QQmlError>>(); |
| } |
| |
| void tst_QQuickLoader::sourceOrComponent() |
| { |
| QFETCH(QString, sourceOrComponent); |
| QFETCH(QString, sourceDefinition); |
| QFETCH(QUrl, sourceUrl); |
| QFETCH(QString, errorString); |
| |
| bool error = !errorString.isEmpty(); |
| if (error) |
| QTest::ignoreMessage(QtWarningMsg, errorString.toUtf8().constData()); |
| |
| QQmlEngine engine; |
| QQmlComponent component(&engine); |
| component.setData(QByteArray( |
| "import QtQuick 2.0\n" |
| "Loader {\n" |
| " property int onItemChangedCount: 0\n" |
| " property int onSourceChangedCount: 0\n" |
| " property int onSourceComponentChangedCount: 0\n" |
| " property int onStatusChangedCount: 0\n" |
| " property int onProgressChangedCount: 0\n" |
| " property int onLoadedCount: 0\n") |
| + sourceDefinition.toUtf8() |
| + QByteArray( |
| " onItemChanged: onItemChangedCount += 1\n" |
| " onSourceChanged: onSourceChangedCount += 1\n" |
| " onSourceComponentChanged: onSourceComponentChangedCount += 1\n" |
| " onStatusChanged: onStatusChangedCount += 1\n" |
| " onProgressChanged: onProgressChangedCount += 1\n" |
| " onLoaded: onLoadedCount += 1\n" |
| "}") |
| , dataDirectoryUrl()); |
| |
| QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(component.create())); |
| QVERIFY(loader != nullptr); |
| QCOMPARE(loader->item() == nullptr, error); |
| QCOMPARE(loader->source(), sourceUrl); |
| QCOMPARE(loader->progress(), 1.0); |
| |
| QCOMPARE(loader->status(), error ? QQuickLoader::Error : QQuickLoader::Ready); |
| QCOMPARE(static_cast<QQuickItem*>(loader.data())->childItems().count(), error ? 0: 1); |
| |
| if (!error) { |
| bool sourceComponentIsChildOfLoader = false; |
| for (int ii = 0; ii < loader->children().size(); ++ii) { |
| QQmlComponent *c = qobject_cast<QQmlComponent*>(loader->children().at(ii)); |
| if (c && c == loader->sourceComponent()) { |
| sourceComponentIsChildOfLoader = true; |
| } |
| } |
| QVERIFY(sourceComponentIsChildOfLoader); |
| } |
| |
| if (sourceOrComponent == "component") { |
| QCOMPARE(loader->property("onSourceComponentChangedCount").toInt(), 1); |
| QCOMPARE(loader->property("onSourceChangedCount").toInt(), 0); |
| } else { |
| QCOMPARE(loader->property("onSourceComponentChangedCount").toInt(), 0); |
| QCOMPARE(loader->property("onSourceChangedCount").toInt(), 1); |
| } |
| QCOMPARE(loader->property("onStatusChangedCount").toInt(), 1); |
| QCOMPARE(loader->property("onProgressChangedCount").toInt(), 1); |
| |
| QCOMPARE(loader->property("onItemChangedCount").toInt(), 1); |
| QCOMPARE(loader->property("onLoadedCount").toInt(), error ? 0 : 1); |
| } |
| |
| void tst_QQuickLoader::sourceOrComponent_data() |
| { |
| QTest::addColumn<QString>("sourceOrComponent"); |
| QTest::addColumn<QString>("sourceDefinition"); |
| QTest::addColumn<QUrl>("sourceUrl"); |
| QTest::addColumn<QString>("errorString"); |
| |
| QTest::newRow("source") << "source" << "source: 'Rect120x60.qml'\n" << testFileUrl("Rect120x60.qml") << ""; |
| QTest::newRow("source with subdir") << "source" << "source: 'subdir/Test.qml'\n" << testFileUrl("subdir/Test.qml") << ""; |
| QTest::newRow("source with encoded subdir literal") << "source" << "source: 'subdir%2fTest.qml'\n" << testFileUrl("subdir/Test.qml") << ""; |
| QTest::newRow("source with encoded subdir optimized binding") << "source" << "source: 'subdir' + '%2fTest.qml'\n" << testFileUrl("subdir/Test.qml") << ""; |
| QTest::newRow("source with encoded subdir binding") << "source" << "source: encodeURIComponent('subdir/Test.qml')\n" << testFileUrl("subdir/Test.qml") << ""; |
| QTest::newRow("sourceComponent") << "component" << "Component { id: comp; Rectangle { width: 100; height: 50 } }\n sourceComponent: comp\n" << QUrl() << ""; |
| QTest::newRow("invalid source") << "source" << "source: 'IDontExist.qml'\n" << testFileUrl("IDontExist.qml") |
| << QString(testFileUrl("IDontExist.qml").toString() + ": No such file or directory"); |
| } |
| |
| void tst_QQuickLoader::clear() |
| { |
| QQmlEngine engine; |
| |
| { |
| QQmlComponent component(&engine); |
| component.setData(QByteArray( |
| "import QtQuick 2.0\n" |
| " Loader { id: loader\n" |
| " source: 'Rect120x60.qml'\n" |
| " Timer { interval: 200; running: true; onTriggered: loader.source = '' }\n" |
| " }") |
| , dataDirectoryUrl()); |
| QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(component.create())); |
| QVERIFY(loader != nullptr); |
| QVERIFY(loader->item()); |
| QCOMPARE(loader->progress(), 1.0); |
| QCOMPARE(static_cast<QQuickItem*>(loader.data())->childItems().count(), 1); |
| |
| QTRY_VERIFY(!loader->item()); |
| QCOMPARE(loader->progress(), 0.0); |
| QCOMPARE(loader->status(), QQuickLoader::Null); |
| QCOMPARE(static_cast<QQuickItem*>(loader.data())->childItems().count(), 0); |
| } |
| { |
| QQmlComponent component(&engine, testFileUrl("/SetSourceComponent.qml")); |
| QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(component.create())); |
| QVERIFY(item); |
| |
| QQuickLoader *loader = qobject_cast<QQuickLoader*>(item->QQuickItem::childItems().at(0)); |
| QVERIFY(loader); |
| QVERIFY(loader->item()); |
| QCOMPARE(loader->progress(), 1.0); |
| QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 1); |
| |
| loader->setSourceComponent(nullptr); |
| |
| QVERIFY(!loader->item()); |
| QCOMPARE(loader->progress(), 0.0); |
| QCOMPARE(loader->status(), QQuickLoader::Null); |
| QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 0); |
| } |
| { |
| QQmlComponent component(&engine, testFileUrl("/SetSourceComponent.qml")); |
| QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(component.create())); |
| QVERIFY(item); |
| |
| QQuickLoader *loader = qobject_cast<QQuickLoader*>(item->QQuickItem::childItems().at(0)); |
| QVERIFY(loader); |
| QVERIFY(loader->item()); |
| QCOMPARE(loader->progress(), 1.0); |
| QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 1); |
| |
| QMetaObject::invokeMethod(item.data(), "clear"); |
| |
| QVERIFY(!loader->item()); |
| QCOMPARE(loader->progress(), 0.0); |
| QCOMPARE(loader->status(), QQuickLoader::Null); |
| QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 0); |
| } |
| } |
| |
| void tst_QQuickLoader::urlToComponent() |
| { |
| QQmlEngine engine; |
| QQmlComponent component(&engine); |
| component.setData(QByteArray("import QtQuick 2.0\n" |
| "Loader {\n" |
| " id: loader\n" |
| " Component { id: myComp; Rectangle { width: 10; height: 10 } }\n" |
| " source: \"Rect120x60.qml\"\n" |
| " Timer { interval: 100; running: true; onTriggered: loader.sourceComponent = myComp }\n" |
| "}" ) |
| , dataDirectoryUrl()); |
| QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(component.create())); |
| QTest::qWait(200); |
| QTRY_VERIFY(loader != nullptr); |
| QVERIFY(loader->item()); |
| QCOMPARE(loader->progress(), 1.0); |
| QCOMPARE(static_cast<QQuickItem*>(loader.data())->childItems().count(), 1); |
| QCOMPARE(loader->width(), 10.0); |
| QCOMPARE(loader->height(), 10.0); |
| } |
| |
| void tst_QQuickLoader::componentToUrl() |
| { |
| QQmlEngine engine; |
| QQmlComponent component(&engine, testFileUrl("/SetSourceComponent.qml")); |
| QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(component.create())); |
| QVERIFY(item); |
| |
| QQuickLoader *loader = qobject_cast<QQuickLoader*>(item->QQuickItem::childItems().at(0)); |
| QVERIFY(loader); |
| QVERIFY(loader->item()); |
| QCOMPARE(loader->progress(), 1.0); |
| QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 1); |
| |
| loader->setSource(testFileUrl("/Rect120x60.qml")); |
| QVERIFY(loader->item()); |
| QCOMPARE(loader->progress(), 1.0); |
| QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 1); |
| QCOMPARE(loader->width(), 120.0); |
| QCOMPARE(loader->height(), 60.0); |
| } |
| |
| void tst_QQuickLoader::anchoredLoader() |
| { |
| QQmlEngine engine; |
| QQmlComponent component(&engine, testFileUrl("/AnchoredLoader.qml")); |
| QScopedPointer<QQuickItem> rootItem(qobject_cast<QQuickItem*>(component.create())); |
| QVERIFY(rootItem != nullptr); |
| QQuickItem *loader = rootItem->findChild<QQuickItem*>("loader"); |
| QQuickItem *sourceElement = rootItem->findChild<QQuickItem*>("sourceElement"); |
| |
| QVERIFY(loader != nullptr); |
| QVERIFY(sourceElement != nullptr); |
| |
| QCOMPARE(rootItem->width(), 300.0); |
| QCOMPARE(rootItem->height(), 200.0); |
| |
| QCOMPARE(loader->width(), 300.0); |
| QCOMPARE(loader->height(), 200.0); |
| |
| QCOMPARE(sourceElement->width(), 300.0); |
| QCOMPARE(sourceElement->height(), 200.0); |
| } |
| |
| void tst_QQuickLoader::sizeLoaderToItem() |
| { |
| QQmlEngine engine; |
| QQmlComponent component(&engine, testFileUrl("/SizeToItem.qml")); |
| QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(component.create())); |
| QVERIFY(loader != nullptr); |
| QCOMPARE(loader->width(), 120.0); |
| QCOMPARE(loader->height(), 60.0); |
| |
| // Check resize |
| QQuickItem *rect = qobject_cast<QQuickItem*>(loader->item()); |
| QVERIFY(rect); |
| rect->setWidth(150); |
| rect->setHeight(45); |
| QCOMPARE(loader->width(), 150.0); |
| QCOMPARE(loader->height(), 45.0); |
| |
| // Check explicit width |
| loader->setWidth(200.0); |
| QCOMPARE(loader->width(), 200.0); |
| QCOMPARE(rect->width(), 200.0); |
| rect->setWidth(100.0); // when rect changes ... |
| QCOMPARE(rect->width(), 100.0); // ... it changes |
| QCOMPARE(loader->width(), 200.0); // ... but loader stays the same |
| |
| // Check explicit height |
| loader->setHeight(200.0); |
| QCOMPARE(loader->height(), 200.0); |
| QCOMPARE(rect->height(), 200.0); |
| rect->setHeight(100.0); // when rect changes ... |
| QCOMPARE(rect->height(), 100.0); // ... it changes |
| QCOMPARE(loader->height(), 200.0); // ... but loader stays the same |
| |
| // Switch mode |
| loader->setWidth(180); |
| loader->setHeight(30); |
| QCOMPARE(rect->width(), 180.0); |
| QCOMPARE(rect->height(), 30.0); |
| } |
| |
| void tst_QQuickLoader::sizeItemToLoader() |
| { |
| QQmlEngine engine; |
| QQmlComponent component(&engine, testFileUrl("/SizeToLoader.qml")); |
| QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(component.create())); |
| QVERIFY(loader != nullptr); |
| QCOMPARE(loader->width(), 200.0); |
| QCOMPARE(loader->height(), 80.0); |
| |
| QQuickItem *rect = qobject_cast<QQuickItem*>(loader->item()); |
| QVERIFY(rect); |
| QCOMPARE(rect->width(), 200.0); |
| QCOMPARE(rect->height(), 80.0); |
| |
| // Check resize |
| QSizeChangeListener sizeListener(rect); |
| const QSizeF size(180, 30); |
| loader->setSize(size); |
| QVERIFY2(!sizeListener.isEmpty(), "There should be at least one signal about the size changed"); |
| for (const QSizeF sizeOnGeometryChanged : sizeListener) { |
| // Check that we have the correct size on all signals |
| QCOMPARE(sizeOnGeometryChanged, size); |
| } |
| QCOMPARE(rect->width(), size.width()); |
| QCOMPARE(rect->height(), size.height()); |
| |
| // Switch mode |
| loader->resetWidth(); // reset explicit size |
| loader->resetHeight(); |
| rect->setWidth(160); |
| rect->setHeight(45); |
| QCOMPARE(loader->width(), 160.0); |
| QCOMPARE(loader->height(), 45.0); |
| } |
| |
| void tst_QQuickLoader::noResize() |
| { |
| QQmlEngine engine; |
| QQmlComponent component(&engine, testFileUrl("/NoResize.qml")); |
| QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(component.create())); |
| QVERIFY(item != nullptr); |
| QCOMPARE(item->width(), 200.0); |
| QCOMPARE(item->height(), 80.0); |
| } |
| |
| void tst_QQuickLoader::networkRequestUrl() |
| { |
| ThreadedTestHTTPServer server(dataDirectory()); |
| |
| QQmlEngine engine; |
| QQmlComponent component(&engine); |
| const QString qml = "import QtQuick 2.0\nLoader { property int signalCount : 0; source: \"" + server.baseUrl().toString() + "/Rect120x60.qml\"; onLoaded: signalCount += 1 }"; |
| component.setData(qml.toUtf8(), testFileUrl("../dummy.qml")); |
| if (component.isError()) |
| qDebug() << component.errors(); |
| QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(component.create())); |
| QVERIFY(loader != nullptr); |
| |
| QTRY_COMPARE(loader->status(), QQuickLoader::Ready); |
| |
| QVERIFY(loader->item()); |
| QCOMPARE(loader->progress(), 1.0); |
| QCOMPARE(loader->property("signalCount").toInt(), 1); |
| QCOMPARE(static_cast<QQuickItem*>(loader.data())->childItems().count(), 1); |
| } |
| |
| /* XXX Component waits until all dependencies are loaded. Is this actually possible? */ |
| void tst_QQuickLoader::networkComponent() |
| { |
| ThreadedTestHTTPServer server(dataDirectory(), TestHTTPServer::Delay); |
| |
| QQmlEngine engine; |
| QQmlComponent component(&engine); |
| const QString qml = "import QtQuick 2.0\n" |
| "import \"" + server.baseUrl().toString() + "/\" as NW\n" |
| "Item {\n" |
| " Component { id: comp; NW.Rect120x60 {} }\n" |
| " Loader { sourceComponent: comp } }"; |
| component.setData(qml.toUtf8(), dataDirectory()); |
| // The component may be loaded synchronously or asynchronously, so we cannot test for |
| // status == Loading here. Also, it makes no sense to instruct the server to send here |
| // because in the synchronous case we're already done loading. |
| QTRY_COMPARE(component.status(), QQmlComponent::Ready); |
| |
| QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(component.create())); |
| QVERIFY(item); |
| |
| QQuickLoader *loader = qobject_cast<QQuickLoader*>(item->children().at(1)); |
| QVERIFY(loader); |
| QTRY_COMPARE(loader->status(), QQuickLoader::Ready); |
| |
| QVERIFY(loader->item()); |
| QCOMPARE(loader->progress(), 1.0); |
| QCOMPARE(loader->status(), QQuickLoader::Ready); |
| QCOMPARE(static_cast<QQuickItem*>(loader)->children().count(), 1); |
| |
| } |
| |
| void tst_QQuickLoader::failNetworkRequest() |
| { |
| ThreadedTestHTTPServer server(dataDirectory()); |
| |
| QTest::ignoreMessage(QtWarningMsg, QString(server.baseUrl().toString() + "/IDontExist.qml: File not found").toUtf8()); |
| |
| QQmlEngine engine; |
| QQmlComponent component(&engine); |
| const QString qml = "import QtQuick 2.0\nLoader { property int did_load: 123; source: \"" + server.baseUrl().toString() + "/IDontExist.qml\"; onLoaded: did_load=456 }"; |
| component.setData(qml.toUtf8(), server.url("/dummy.qml")); |
| QTRY_COMPARE(component.status(), QQmlComponent::Ready); |
| QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(component.create())); |
| QVERIFY(loader != nullptr); |
| |
| QTRY_COMPARE(loader->status(), QQuickLoader::Error); |
| |
| QVERIFY(!loader->item()); |
| QCOMPARE(loader->progress(), 1.0); |
| QCOMPARE(loader->property("did_load").toInt(), 123); |
| QCOMPARE(static_cast<QQuickItem*>(loader.data())->childItems().count(), 0); |
| } |
| |
| void tst_QQuickLoader::active() |
| { |
| QQmlEngine engine; |
| |
| // check that the item isn't instantiated until active is set to true |
| { |
| QQmlComponent component(&engine, testFileUrl("active.1.qml")); |
| QScopedPointer<QObject> object(component.create()); |
| QVERIFY(object != nullptr); |
| QQuickLoader *loader = object->findChild<QQuickLoader*>("loader"); |
| |
| QVERIFY(loader->active() == false); // set manually to false |
| QVERIFY(!loader->item()); |
| QMetaObject::invokeMethod(object.data(), "doSetSourceComponent"); |
| QVERIFY(!loader->item()); |
| QMetaObject::invokeMethod(object.data(), "doSetSource"); |
| QVERIFY(!loader->item()); |
| QMetaObject::invokeMethod(object.data(), "doSetActive"); |
| QVERIFY(loader->item() != nullptr); |
| } |
| |
| // check that the status is Null if active is set to false |
| { |
| QQmlComponent component(&engine, testFileUrl("active.2.qml")); |
| QScopedPointer<QObject> object(component.create()); |
| QVERIFY(object != nullptr); |
| QQuickLoader *loader = object->findChild<QQuickLoader*>("loader"); |
| |
| QVERIFY(loader->active() == true); // active is true by default |
| QCOMPARE(loader->status(), QQuickLoader::Ready); |
| int currStatusChangedCount = loader->property("statusChangedCount").toInt(); |
| QMetaObject::invokeMethod(object.data(), "doSetInactive"); |
| QCOMPARE(loader->status(), QQuickLoader::Null); |
| QCOMPARE(loader->property("statusChangedCount").toInt(), (currStatusChangedCount+1)); |
| } |
| |
| // check that the source is not cleared if active is set to false |
| { |
| QQmlComponent component(&engine, testFileUrl("active.3.qml")); |
| QScopedPointer<QObject> object(component.create()); |
| QVERIFY(object != nullptr); |
| QQuickLoader *loader = object->findChild<QQuickLoader*>("loader"); |
| |
| QVERIFY(loader->active() == true); // active is true by default |
| QVERIFY(!loader->source().isEmpty()); |
| int currSourceChangedCount = loader->property("sourceChangedCount").toInt(); |
| QMetaObject::invokeMethod(object.data(), "doSetInactive"); |
| QVERIFY(!loader->source().isEmpty()); |
| QCOMPARE(loader->property("sourceChangedCount").toInt(), currSourceChangedCount); |
| } |
| |
| // check that the sourceComponent is not cleared if active is set to false |
| { |
| QQmlComponent component(&engine, testFileUrl("active.4.qml")); |
| QScopedPointer<QObject> object(component.create()); |
| QVERIFY(object != nullptr); |
| QQuickLoader *loader = object->findChild<QQuickLoader*>("loader"); |
| |
| QVERIFY(loader->active() == true); // active is true by default |
| QVERIFY(loader->sourceComponent() != nullptr); |
| int currSourceComponentChangedCount = loader->property("sourceComponentChangedCount").toInt(); |
| QMetaObject::invokeMethod(object.data(), "doSetInactive"); |
| QVERIFY(loader->sourceComponent() != nullptr); |
| QCOMPARE(loader->property("sourceComponentChangedCount").toInt(), currSourceComponentChangedCount); |
| } |
| |
| // check that the item is released if active is set to false |
| { |
| QQmlComponent component(&engine, testFileUrl("active.5.qml")); |
| QScopedPointer<QObject> object(component.create()); |
| QVERIFY(object != nullptr); |
| QQuickLoader *loader = object->findChild<QQuickLoader*>("loader"); |
| |
| QVERIFY(loader->active() == true); // active is true by default |
| QVERIFY(loader->item() != nullptr); |
| int currItemChangedCount = loader->property("itemChangedCount").toInt(); |
| QMetaObject::invokeMethod(object.data(), "doSetInactive"); |
| QVERIFY(!loader->item()); |
| QCOMPARE(loader->property("itemChangedCount").toInt(), (currItemChangedCount+1)); |
| } |
| |
| // check that the activeChanged signal is emitted correctly |
| { |
| QQmlComponent component(&engine, testFileUrl("active.6.qml")); |
| QScopedPointer<QObject> object(component.create()); |
| QVERIFY(object != nullptr); |
| QQuickLoader *loader = object->findChild<QQuickLoader*>("loader"); |
| |
| QVERIFY(loader->active() == true); // active is true by default |
| loader->setActive(true); // no effect |
| QCOMPARE(loader->property("activeChangedCount").toInt(), 0); |
| loader->setActive(false); // change signal should be emitted |
| QCOMPARE(loader->property("activeChangedCount").toInt(), 1); |
| loader->setActive(false); // no effect |
| QCOMPARE(loader->property("activeChangedCount").toInt(), 1); |
| loader->setActive(true); // change signal should be emitted |
| QCOMPARE(loader->property("activeChangedCount").toInt(), 2); |
| loader->setActive(false); // change signal should be emitted |
| QCOMPARE(loader->property("activeChangedCount").toInt(), 3); |
| QMetaObject::invokeMethod(object.data(), "doSetActive"); |
| QCOMPARE(loader->property("activeChangedCount").toInt(), 4); |
| QMetaObject::invokeMethod(object.data(), "doSetActive"); |
| QCOMPARE(loader->property("activeChangedCount").toInt(), 4); |
| QMetaObject::invokeMethod(object.data(), "doSetInactive"); |
| QCOMPARE(loader->property("activeChangedCount").toInt(), 5); |
| loader->setActive(true); // change signal should be emitted |
| QCOMPARE(loader->property("activeChangedCount").toInt(), 6); |
| } |
| |
| // check that the component isn't loaded until active is set to true |
| { |
| QQmlComponent component(&engine, testFileUrl("active.7.qml")); |
| QScopedPointer<QObject> object(component.create()); |
| QVERIFY(object != nullptr); |
| QCOMPARE(object->property("success").toBool(), true); |
| } |
| |
| // check that the component is loaded if active is not set (true by default) |
| { |
| QQmlComponent component(&engine, testFileUrl("active.8.qml")); |
| QScopedPointer<QObject> object(component.create()); |
| QVERIFY(object != nullptr); |
| QCOMPARE(object->property("success").toBool(), true); |
| } |
| } |
| |
| void tst_QQuickLoader::initialPropertyValues_data() |
| { |
| QTest::addColumn<QUrl>("qmlFile"); |
| QTest::addColumn<QStringList>("expectedWarnings"); |
| QTest::addColumn<QStringList>("propertyNames"); |
| QTest::addColumn<QVariantList>("propertyValues"); |
| |
| QTest::newRow("source url with value set in onLoaded, initially active = true") << testFileUrl("initialPropertyValues.1.qml") |
| << QStringList() |
| << (QStringList() << "initialValue" << "behaviorCount") |
| << (QVariantList() << 1 << 1); |
| |
| QTest::newRow("set source with initial property values specified, active = true") << testFileUrl("initialPropertyValues.2.qml") |
| << QStringList() |
| << (QStringList() << "initialValue" << "behaviorCount") |
| << (QVariantList() << 2 << 0); |
| |
| QTest::newRow("set source with initial property values specified, active = false") << testFileUrl("initialPropertyValues.3.qml") |
| << (QStringList() << QString(testFileUrl("initialPropertyValues.3.qml").toString() + QLatin1String(":16: TypeError: Cannot read property 'canary' of null"))) |
| << (QStringList()) |
| << (QVariantList()); |
| |
| QTest::newRow("set source with initial property values specified, active = false, with active set true later") << testFileUrl("initialPropertyValues.4.qml") |
| << QStringList() |
| << (QStringList() << "initialValue" << "behaviorCount") |
| << (QVariantList() << 4 << 0); |
| |
| QTest::newRow("set source without initial property values specified, active = true") << testFileUrl("initialPropertyValues.5.qml") |
| << QStringList() |
| << (QStringList() << "initialValue" << "behaviorCount") |
| << (QVariantList() << 0 << 0); |
| |
| QTest::newRow("set source with initial property values specified with binding, active = true") << testFileUrl("initialPropertyValues.6.qml") |
| << QStringList() |
| << (QStringList() << "initialValue" << "behaviorCount") |
| << (QVariantList() << 6 << 0); |
| |
| QTest::newRow("ensure initial property value semantics mimic createObject") << testFileUrl("initialPropertyValues.7.qml") |
| << QStringList() |
| << (QStringList() << "loaderValue" << "createObjectValue") |
| << (QVariantList() << 1 << 1); |
| |
| QTest::newRow("ensure initial property values aren't disposed prior to component completion") << testFileUrl("initialPropertyValues.8.qml") |
| << QStringList() |
| << (QStringList() << "initialValue") |
| << (QVariantList() << 6); |
| |
| QTest::newRow("ensure required properties are set correctly") << testFileUrl("initialPropertyValues.9.qml") |
| << QStringList() |
| << (QStringList() << "i" << "s") |
| << (QVariantList() << 42 << QLatin1String("hello world")); |
| |
| QTest::newRow("required properties only partially set =") << testFileUrl("initialPropertyValues.10.qml") |
| << (QStringList() << QString(testFileUrl("RequiredPropertyValuesComponent.qml").toString() + QLatin1String(":6:5: Required property s was not initialized"))) |
| << (QStringList() << "i" << "s") |
| << (QVariantList() << 0 << QLatin1String("")); |
| |
| QTest::newRow("source url changed, previously initial properties are discared") << testFileUrl("initialPropertyValues.11.qml") |
| << QStringList() |
| << (QStringList() << "oldi" << "i") |
| << (QVariantList() << 12 << 42); |
| } |
| |
| void tst_QQuickLoader::initialPropertyValues() |
| { |
| QFETCH(QUrl, qmlFile); |
| QFETCH(QStringList, expectedWarnings); |
| QFETCH(QStringList, propertyNames); |
| QFETCH(QVariantList, propertyValues); |
| |
| ThreadedTestHTTPServer server(dataDirectory()); |
| |
| foreach (const QString &warning, expectedWarnings) |
| QTest::ignoreMessage(QtWarningMsg, warning.toLatin1().constData()); |
| |
| QQmlEngine engine; |
| QQmlComponent component(&engine, qmlFile); |
| QScopedPointer<QObject> object(component.beginCreate(engine.rootContext())); |
| QVERIFY(object != nullptr); |
| |
| const int serverBaseUrlPropertyIndex = object->metaObject()->indexOfProperty("serverBaseUrl"); |
| if (serverBaseUrlPropertyIndex != -1) { |
| QMetaProperty prop = object->metaObject()->property(serverBaseUrlPropertyIndex); |
| QVERIFY(prop.write(object.data(), server.baseUrl().toString())); |
| } |
| |
| component.completeCreate(); |
| if (expectedWarnings.isEmpty()) { |
| QQuickLoader *loader = object->findChild<QQuickLoader*>("loader"); |
| QTRY_VERIFY(loader->item()); |
| } |
| |
| for (int i = 0; i < propertyNames.size(); ++i) |
| QCOMPARE(object->property(propertyNames.at(i).toLatin1().constData()), propertyValues.at(i)); |
| } |
| |
| void tst_QQuickLoader::initialPropertyValuesBinding() |
| { |
| QQmlEngine engine; |
| QQmlComponent component(&engine, testFileUrl("initialPropertyValues.binding.qml")); |
| QScopedPointer<QObject> object(component.create()); |
| QVERIFY(object != nullptr); |
| |
| QVERIFY(object->setProperty("bindable", QVariant(8))); |
| QCOMPARE(object->property("canaryValue").toInt(), 8); |
| } |
| |
| void tst_QQuickLoader::initialPropertyValuesError_data() |
| { |
| QTest::addColumn<QUrl>("qmlFile"); |
| QTest::addColumn<QStringList>("expectedWarnings"); |
| |
| QTest::newRow("invalid initial property values object") << testFileUrl("initialPropertyValues.error.1.qml") |
| << (QStringList() << QString(testFileUrl("initialPropertyValues.error.1.qml").toString() + ":6:5: QML Loader: setSource: value is not an object")); |
| |
| QTest::newRow("nonexistent source url") << testFileUrl("initialPropertyValues.error.2.qml") |
| << (QStringList() << QString(testFileUrl("NonexistentSourceComponent.qml").toString() + ": No such file or directory")); |
| |
| QTest::newRow("invalid source url") << testFileUrl("initialPropertyValues.error.3.qml") |
| << (QStringList() << QString(testFileUrl("InvalidSourceComponent.qml").toString() + ":5:1: Expected token `:'")); |
| |
| QTest::newRow("invalid initial property values object with invalid property access") << testFileUrl("initialPropertyValues.error.4.qml") |
| << (QStringList() << QString(testFileUrl("initialPropertyValues.error.4.qml").toString() + ":7:5: QML Loader: setSource: value is not an object") |
| << QString(testFileUrl("initialPropertyValues.error.4.qml").toString() + ":5: TypeError: Cannot read property 'canary' of null")); |
| } |
| |
| void tst_QQuickLoader::initialPropertyValuesError() |
| { |
| QFETCH(QUrl, qmlFile); |
| QFETCH(QStringList, expectedWarnings); |
| |
| foreach (const QString &warning, expectedWarnings) |
| QTest::ignoreMessage(QtWarningMsg, warning.toUtf8().constData()); |
| |
| QQmlEngine engine; |
| QQmlComponent component(&engine, qmlFile); |
| QScopedPointer<QObject> object(component.create()); |
| QVERIFY(object != nullptr); |
| QQuickLoader *loader = object->findChild<QQuickLoader*>("loader"); |
| QVERIFY(loader != nullptr); |
| QVERIFY(!loader->item()); |
| } |
| |
| // QTBUG-9241 |
| void tst_QQuickLoader::deleteComponentCrash() |
| { |
| QQmlEngine engine; |
| QQmlComponent component(&engine, testFileUrl("crash.qml")); |
| QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(component.create())); |
| QVERIFY(item); |
| |
| item->metaObject()->invokeMethod(item.data(), "setLoaderSource"); |
| |
| QQuickLoader *loader = qobject_cast<QQuickLoader*>(item->QQuickItem::childItems().at(0)); |
| QVERIFY(loader); |
| QVERIFY(loader->item()); |
| QCOMPARE(loader->item()->objectName(), QLatin1String("blue")); |
| QCOMPARE(loader->progress(), 1.0); |
| QCOMPARE(loader->status(), QQuickLoader::Ready); |
| QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); |
| QCoreApplication::processEvents(); |
| QTRY_COMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 1); |
| QCOMPARE(loader->source(), testFileUrl("BlueRect.qml")); |
| } |
| |
| void tst_QQuickLoader::nonItem() |
| { |
| QQmlEngine engine; |
| QQmlComponent component(&engine, testFileUrl("nonItem.qml")); |
| |
| QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(component.create())); |
| QVERIFY(loader); |
| QVERIFY(loader->item()); |
| |
| QCOMPARE(loader.data(), loader->item()->parent()); |
| |
| QPointer<QObject> item = loader->item(); |
| loader->setActive(false); |
| QVERIFY(!loader->item()); |
| QTRY_VERIFY(!item); |
| } |
| |
| void tst_QQuickLoader::vmeErrors() |
| { |
| QQmlEngine engine; |
| QQmlComponent component(&engine, testFileUrl("vmeErrors.qml")); |
| QString err = testFileUrl("VmeError.qml").toString() + ":6:26: Cannot assign object type QObject with no default method"; |
| QTest::ignoreMessage(QtWarningMsg, err.toLatin1().constData()); |
| QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(component.create())); |
| QVERIFY(loader); |
| QVERIFY(!loader->item()); |
| } |
| |
| // QTBUG-13481 |
| void tst_QQuickLoader::creationContext() |
| { |
| QQmlEngine engine; |
| QQmlComponent component(&engine, testFileUrl("creationContext.qml")); |
| |
| QScopedPointer<QObject> o(component.create()); |
| QVERIFY(o != nullptr); |
| |
| QCOMPARE(o->property("test").toBool(), true); |
| } |
| |
| void tst_QQuickLoader::QTBUG_16928() |
| { |
| QQmlEngine engine; |
| QQmlComponent component(&engine, testFileUrl("QTBUG_16928.qml")); |
| QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(component.create())); |
| QVERIFY(item); |
| |
| QCOMPARE(item->width(), 250.); |
| QCOMPARE(item->height(), 250.); |
| } |
| |
| void tst_QQuickLoader::implicitSize() |
| { |
| QQmlEngine engine; |
| QQmlComponent component(&engine, testFileUrl("implicitSize.qml")); |
| QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(component.create())); |
| QVERIFY(item); |
| |
| QCOMPARE(item->width(), 150.); |
| QCOMPARE(item->height(), 150.); |
| |
| QCOMPARE(item->property("implHeight").toReal(), 100.); |
| QCOMPARE(item->property("implWidth").toReal(), 100.); |
| |
| QQuickLoader *loader = item->findChild<QQuickLoader*>("loader"); |
| QSignalSpy implWidthSpy(loader, SIGNAL(implicitWidthChanged())); |
| QSignalSpy implHeightSpy(loader, SIGNAL(implicitHeightChanged())); |
| |
| QMetaObject::invokeMethod(item.data(), "changeImplicitSize"); |
| |
| QCOMPARE(loader->property("implicitWidth").toReal(), 200.); |
| QCOMPARE(loader->property("implicitHeight").toReal(), 300.); |
| |
| QCOMPARE(implWidthSpy.count(), 1); |
| QCOMPARE(implHeightSpy.count(), 1); |
| } |
| |
| void tst_QQuickLoader::QTBUG_17114() |
| { |
| QQmlEngine engine; |
| QQmlComponent component(&engine, testFileUrl("QTBUG_17114.qml")); |
| QScopedPointer<QQuickItem> item(qobject_cast<QQuickItem*>(component.create())); |
| QVERIFY(item); |
| |
| QCOMPARE(item->property("loaderWidth").toReal(), 32.); |
| QCOMPARE(item->property("loaderHeight").toReal(), 32.); |
| } |
| |
| void tst_QQuickLoader::asynchronous_data() |
| { |
| QTest::addColumn<QUrl>("qmlFile"); |
| QTest::addColumn<QStringList>("expectedWarnings"); |
| |
| QTest::newRow("Valid component") << testFileUrl("BigComponent.qml") |
| << QStringList(); |
| |
| QTest::newRow("Non-existent component") << testFileUrl("IDoNotExist.qml") |
| << (QStringList() << QString(testFileUrl("IDoNotExist.qml").toString() + ": No such file or directory")); |
| |
| QTest::newRow("Invalid component") << testFileUrl("InvalidSourceComponent.qml") |
| << (QStringList() << QString(testFileUrl("InvalidSourceComponent.qml").toString() + ":5:1: Expected token `:'")); |
| } |
| |
| void tst_QQuickLoader::asynchronous() |
| { |
| QFETCH(QUrl, qmlFile); |
| QFETCH(QStringList, expectedWarnings); |
| |
| QQmlEngine engine; |
| PeriodicIncubationController *controller = new PeriodicIncubationController; |
| QQmlIncubationController *previous = engine.incubationController(); |
| engine.setIncubationController(controller); |
| delete previous; |
| |
| QQmlComponent component(&engine, testFileUrl("asynchronous.qml")); |
| QScopedPointer<QQuickItem> root(qobject_cast<QQuickItem*>(component.create())); |
| QVERIFY(root); |
| |
| QQuickLoader *loader = root->findChild<QQuickLoader*>("loader"); |
| QVERIFY(loader); |
| |
| foreach (const QString &warning, expectedWarnings) |
| QTest::ignoreMessage(QtWarningMsg, warning.toUtf8().constData()); |
| |
| QVERIFY(!loader->item()); |
| QCOMPARE(loader->progress(), 0.0); |
| root->setProperty("comp", qmlFile.toString()); |
| QMetaObject::invokeMethod(root.data(), "loadComponent"); |
| QVERIFY(!loader->item()); |
| |
| if (expectedWarnings.isEmpty()) { |
| QCOMPARE(loader->status(), QQuickLoader::Loading); |
| |
| controller->start(); |
| QVERIFY(!controller->incubated); // asynchronous compilation means not immediately compiled/incubating. |
| QTRY_VERIFY(controller->incubated); // but should start incubating once compilation is complete. |
| QTRY_VERIFY(loader->item()); |
| QCOMPARE(loader->progress(), 1.0); |
| QCOMPARE(loader->status(), QQuickLoader::Ready); |
| } else { |
| QTRY_COMPARE(loader->progress(), 1.0); |
| QTRY_COMPARE(loader->status(), QQuickLoader::Error); |
| } |
| } |
| |
| void tst_QQuickLoader::asynchronous_clear() |
| { |
| QQmlEngine engine; |
| PeriodicIncubationController *controller = new PeriodicIncubationController; |
| QQmlIncubationController *previous = engine.incubationController(); |
| engine.setIncubationController(controller); |
| delete previous; |
| |
| QQmlComponent component(&engine, testFileUrl("asynchronous.qml")); |
| QScopedPointer<QQuickItem> root(qobject_cast<QQuickItem*>(component.create())); |
| QVERIFY(root); |
| |
| QQuickLoader *loader = root->findChild<QQuickLoader*>("loader"); |
| QVERIFY(loader); |
| |
| QVERIFY(!loader->item()); |
| root->setProperty("comp", "BigComponent.qml"); |
| QMetaObject::invokeMethod(root.data(), "loadComponent"); |
| QVERIFY(!loader->item()); |
| |
| controller->start(); |
| QCOMPARE(loader->status(), QQuickLoader::Loading); |
| QTRY_COMPARE(engine.incubationController()->incubatingObjectCount(), 1); |
| |
| // clear before component created |
| root->setProperty("comp", ""); |
| QMetaObject::invokeMethod(root.data(), "loadComponent"); |
| QVERIFY(!loader->item()); |
| QCOMPARE(engine.incubationController()->incubatingObjectCount(), 0); |
| |
| QCOMPARE(loader->progress(), 0.0); |
| QCOMPARE(loader->status(), QQuickLoader::Null); |
| QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 0); |
| |
| // check loading component |
| root->setProperty("comp", "BigComponent.qml"); |
| QMetaObject::invokeMethod(root.data(), "loadComponent"); |
| QVERIFY(!loader->item()); |
| |
| QCOMPARE(loader->status(), QQuickLoader::Loading); |
| QCOMPARE(engine.incubationController()->incubatingObjectCount(), 1); |
| |
| QTRY_VERIFY(loader->item()); |
| QCOMPARE(loader->progress(), 1.0); |
| QCOMPARE(loader->status(), QQuickLoader::Ready); |
| QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 1); |
| } |
| |
| void tst_QQuickLoader::simultaneousSyncAsync() |
| { |
| QQmlEngine engine; |
| PeriodicIncubationController *controller = new PeriodicIncubationController; |
| QQmlIncubationController *previous = engine.incubationController(); |
| engine.setIncubationController(controller); |
| delete previous; |
| |
| QQmlComponent component(&engine, testFileUrl("simultaneous.qml")); |
| QScopedPointer<QQuickItem> root(qobject_cast<QQuickItem*>(component.create())); |
| QVERIFY(root); |
| |
| QQuickLoader *asyncLoader = root->findChild<QQuickLoader*>("asyncLoader"); |
| QQuickLoader *syncLoader = root->findChild<QQuickLoader*>("syncLoader"); |
| QVERIFY(asyncLoader); |
| QVERIFY(syncLoader); |
| |
| QVERIFY(!asyncLoader->item()); |
| QVERIFY(!syncLoader->item()); |
| QMetaObject::invokeMethod(root.data(), "loadComponents"); |
| QVERIFY(!asyncLoader->item()); |
| QVERIFY(syncLoader->item()); |
| |
| controller->start(); |
| QCOMPARE(asyncLoader->status(), QQuickLoader::Loading); |
| QVERIFY(!controller->incubated); // asynchronous compilation means not immediately compiled/incubating. |
| QTRY_VERIFY(controller->incubated); // but should start incubating once compilation is complete. |
| QTRY_VERIFY(asyncLoader->item()); |
| QCOMPARE(asyncLoader->progress(), 1.0); |
| QCOMPARE(asyncLoader->status(), QQuickLoader::Ready); |
| } |
| |
| void tst_QQuickLoader::asyncToSync1() |
| { |
| QQmlEngine engine; |
| PeriodicIncubationController *controller = new PeriodicIncubationController; |
| QQmlIncubationController *previous = engine.incubationController(); |
| engine.setIncubationController(controller); |
| delete previous; |
| |
| QQmlComponent component(&engine, testFileUrl("asynchronous.qml")); |
| QScopedPointer<QQuickItem> root(qobject_cast<QQuickItem*>(component.create())); |
| QVERIFY(root); |
| |
| QQuickLoader *loader = root->findChild<QQuickLoader*>("loader"); |
| QVERIFY(loader); |
| |
| QVERIFY(!loader->item()); |
| root->setProperty("comp", "BigComponent.qml"); |
| QMetaObject::invokeMethod(root.data(), "loadComponent"); |
| QVERIFY(!loader->item()); |
| |
| controller->start(); |
| QCOMPARE(loader->status(), QQuickLoader::Loading); |
| QCOMPARE(engine.incubationController()->incubatingObjectCount(), 0); |
| |
| // force completion before component created |
| loader->setAsynchronous(false); |
| QVERIFY(loader->item()); |
| QCOMPARE(loader->progress(), 1.0); |
| QCOMPARE(loader->status(), QQuickLoader::Ready); |
| QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 1); |
| } |
| |
| void tst_QQuickLoader::asyncToSync2() |
| { |
| QQmlEngine engine; |
| PeriodicIncubationController *controller = new PeriodicIncubationController; |
| QQmlIncubationController *previous = engine.incubationController(); |
| engine.setIncubationController(controller); |
| delete previous; |
| |
| QQmlComponent component(&engine, testFileUrl("asynchronous.qml")); |
| QScopedPointer<QQuickItem> root(qobject_cast<QQuickItem*>(component.create())); |
| QVERIFY(root); |
| |
| QQuickLoader *loader = root->findChild<QQuickLoader*>("loader"); |
| QVERIFY(loader); |
| |
| QVERIFY(!loader->item()); |
| root->setProperty("comp", "BigComponent.qml"); |
| QMetaObject::invokeMethod(root.data(), "loadComponent"); |
| QVERIFY(!loader->item()); |
| |
| controller->start(); |
| QCOMPARE(loader->status(), QQuickLoader::Loading); |
| QTRY_COMPARE(engine.incubationController()->incubatingObjectCount(), 1); |
| |
| // force completion after component created but before incubation complete |
| loader->setAsynchronous(false); |
| QVERIFY(loader->item()); |
| QCOMPARE(loader->progress(), 1.0); |
| QCOMPARE(loader->status(), QQuickLoader::Ready); |
| QCOMPARE(static_cast<QQuickItem*>(loader)->childItems().count(), 1); |
| } |
| |
| void tst_QQuickLoader::loadedSignal() |
| { |
| QQmlEngine engine; |
| PeriodicIncubationController *controller = new PeriodicIncubationController; |
| QQmlIncubationController *previous = engine.incubationController(); |
| engine.setIncubationController(controller); |
| delete previous; |
| |
| { |
| // ensure that triggering loading (by setting active = true) |
| // and then immediately setting active to false, causes the |
| // loader to be deactivated, including disabling the incubator. |
| QQmlComponent component(&engine, testFileUrl("loadedSignal.qml")); |
| QScopedPointer<QObject> obj(component.create()); |
| |
| QMetaObject::invokeMethod(obj.data(), "triggerLoading"); |
| QTest::qWait(100); // ensure that loading would have finished if it wasn't deactivated |
| QCOMPARE(obj->property("loadCount").toInt(), 0); |
| QVERIFY(obj->property("success").toBool()); |
| |
| QMetaObject::invokeMethod(obj.data(), "triggerLoading"); |
| QTest::qWait(100); |
| QCOMPARE(obj->property("loadCount").toInt(), 0); |
| QVERIFY(obj->property("success").toBool()); |
| |
| QMetaObject::invokeMethod(obj.data(), "triggerMultipleLoad"); |
| controller->start(); |
| QTest::qWait(100); |
| QTRY_COMPARE(obj->property("loadCount").toInt(), 1); // only one loaded signal should be emitted. |
| QVERIFY(obj->property("success").toBool()); |
| } |
| |
| { |
| // ensure that an error doesn't result in the onLoaded signal being emitted. |
| QQmlComponent component(&engine, testFileUrl("loadedSignal.2.qml")); |
| QScopedPointer<QObject> obj(component.create()); |
| |
| QMetaObject::invokeMethod(obj.data(), "triggerLoading"); |
| QTest::qWait(100); |
| QCOMPARE(obj->property("loadCount").toInt(), 0); |
| QVERIFY(obj->property("success").toBool()); |
| } |
| } |
| |
| void tst_QQuickLoader::parented() |
| { |
| QQmlEngine engine; |
| QQmlComponent component(&engine, testFileUrl("parented.qml")); |
| QScopedPointer<QQuickItem> root(qobject_cast<QQuickItem*>(component.create())); |
| QVERIFY(root); |
| |
| QQuickItem *item = root->findChild<QQuickItem*>("comp"); |
| QVERIFY(item); |
| |
| QCOMPARE(item->parentItem(), root.data()); |
| |
| QCOMPARE(item->width(), 300.); |
| QCOMPARE(item->height(), 300.); |
| } |
| |
| void tst_QQuickLoader::sizeBound() |
| { |
| QQmlEngine engine; |
| QQmlComponent component(&engine, testFileUrl("sizebound.qml")); |
| QScopedPointer<QQuickItem> root(qobject_cast<QQuickItem*>(component.create())); |
| QVERIFY(root); |
| QQuickLoader *loader = root->findChild<QQuickLoader*>("loader"); |
| QVERIFY(loader != nullptr); |
| |
| QVERIFY(loader->item()); |
| |
| QCOMPARE(loader->width(), 50.0); |
| QCOMPARE(loader->height(), 60.0); |
| |
| QMetaObject::invokeMethod(root.data(), "switchComponent"); |
| |
| QCOMPARE(loader->width(), 80.0); |
| QCOMPARE(loader->height(), 90.0); |
| } |
| |
| void tst_QQuickLoader::QTBUG_30183() |
| { |
| QQmlEngine engine; |
| QQmlComponent component(&engine, testFileUrl("/QTBUG_30183.qml")); |
| QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(component.create())); |
| QVERIFY(loader != nullptr); |
| QCOMPARE(loader->width(), 240.0); |
| QCOMPARE(loader->height(), 120.0); |
| |
| // the loaded item must follow the size |
| QQuickItem *rect = qobject_cast<QQuickItem*>(loader->item()); |
| QVERIFY(rect); |
| QCOMPARE(rect->width(), 240.0); |
| QCOMPARE(rect->height(), 120.0); |
| } |
| |
| void tst_QQuickLoader::transientWindow() // QTBUG-52944 |
| { |
| QQuickView view; |
| view.setSource(testFileUrl("itemLoaderWindow.qml")); |
| QQuickItem *root = qobject_cast<QQuickItem*>(view.rootObject()); |
| QVERIFY(root); |
| QQuickLoader *loader = root->findChild<QQuickLoader *>(); |
| QVERIFY(loader); |
| QTRY_COMPARE(loader->status(), QQuickLoader::Ready); |
| QQuickWindowQmlImpl *loadedWindow = qobject_cast<QQuickWindowQmlImpl *>(loader->item()); |
| QVERIFY(loadedWindow); |
| QCOMPARE(loadedWindow->visibility(), QWindow::Hidden); |
| |
| QElapsedTimer timer; |
| qint64 viewVisibleTime = -1; |
| qint64 loadedWindowVisibleTime = -1; |
| connect(&view, &QWindow::visibleChanged, |
| [&viewVisibleTime, &timer]() { viewVisibleTime = timer.elapsed(); } ); |
| connect(loadedWindow, &QQuickWindowQmlImpl::visibilityChanged, |
| [&loadedWindowVisibleTime, &timer]() { loadedWindowVisibleTime = timer.elapsed(); } ); |
| timer.start(); |
| view.show(); |
| |
| QVERIFY(QTest::qWaitForWindowExposed(&view)); |
| QTRY_VERIFY(loadedWindowVisibleTime >= 0); |
| QVERIFY(viewVisibleTime >= 0); |
| |
| // now that we're sure they are both visible, which one became visible first? |
| qCDebug(lcTests) << "transient Window became visible" << (loadedWindowVisibleTime - viewVisibleTime) << "ms after the root Item"; |
| QVERIFY((loadedWindowVisibleTime - viewVisibleTime) >= 0); |
| |
| QWindowList windows = QGuiApplication::topLevelWindows(); |
| QTRY_COMPARE(windows.size(), 2); |
| |
| // TODO Ideally we would now close the outer window and make sure the transient window closes too. |
| // It works during manual testing because of QWindowPrivate::maybeQuitOnLastWindowClosed() |
| // but quitting an autotest doesn't make sense. |
| } |
| |
| void tst_QQuickLoader::nestedTransientWindow() // QTBUG-52944 |
| { |
| QQuickView view; |
| view.setSource(testFileUrl("itemLoaderItemWindow.qml")); |
| QQuickItem *root = qobject_cast<QQuickItem*>(view.rootObject()); |
| QVERIFY(root); |
| QQuickLoader *loader = root->findChild<QQuickLoader *>(); |
| QVERIFY(loader); |
| QTRY_COMPARE(loader->status(), QQuickLoader::Ready); |
| QQuickItem *loadedItem = qobject_cast<QQuickItem *>(loader->item()); |
| QVERIFY(loadedItem); |
| QQuickWindowQmlImpl *loadedWindow = loadedItem->findChild<QQuickWindowQmlImpl *>(); |
| QVERIFY(loadedWindow); |
| QCOMPARE(loadedWindow->visibility(), QWindow::Hidden); |
| |
| QElapsedTimer timer; |
| qint64 viewVisibleTime = -1; |
| qint64 loadedWindowVisibleTime = -1; |
| connect(&view, &QWindow::visibleChanged, |
| [&viewVisibleTime, &timer]() { viewVisibleTime = timer.elapsed(); } ); |
| connect(loadedWindow, &QQuickWindowQmlImpl::visibilityChanged, |
| [&loadedWindowVisibleTime, &timer]() { loadedWindowVisibleTime = timer.elapsed(); } ); |
| timer.start(); |
| view.show(); |
| |
| QVERIFY(QTest::qWaitForWindowExposed(&view)); |
| QTRY_VERIFY(loadedWindowVisibleTime >= 0); |
| QVERIFY(viewVisibleTime >= 0); |
| |
| // now that we're sure they are both visible, which one became visible first? |
| qCDebug(lcTests) << "transient Window became visible" << (loadedWindowVisibleTime - viewVisibleTime) << "ms after the root Item"; |
| QVERIFY((loadedWindowVisibleTime - viewVisibleTime) >= 0); |
| |
| QWindowList windows = QGuiApplication::topLevelWindows(); |
| QTRY_COMPARE(windows.size(), 2); |
| |
| // TODO Ideally we would now close the outer window and make sure the transient window closes too. |
| // It works during manual testing because of QWindowPrivate::maybeQuitOnLastWindowClosed() |
| // but quitting an autotest doesn't make sense. |
| } |
| |
| void tst_QQuickLoader::sourceComponentGarbageCollection() |
| { |
| QQmlEngine engine; |
| QQmlComponent component(&engine, testFileUrl("sourceComponentGarbageCollection.qml")); |
| QScopedPointer<QObject> obj(component.create()); |
| QVERIFY(!obj.isNull()); |
| |
| QMetaObject::invokeMethod(obj.data(), "setSourceComponent"); |
| engine.collectGarbage(); |
| QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); |
| |
| QSignalSpy spy(obj.data(), SIGNAL(loaded())); |
| |
| obj->setProperty("active", true); |
| |
| if (spy.isEmpty()) |
| QVERIFY(spy.wait()); |
| |
| QCOMPARE(spy.count(), 1); |
| } |
| |
| // QTBUG-51995 |
| void tst_QQuickLoader::bindings() |
| { |
| QQmlEngine engine; |
| QQmlComponent component(&engine, testFileUrl("bindings.qml")); |
| QScopedPointer<QObject> object(component.create()); |
| QVERIFY(!object.isNull()); |
| |
| QQuickItem *game = object->property("game").value<QQuickItem*>(); |
| QVERIFY(game); |
| |
| QQuickLoader *loader = object->property("loader").value<QQuickLoader*>(); |
| QVERIFY(loader); |
| |
| QSignalSpy warningsSpy(&engine, SIGNAL(warnings(QList<QQmlError>))); |
| |
| // Causes the Loader to become active |
| game->setState(QLatin1String("running")); |
| QTRY_VERIFY(loader->item()); |
| |
| // Causes the Loader to become inactive - should not cause binding errors |
| game->setState(QLatin1String("invalid")); |
| QTRY_VERIFY(!loader->item()); |
| |
| QString failureMessage; |
| if (!warningsSpy.isEmpty()) { |
| QDebug stream(&failureMessage); |
| stream << warningsSpy.first().first().value<QList<QQmlError>>(); |
| } |
| QVERIFY2(warningsSpy.isEmpty(), qPrintable(failureMessage)); |
| } |
| |
| // QTBUG-47321 |
| void tst_QQuickLoader::parentErrors() |
| { |
| QQmlEngine engine; |
| QQmlComponent component(&engine, testFileUrl("parentErrors.qml")); |
| QScopedPointer<QObject> object(component.create()); |
| QVERIFY(!object.isNull()); |
| |
| QQuickLoader *loader = object->property("loader").value<QQuickLoader*>(); |
| QVERIFY(loader); |
| |
| QSignalSpy warningsSpy(&engine, SIGNAL(warnings(QList<QQmlError>))); |
| |
| // Give the loader a component |
| loader->setSourceComponent(object->property("component").value<QQmlComponent*>()); |
| QTRY_VERIFY(loader->item()); |
| |
| // Clear the loader's component; should not cause binding errors |
| loader->setSourceComponent(nullptr); |
| QTRY_VERIFY(!loader->item()); |
| |
| QString failureMessage; |
| if (!warningsSpy.isEmpty()) { |
| QDebug stream(&failureMessage); |
| stream << warningsSpy.first().first().value<QList<QQmlError>>(); |
| } |
| QVERIFY2(warningsSpy.isEmpty(), qPrintable(failureMessage)); |
| } |
| |
| class ObjectInRootContext: public QObject |
| { |
| Q_OBJECT |
| |
| public: |
| int didIt = 0; |
| |
| public slots: |
| void doIt() { |
| didIt += 1; |
| } |
| }; |
| |
| void tst_QQuickLoader::rootContext() |
| { |
| QQmlEngine engine; |
| ObjectInRootContext objectInRootContext; |
| engine.rootContext()->setContextProperty("objectInRootContext", &objectInRootContext); |
| |
| QQmlComponent component(&engine, testFileUrl("rootContext.qml")); |
| QScopedPointer<QObject> object(component.create()); |
| QVERIFY(!object.isNull()); |
| |
| QQuickLoader *loader = object->property("loader").value<QQuickLoader*>(); |
| QVERIFY(loader); |
| |
| QSignalSpy warningsSpy(&engine, SIGNAL(warnings(QList<QQmlError>))); |
| |
| // Give the loader a component |
| loader->setSourceComponent(object->property("component").value<QQmlComponent*>()); |
| QTRY_VERIFY(loader->active()); |
| QTRY_VERIFY(loader->item()); |
| |
| QString failureMessage; |
| if (!warningsSpy.isEmpty()) { |
| QDebug stream(&failureMessage); |
| stream << warningsSpy.first().first().value<QList<QQmlError>>(); |
| } |
| QVERIFY2(warningsSpy.isEmpty(), qPrintable(failureMessage)); |
| QCOMPARE(objectInRootContext.didIt, 0); |
| |
| // Deactivate the loader, which deletes the item. |
| // Check that a) there are no errors, and b) the objectInRootContext can still be resolved even |
| // after deactivating the loader. If it cannot, a ReferenceError for objectInRootContext is |
| // generated (and the 'doIt' counter in objectInRootContext will be 1 for the call before |
| // the deactivation). |
| loader->item()->setProperty("trigger", true); |
| QTRY_VERIFY(!loader->active()); |
| QTRY_VERIFY(!loader->item()); |
| |
| if (!warningsSpy.isEmpty()) { |
| QDebug stream(&failureMessage); |
| stream << warningsSpy.first().first().value<QList<QQmlError>>(); |
| } |
| QVERIFY2(warningsSpy.isEmpty(), qPrintable(failureMessage)); |
| QCOMPARE(objectInRootContext.didIt, 2); |
| } |
| |
| void tst_QQuickLoader::sourceURLKeepComponent() |
| { |
| QQmlEngine engine; |
| QQmlComponent component(&engine); |
| component.setData(QByteArray( |
| "import QtQuick 2.0\n" |
| " Loader { id: loader\n }"), |
| dataDirectoryUrl()); |
| |
| QScopedPointer<QQuickLoader> loader(qobject_cast<QQuickLoader*>(component.create())); |
| loader->setSource(testFileUrl("/Rect120x60.qml")); |
| |
| QVERIFY(loader); |
| QVERIFY(loader->item()); |
| QVERIFY(loader->sourceComponent()); |
| QCOMPARE(loader->progress(), 1.0); |
| |
| const QPointer<QQmlComponent> sourceComponent = loader->sourceComponent(); |
| |
| //Ensure toggling active status does not recreate component |
| loader->setActive(false); |
| QVERIFY(!loader->item()); |
| QVERIFY(loader->sourceComponent()); |
| QCOMPARE(sourceComponent.data(), loader->sourceComponent()); |
| |
| loader->setActive(true); |
| QVERIFY(loader->item()); |
| QVERIFY(loader->sourceComponent()); |
| QCOMPARE(sourceComponent.data(), loader->sourceComponent()); |
| |
| loader->setActive(false); |
| QVERIFY(!loader->item()); |
| QVERIFY(loader->sourceComponent()); |
| QCOMPARE(sourceComponent.data(), loader->sourceComponent()); |
| |
| //Ensure changing source url causes component to be recreated when inactive |
| loader->setSource(testFileUrl("/BlueRect.qml")); |
| |
| loader->setActive(true); |
| QVERIFY(loader->item()); |
| QVERIFY(loader->sourceComponent()); |
| |
| const QPointer<QQmlComponent> newSourceComponent = loader->sourceComponent(); |
| QVERIFY(sourceComponent.data() != newSourceComponent.data()); |
| |
| //Ensure changing source url causes component to be recreated when active |
| loader->setSource(testFileUrl("/Rect120x60.qml")); |
| QVERIFY(loader->sourceComponent() != newSourceComponent.data()); |
| |
| } |
| |
| // QTBUG-82002 |
| void tst_QQuickLoader::statusChangeOnlyEmittedOnce() |
| { |
| QQmlApplicationEngine engine; |
| auto url = testFileUrl("statusChanged.qml"); |
| engine.load(url); |
| auto root = engine.rootObjects().at(0); |
| QVERIFY(root); |
| QTRY_COMPARE(QQuickLoader::Status(root->property("status").toInt()), QQuickLoader::Ready); |
| QCOMPARE(root->property("statusChangedCounter").toInt(), 2); // 1xLoading + 1xReady*/ |
| } |
| |
| QTEST_MAIN(tst_QQuickLoader) |
| |
| #include "tst_qquickloader.moc" |