// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lifecyclewatcher.h" #include using namespace Qt::StringLiterals; class WithQJSValue : public QObject { Q_OBJECT Q_PROPERTY(QJSValue v READ v WRITE setV NOTIFY vChanged) public: QJSValue v() const { return m_v; } void setV(const QJSValue &newV) { if (!m_v.strictlyEquals(newV)) { m_v = newV; emit vChanged(); } } signals: void vChanged(); private: QJSValue m_v; }; class MyIC : public QObject, public QQmlIncubationController { Q_OBJECT public: MyIC() { startTimer(5); } protected: void timerEvent(QTimerEvent*) override { incubateFor(5); } }; class ComponentWatcher : public QObject { Q_OBJECT public: ComponentWatcher(QQmlComponent *comp) { progresses.append(comp->progress()); connect(comp, &QQmlComponent::statusChanged, this, &ComponentWatcher::statusChanged); connect(comp, &QQmlComponent::progressChanged, this, &ComponentWatcher::progressChanged); } int loading = 0; int error = 0; int ready = 0; QList progresses; public slots: void statusChanged(QQmlComponent::Status status) { switch (status) { case QQmlComponent::Loading: ++loading; break; case QQmlComponent::Error: ++error; break; case QQmlComponent::Ready: ++ready; break; default: break; } } void progressChanged(qreal progress) { progresses.append(progress); } }; class tst_qqmlcomponent : public QQmlDataTest { Q_OBJECT public: tst_qqmlcomponent() : QQmlDataTest(QT_QMLTEST_DATADIR) { engine.setIncubationController(&ic); } private slots: void null(); void loadEmptyUrl(); void qmlCreateWindow(); void qmlCreateObjectAutoParent_data(); void qmlCreateObjectAutoParent(); void qmlCreateObjectWithProperties(); void qmlCreateObjectClean(); void qmlCreateObjectDirty(); void qmlIncubateObject(); void qmlCreateParentReference(); void async_data(); void async(); void asyncHierarchy(); void asyncForceSync(); void componentUrlCanonicalization(); void onDestructionLookup(); void onDestructionCount(); void recursion(); void recursionContinuation(); void partialComponentCreation(); void callingContextForInitialProperties(); void setNonExistentInitialProperty(); void relativeUrl_data(); void relativeUrl(); void setDataNoEngineNoSegfault(); void testRequiredProperties_data(); void testRequiredProperties(); void testRequiredPropertiesFromQml(); void testSetInitialProperties(); void createInsideJSModule(); void qmlErrorIsReported(); void initJSValueProp(); void qmlPropertySignalExists(); void componentTypes(); void boundComponent(); void loadFromModule_data(); void loadFromModule(); void loadFromModuleSyncAndAsync_data(); void loadFromModuleSyncAndAsync(); void loadFromModuleLifecycle(); void loadFromModuleThenCreateWithIncubator(); void loadFromModuleFailures_data(); void loadFromModuleFailures(); void loadFromModuleRequired(); void loadUrlRequired(); void loadFromQrc(); void loadFromRelativeQrcPath(); void removeBinding(); void complexObjectArgument(); void bindingEvaluationOrder(); void compilationUnitsWithSameUrl(); void bindingInRequired(); private: QQmlEngine engine; MyIC ic; }; void tst_qqmlcomponent::null() { { QQmlComponent c; QVERIFY(c.isNull()); } { QQmlComponent c(&engine); QVERIFY(c.isNull()); } } void tst_qqmlcomponent::loadEmptyUrl() { QQmlComponent c(&engine); c.loadUrl(QUrl()); QVERIFY(c.isError()); QCOMPARE(c.errors().size(), 1); QQmlError error = c.errors().first(); QCOMPARE(error.url(), QUrl()); QCOMPARE(error.line(), -1); QCOMPARE(error.column(), -1); QCOMPARE(error.description(), QLatin1String("Invalid empty URL")); } void tst_qqmlcomponent::qmlIncubateObject() { QQmlComponent component(&engine, testFileUrl("incubateObject.qml")); std::unique_ptr object { component.create() }; QVERIFY(object != nullptr); QCOMPARE(object->property("test1").toBool(), true); QCOMPARE(object->property("test2").toBool(), false); QTRY_VERIFY(object->property("test2").toBool()); } void tst_qqmlcomponent::qmlCreateWindow() { QQmlEngine engine; QQmlComponent component(&engine); component.loadUrl(testFileUrl("createWindow.qml")); QVERIFY2(component.isReady(), qPrintable(component.errorString())); QScopedPointer window(qobject_cast(component.create())); QVERIFY(!window.isNull()); } void tst_qqmlcomponent::qmlCreateObjectAutoParent_data() { QTest::addColumn("testFile"); QTest::newRow("createObject") << QStringLiteral("createObject.qml"); QTest::newRow("createQmlObject") << QStringLiteral("createQmlObject.qml"); } void tst_qqmlcomponent::qmlCreateObjectAutoParent() { QFETCH(QString, testFile); QQmlEngine engine; QQmlComponent component(&engine, testFileUrl(testFile)); QScopedPointer root(qobject_cast(component.create())); QVERIFY(!root.isNull()); QObject *qtobjectParent = root->property("qtobjectParent").value(); QQuickItem *itemParent = qobject_cast(root->property("itemParent").value()); QQuickWindow *windowParent = qobject_cast(root->property("windowParent").value()); QVERIFY(qtobjectParent); QVERIFY(itemParent); QVERIFY(windowParent); QObject *qtobject_qtobject = root->property("qtobject_qtobject").value(); QObject *qtobject_item = root->property("qtobject_item").value(); QObject *qtobject_window = root->property("qtobject_window").value(); QObject *item_qtobject = root->property("item_qtobject").value(); QObject *item_item = root->property("item_item").value(); QObject *item_window = root->property("item_window").value(); QObject *window_qtobject = root->property("window_qtobject").value(); QObject *window_item = root->property("window_item").value(); QObject *window_window = root->property("window_window").value(); QVERIFY(qtobject_qtobject); QVERIFY(qtobject_item); QVERIFY(qtobject_window); QVERIFY(item_qtobject); QVERIFY(item_item); QVERIFY(item_window); QVERIFY(window_qtobject); QVERIFY(window_item); QVERIFY(window_window); QVERIFY(QByteArray(qtobject_item->metaObject()->className()).startsWith("QQuickItem")); QVERIFY(QByteArray(qtobject_window->metaObject()->className()).startsWith("QQuickWindow")); QVERIFY(QByteArray(item_item->metaObject()->className()).startsWith("QQuickItem")); QVERIFY(QByteArray(item_window->metaObject()->className()).startsWith("QQuickWindow")); QVERIFY(QByteArray(window_item->metaObject()->className()).startsWith("QQuickItem")); QVERIFY(QByteArray(window_window->metaObject()->className()).startsWith("QQuickWindow")); QCOMPARE(qtobject_qtobject->parent(), qtobjectParent); QCOMPARE(qtobject_item->parent(), qtobjectParent); QCOMPARE(qtobject_window->parent(), qtobjectParent); QCOMPARE(item_qtobject->parent(), itemParent); QCOMPARE(item_item->parent(), itemParent); QCOMPARE(item_window->parent(), itemParent); QCOMPARE(window_qtobject->parent(), windowParent); QCOMPARE(window_item->parent(), windowParent); QCOMPARE(window_window->parent(), windowParent); QCOMPARE(qobject_cast(qtobject_item)->parentItem(), (QQuickItem *)nullptr); QCOMPARE(qobject_cast(qtobject_window)->transientParent(), (QQuickWindow *)nullptr); QCOMPARE(qobject_cast(item_item)->parentItem(), itemParent); QCOMPARE(qobject_cast(item_window)->transientParent(), itemParent->window()); QCOMPARE(qobject_cast(window_item)->parentItem(), windowParent->contentItem()); QCOMPARE(qobject_cast(window_window)->transientParent(), windowParent); } void tst_qqmlcomponent::qmlCreateObjectWithProperties() { QQmlEngine engine; QTest::ignoreMessage( QtMsgType::QtWarningMsg, QRegularExpression(".*createObjectWithScript.qml: Setting initial properties failed: " "Item does not have a property called not_i")); QTest::ignoreMessage( QtMsgType::QtWarningMsg, QRegularExpression( ".*createObjectWithScript.qml:45:13: Required property i was not initialized")); QQmlComponent component(&engine, testFileUrl("createObjectWithScript.qml")); QVERIFY2(component.errorString().isEmpty(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); { QScopedPointer testObject1(object->property("declarativerectangle") .value()); QVERIFY(testObject1); QCOMPARE(testObject1->parent(), object.data()); QCOMPARE(testObject1->property("x").value(), 17); QCOMPARE(testObject1->property("y").value(), 17); QCOMPARE(testObject1->property("color").value(), QColor(255,255,255)); QCOMPARE(QQmlProperty::read(testObject1.data(),"border.width").toInt(), 3); QCOMPARE(QQmlProperty::read(testObject1.data(),"innerRect.border.width").toInt(), 20); } { QScopedPointer testObject2(object->property("declarativeitem").value()); QVERIFY(testObject2); QCOMPARE(testObject2->parent(), object.data()); //QCOMPARE(testObject2->metaObject()->className(), "QDeclarativeItem_QML_2"); QCOMPARE(testObject2->property("x").value(), 17); QCOMPARE(testObject2->property("y").value(), 17); QCOMPARE(testObject2->property("testBool").value(), true); QCOMPARE(testObject2->property("testInt").value(), 17); QCOMPARE(testObject2->property("testObject").value(), object.data()); } { QScopedPointer testBindingObj(object->property("bindingTestObject") .value()); QVERIFY(testBindingObj); QCOMPARE(testBindingObj->parent(), object.data()); QCOMPARE(testBindingObj->property("testValue").value(), 300); object->setProperty("width", 150); QCOMPARE(testBindingObj->property("testValue").value(), 150 * 3); } { QScopedPointer testBindingThisObj(object->property("bindingThisTestObject") .value()); QVERIFY(testBindingThisObj); QCOMPARE(testBindingThisObj->parent(), object.data()); QCOMPARE(testBindingThisObj->property("testValue").value(), 900); testBindingThisObj->setProperty("width", 200); QCOMPARE(testBindingThisObj->property("testValue").value(), 200 * 3); } { QScopedPointer badRequired(object->property("badRequired").value()); QVERIFY(!badRequired); QScopedPointer goodRequired(object->property("goodRequired").value()); QVERIFY(goodRequired); QCOMPARE(goodRequired->parent(), object.data()); QCOMPARE(goodRequired->property("i").value(), 42); } { QScopedPointer bindingAsInitial(object->property("bindingAsInitial").value()); QVERIFY(bindingAsInitial); QVERIFY(object->property("bindingUsed").toBool()); } } void tst_qqmlcomponent::qmlCreateObjectClean() { QQmlEngine engine; QVERIFY(engine.outputWarningsToStandardError()); QObject::connect(&engine, &QQmlEngine::warnings, [](const QList &) { QFAIL("Calls with suitable parameters should not generate any warnings."); }); QQmlComponent component(&engine, testFileUrl("createObjectClean.qml")); QVERIFY2(component.errorString().isEmpty(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QVERIFY(qvariant_cast(object->property("a")) != nullptr); QVERIFY(qvariant_cast(object->property("b")) != nullptr); QVERIFY(qvariant_cast(object->property("c")) != nullptr); QVERIFY(qvariant_cast(object->property("d")) != nullptr); } void tst_qqmlcomponent::qmlCreateObjectDirty() { QQmlEngine engine; engine.setOutputWarningsToStandardError(false); QObject::connect(&engine, &QQmlEngine::warnings, [](const QList &warnings) { QCOMPARE(warnings.size(), 1); QCOMPARE(warnings[0].description(), "QML Component: Unsuitable arguments passed to createObject(). The first argument " "should be a QObject* or null, and the second argument should be a JavaScript " "object or a QVariantMap"); }); QQmlComponent component(&engine, testFileUrl("createObjectDirty.qml")); QVERIFY2(component.errorString().isEmpty(), component.errorString().toUtf8()); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QVERIFY(qvariant_cast(object->property("a")) != nullptr); } void tst_qqmlcomponent::qmlCreateParentReference() { QQmlEngine engine; QCOMPARE(engine.outputWarningsToStandardError(), true); QQmlTestMessageHandler messageHandler; QQmlComponent component(&engine, testFileUrl("createParentReference.qml")); QVERIFY2(component.errorString().isEmpty(), component.errorString().toUtf8()); std::unique_ptr object { component.create() }; QVERIFY(object != nullptr); QVERIFY(QMetaObject::invokeMethod(object.get(), "createChild")); object.reset(); engine.setOutputWarningsToStandardError(false); QCOMPARE(engine.outputWarningsToStandardError(), false); QVERIFY2(messageHandler.messages().isEmpty(), qPrintable(messageHandler.messageString())); } void tst_qqmlcomponent::async_data() { // We cannot really test for the number of progress changes since the TCP protocol will do // its own thing with the chunks we send. By creating smaller chunks we merely increase the // chances of them being sent separately. QTest::addColumn("chunkSize"); QTest::addRow("all in one") << std::numeric_limits::max(); QTest::addRow("512 bytes") << qsizetype(512); // Anything smaller than this will probably make the test time out on some platform. QTest::addRow("128 bytes") << qsizetype(128); } void tst_qqmlcomponent::async() { QFETCH(qsizetype, chunkSize); TestHTTPServer server; server.setChunkSize(chunkSize); QVERIFY2(server.listen(), qPrintable(server.errorString())); server.serveDirectory(dataDirectory()); QQmlEngine engine; QQmlComponent component(&engine); ComponentWatcher watcher(&component); component.loadUrl(server.url("http://code.qt.io/TestComponent.qml"), QQmlComponent::Asynchronous); QCOMPARE(watcher.loading, 1); QTRY_VERIFY(component.isReady()); QCOMPARE(watcher.ready, 1); QCOMPARE(watcher.error, 0); QVERIFY(watcher.progresses.length() >= 2); QCOMPARE(watcher.progresses.first(), 0); QCOMPARE(watcher.progresses.last(), 1); for (qsizetype i = 1, end = watcher.progresses.length(); i < end; ++i) QVERIFY(watcher.progresses[i - 1] < watcher.progresses[i]); std::unique_ptr object { component.create() }; QVERIFY(object != nullptr); } void tst_qqmlcomponent::asyncHierarchy() { TestHTTPServer server; QVERIFY2(server.listen(), qPrintable(server.errorString())); server.serveDirectory(dataDirectory()); // ensure that the item hierarchy is compiled correctly. QQmlComponent component(&engine); ComponentWatcher watcher(&component); component.loadUrl(server.url("http://code.qt.io/TestComponent.2.qml"), QQmlComponent::Asynchronous); QCOMPARE(watcher.loading, 1); QTRY_VERIFY(component.isReady()); QCOMPARE(watcher.ready, 1); QCOMPARE(watcher.error, 0); std::unique_ptr root { component.create() }; QVERIFY(root != nullptr); // ensure that the parent-child relationship hierarchy is correct // (use QQuickItem* for all children rather than types which are not publicly exported) QQuickItem *c1 = root->findChild("c1", Qt::FindDirectChildrenOnly); QVERIFY(c1); QQuickItem *c1c1 = c1->findChild("c1c1", Qt::FindDirectChildrenOnly); QVERIFY(c1c1); QQuickItem *c1c2 = c1->findChild("c1c2", Qt::FindDirectChildrenOnly); QVERIFY(c1c2); QQuickItem *c1c2c3 = c1c2->findChild("c1c2c3", Qt::FindDirectChildrenOnly); QVERIFY(c1c2c3); QQuickItem *c2 = root->findChild("c2", Qt::FindDirectChildrenOnly); QVERIFY(c2); QQuickItem *c2c1 = c2->findChild("c2c1", Qt::FindDirectChildrenOnly); QVERIFY(c2c1); QQuickItem *c2c1c1 = c2c1->findChild("c2c1c1", Qt::FindDirectChildrenOnly); QVERIFY(c2c1c1); QQuickItem *c2c1c2 = c2c1->findChild("c2c1c2", Qt::FindDirectChildrenOnly); QVERIFY(c2c1c2); // ensure that values and bindings are assigned correctly QVERIFY(root->property("success").toBool()); } void tst_qqmlcomponent::asyncForceSync() { { // 1) make sure that HTTP URLs cannot be completed synchronously TestHTTPServer server; QVERIFY2(server.listen(), qPrintable(server.errorString())); server.serveDirectory(dataDirectory()); // ensure that the item hierarchy is compiled correctly. QQmlComponent component(&engine); component.loadUrl(server.url("http://code.qt.io/TestComponent.2.qml"), QQmlComponent::Asynchronous); QCOMPARE(component.status(), QQmlComponent::Loading); QQmlComponent component2(&engine, server.url("http://code.qt.io/TestComponent.2.qml"), QQmlComponent::PreferSynchronous); QCOMPARE(component2.status(), QQmlComponent::Loading); } { // 2) make sure that file:// URL can be completed synchronously // ensure that the item hierarchy is compiled correctly. QQmlComponent component(&engine); component.loadUrl(testFileUrl("/TestComponent.2.qml"), QQmlComponent::Asynchronous); QCOMPARE(component.status(), QQmlComponent::Loading); QQmlComponent component2(&engine, testFileUrl("/TestComponent.2.qml"), QQmlComponent::PreferSynchronous); QCOMPARE(component2.status(), QQmlComponent::Ready); QCOMPARE(component.status(), QQmlComponent::Loading); QTRY_COMPARE_WITH_TIMEOUT(component.status(), QQmlComponent::Ready, 0); } } void tst_qqmlcomponent::componentUrlCanonicalization() { // ensure that url canonicalization succeeds so that type information // is not generated multiple times for the same component. { // load components via import QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("componentUrlCanonicalization.qml")); QScopedPointer object(component.create()); QVERIFY(object != nullptr); QVERIFY(object->property("success").toBool()); } { // load one of the components dynamically, which would trigger // import of the other if it were not already loaded. QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("componentUrlCanonicalization.2.qml")); QScopedPointer object(component.create()); QVERIFY(object != nullptr); QVERIFY(object->property("success").toBool()); } { // load components with more deeply nested imports QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("componentUrlCanonicalization.3.qml")); QScopedPointer object(component.create()); QVERIFY(object != nullptr); QVERIFY(object->property("success").toBool()); } { // load components with unusually specified import paths QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("componentUrlCanonicalization.4.qml")); QScopedPointer object(component.create()); QVERIFY(object != nullptr); QVERIFY(object->property("success").toBool()); } { // Do not crash with various nonsense import paths QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("componentUrlCanonicalization.5.qml")); QTest::ignoreMessage(QtWarningMsg, QLatin1String("QQmlComponent: Component is not ready").data()); QScopedPointer object(component.create()); QVERIFY(object.isNull()); } } void tst_qqmlcomponent::onDestructionLookup() { QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("onDestructionLookup.qml")); QScopedPointer object(component.create()); gc(engine); QVERIFY(object != nullptr); QVERIFY(object->property("success").toBool()); } void tst_qqmlcomponent::onDestructionCount() { QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("onDestructionCount.qml")); QLatin1String warning("Component.onDestruction"); { // Warning should be emitted during create() QTest::ignoreMessage(QtWarningMsg, warning.data()); QScopedPointer object(component.create()); QVERIFY(object != nullptr); } // Warning should not be emitted any further QCOMPARE(engine.outputWarningsToStandardError(), true); QStringList warnings; { QQmlTestMessageHandler messageHandler; QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); QCoreApplication::processEvents(); warnings = messageHandler.messages(); } engine.setOutputWarningsToStandardError(false); QCOMPARE(engine.outputWarningsToStandardError(), false); QCOMPARE(warnings.size(), 0); } void tst_qqmlcomponent::recursion() { QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("recursion.qml")); QVERIFY2(component.isReady(), qPrintable(component.errorString())); QTest::ignoreMessage(QtWarningMsg, QLatin1String("QQmlComponent: Component creation is recursing - aborting").data()); QScopedPointer object(component.create()); QVERIFY(object != nullptr); // Sub-object creation does not succeed QCOMPARE(object->property("success").toBool(), false); } void tst_qqmlcomponent::recursionContinuation() { QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("recursionContinuation.qml")); for (int i = 0; i < 10; ++i) QTest::ignoreMessage(QtWarningMsg, QLatin1String("QQmlComponent: Component creation is recursing - aborting").data()); QScopedPointer object(component.create()); QVERIFY(object != nullptr); // Eventual sub-object creation succeeds QVERIFY(object->property("success").toBool()); } void tst_qqmlcomponent::partialComponentCreation() { const int maxCount = 17; QQmlEngine engine; QScopedPointer components[maxCount]; QScopedPointer objects[maxCount]; QQmlTestMessageHandler messageHandler; QCOMPARE(engine.outputWarningsToStandardError(), true); for (int i = 0; i < maxCount; i++) { components[i].reset(new QQmlComponent(&engine, testFileUrl("QtObjectComponent.qml"))); objects[i].reset(components[i]->beginCreate(engine.rootContext())); QVERIFY(objects[i].isNull() == false); } QVERIFY2(messageHandler.messages().isEmpty(), qPrintable(messageHandler.messageString())); for (int i = 0; i < maxCount; i++) { components[i]->completeCreate(); } QVERIFY2(messageHandler.messages().isEmpty(), qPrintable(messageHandler.messageString())); } class CallingContextCheckingClass : public QObject { Q_OBJECT Q_PROPERTY(int value READ value WRITE setValue) public: CallingContextCheckingClass() : m_value(0) {} int value() const { return m_value; } void setValue(int v) { scopeObject.clear(); callingContextData.setContextData(nullptr); m_value = v; QJSEngine *jsEngine = qjsEngine(this); if (!jsEngine) return; QV4::ExecutionEngine *v4 = jsEngine->handle(); if (!v4) return; QV4::Scope scope(v4); QV4::Scoped qmlContext(scope, v4->qmlContext()); if (!qmlContext) return; callingContextData = qmlContext->qmlContext(); scopeObject = qmlContext->qmlScope(); } int m_value; QQmlGuardedContextData callingContextData; QPointer scopeObject; }; void tst_qqmlcomponent::callingContextForInitialProperties() { qmlRegisterType("qqmlcomponenttest", 1, 0, "CallingContextCheckingClass"); QQmlComponent testFactory(&engine, testFileUrl("callingQmlContextComponent.qml")); QQmlComponent component(&engine, testFileUrl("callingQmlContext.qml")); QScopedPointer root(component.beginCreate(engine.rootContext())); QVERIFY(!root.isNull()); root->setProperty("factory", QVariant::fromValue(&testFactory)); component.completeCreate(); QTRY_VERIFY(qvariant_cast(root->property("incubatedObject"))); QObject *o = qvariant_cast(root->property("incubatedObject")); CallingContextCheckingClass *checker = qobject_cast(o); QVERIFY(checker); QVERIFY(!checker->callingContextData.isNull()); QVERIFY(checker->callingContextData->urlString().endsWith(QStringLiteral("callingQmlContext.qml"))); QVERIFY(!checker->scopeObject.isNull()); QVERIFY(checker->scopeObject->metaObject()->indexOfProperty("incubatedObject") != -1); } void tst_qqmlcomponent::setNonExistentInitialProperty() { QQmlIncubationController controller; QQmlEngine engine; engine.setIncubationController(&controller); QQmlComponent component(&engine, testFileUrl("nonExistentInitialProperty.qml")); QScopedPointer obj(component.create()); QVERIFY(!obj.isNull()); QMetaObject::invokeMethod(obj.data(), "startIncubation"); QJSValue incubatorStatus = obj->property("incubator").value(); incubatorStatus.property("forceCompletion").callWithInstance(incubatorStatus); QJSValue objectWrapper = incubatorStatus.property("object"); QVERIFY(objectWrapper.isQObject()); QPointer object(objectWrapper.toQObject()); QVERIFY(object->property("ok").toBool()); } void tst_qqmlcomponent::relativeUrl_data() { QTest::addColumn("url"); #if !defined(Q_OS_ANDROID) && !defined(BUILTIN_TESTDATA) QTest::addRow("fromLocalFile") << QUrl::fromLocalFile("data/QtObjectComponent.qml"); QTest::addRow("fromLocalFileHash") << QUrl::fromLocalFile("data/QtObjectComponent#2.qml"); QTest::addRow("constructor") << QUrl("data/QtObjectComponent.qml"); #endif QTest::addRow("absolute") << QUrl::fromLocalFile(QFINDTESTDATA("data/QtObjectComponent.qml")); QTest::addRow("qrc") << QUrl("qrc:/data/QtObjectComponent.qml"); } void tst_qqmlcomponent::relativeUrl() { QFETCH(QUrl, url); QQmlComponent component(&engine); // Shouldn't assert in QQmlTypeLoader; we want QQmlComponent to assume that // data/QtObjectComponent.qml refers to the data/QtObjectComponent.qml in the current working directory. component.loadUrl(url); QVERIFY2(!component.isError(), qPrintable(component.errorString())); } void tst_qqmlcomponent::setDataNoEngineNoSegfault() { QQmlEngine eng; QQmlComponent comp; QTest::ignoreMessage(QtWarningMsg, "QQmlComponent: Must provide an engine before calling setData"); comp.setData("import QtQuick 1.0; QtObject { }", QUrl("")); QTest::ignoreMessage(QtWarningMsg, "QQmlComponent: Must provide an engine before calling create"); auto c = comp.create(); QVERIFY(!c); } class RequiredDefaultCpp : public QObject { Q_OBJECT public: Q_PROPERTY(QQuickItem *defaultProperty MEMBER m_defaultProperty NOTIFY defaultPropertyChanged REQUIRED) Q_SIGNAL void defaultPropertyChanged(); Q_CLASSINFO("DefaultProperty", "defaultProperty") private: QQuickItem *m_defaultProperty = nullptr; }; class TwoRequiredProperties : public QObject { Q_OBJECT Q_PROPERTY(int index READ index WRITE setIndex NOTIFY indexChanged REQUIRED) Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged REQUIRED) int m_index = 0; QString m_name; public: TwoRequiredProperties(QObject *parent = nullptr) : QObject(parent) { } int index() const { return m_index; } QString name() const { return m_name; } void setIndex(int x) { if (m_index == x) return; m_index = x; Q_EMIT indexChanged(); } void setName(const QString &x) { if (m_name == x) return; m_name = x; Q_EMIT nameChanged(); } Q_SIGNALS: void indexChanged(); void nameChanged(); }; class ShadowedRequiredProperty : public TwoRequiredProperties { Q_OBJECT Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged /* REQUIRED */) // Note: should be able to take methods from the base class }; class AttachedRequiredProperties : public QObject { Q_OBJECT QML_ATTACHED(TwoRequiredProperties) public: AttachedRequiredProperties(QObject *parent = nullptr) : QObject(parent) { } static TwoRequiredProperties *qmlAttachedProperties(QObject *parent) { return new TwoRequiredProperties(parent); } }; class GroupedRequiredProperties : public QObject { Q_OBJECT Q_PROPERTY(TwoRequiredProperties *group READ group) TwoRequiredProperties m_group; public: GroupedRequiredProperties(QObject *parent = nullptr) : QObject(parent) { } TwoRequiredProperties *group() { return &m_group; } }; class RequiredGroup : public QObject { Q_OBJECT // use QQuickPalette not to create an extra test-only Q_PROPERTY(QQuickPalette *group READ group REQUIRED) QQuickPalette m_group; public: RequiredGroup(QObject *parent = nullptr) : QObject(parent) { } QQuickPalette *group() { return &m_group; } }; void tst_qqmlcomponent::testRequiredProperties_data() { qmlRegisterType("qt.test", 1, 0, "RequiredDefaultCpp"); qmlRegisterType("qt.test", 1, 0, "TwoRequiredProperties"); qmlRegisterType("qt.test", 1, 0, "ShadowedRequiredProperty"); qmlRegisterUncreatableType( "qt.test", 1, 0, "AttachedRequiredProperty", tr("AttachedRequiredProperties is an attached property")); qmlRegisterType("qt.test", 1, 0, "GroupedRequiredProperty"); qmlRegisterType("qt.test", 1, 0, "RequiredGroup"); QTest::addColumn("testFile"); QTest::addColumn("shouldSucceed"); QTest::addColumn("errorMsg"); QTest::addRow("requiredSetViaChainedAlias") << testFileUrl("requiredSetViaChainedAlias.qml") << true << ""; QTest::addRow("requiredNotSet") << testFileUrl("requiredNotSet.qml") << false << "Required property i was not initialized"; QTest::addRow("requiredSetInSameFile") << testFileUrl("RequiredSetInSameFile.qml") << true << ""; QTest::addRow("requiredSetViaAlias1") << testFileUrl("requiredSetViaAliasBeforeSameFile.qml") << true << ""; QTest::addRow("requiredSetViaAlias2") << testFileUrl("requiredSetViaAliasAfterSameFile.qml") << true << ""; QTest::addRow("requiredSetViaAlias3") << testFileUrl("requiredSetViaAliasParentFile.qml") << true << ""; QTest::addRow("requiredSetInBase") << testFileUrl("requiredChildOfGoodBase.qml") << true << ""; QTest::addRow("requiredNotSetInBase") << testFileUrl("requiredChildOfBadBase.qml") << false << "Required property i was not initialized"; QTest::addRow("rerequiredSet") << testFileUrl("rerequiredSet.qml") << true << ""; QTest::addRow("rerequiredNotSet") << testFileUrl("RerequiredNotSet.qml") << false << "Required property i was not initialized"; QTest::addRow("requiredSetLater(rerequired)") << testFileUrl("requiredSetLater.rerequired.qml") << true << ""; QTest::addRow("rerequiredSetLater") << testFileUrl("rerequiredSetLater.qml") << true << ""; QTest::addRow("shadowing") << testFileUrl("shadowing.qml") << false << "Required property i was not initialized"; QTest::addRow("shadowing (C++)") << testFileUrl("shadowingFromCpp.qml") << false << "Required property name was not initialized"; QTest::addRow("shadowing (C++ indirect)") << testFileUrl("shadowingFromQmlChild.qml") << false << "Required property name was not initialized"; QTest::addRow("setLater") << testFileUrl("requiredSetLater.qml") << true << ""; QTest::addRow("setViaAliasToSubcomponent") << testFileUrl("setViaAliasToSubcomponent.qml") << true << ""; QTest::addRow("aliasToSubcomponentNotSet") << testFileUrl("aliasToSubcomponentNotSet.qml") << false << "It can be set via the alias property i_alias"; QTest::addRow("required default set") << testFileUrl("requiredDefault.1.qml") << true << ""; QTest::addRow("required default not set") << testFileUrl("requiredDefault.2.qml") << false << "Required property requiredDefault was not initialized"; QTest::addRow("required default set (C++)") << testFileUrl("requiredDefault.3.qml") << true << ""; QTest::addRow("required default not set (C++)") << testFileUrl("requiredDefault.4.qml") << false << "Required property defaultProperty was not initialized"; // QTBUG-96200: QTest::addRow("required two set one (C++)") << testFileUrl("requiredTwoProperties.qml") << false << "Required property name was not initialized"; QTest::addRow("required two set two (C++)") << testFileUrl("RequiredTwoPropertiesSet.qml") << true << ""; QTest::addRow("required two set two (C++ indirect)") << testFileUrl("requiredTwoPropertiesDummy.qml") << true << ""; QTest::addRow("required set (inline component)") << testFileUrl("requiredPropertyInlineComponent.good.qml") << true << ""; QTest::addRow("required not set (inline component)") << testFileUrl("requiredPropertyInlineComponent.bad.qml") << false << "Required property i was not initialized"; QTest::addRow("required set (inline component, C++)") << testFileUrl("requiredPropertyInlineComponentWithCppBase.good.qml") << true << ""; QTest::addRow("required not set (inline component, C++)") << testFileUrl("requiredPropertyInlineComponentWithCppBase.bad.qml") << false << "Required property name was not initialized"; QTest::addRow("required set (inline component, C++ indirect)") << testFileUrl("requiredPropertyInlineComponentWithIndirectCppBase.qml") << true << ""; QTest::addRow("required not set (attached)") << testFileUrl("requiredPropertiesInAttached.bad.qml") << false << "Attached property has required properties. This is not supported"; QTest::addRow("required two set one (attached)") << testFileUrl("requiredPropertiesInAttached.bad2.qml") << false << "Attached property has required properties. This is not supported"; QTest::addRow("required two set two (attached)") << testFileUrl("RequiredPropertiesInAttached.qml") << false << "Attached property has required properties. This is not supported"; QTest::addRow("required two set two (attached indirect)") << testFileUrl("requiredPropertiesInAttachedIndirect.qml") << false << "Attached property has required properties. This is not supported"; QTest::addRow("required itself not set (group)") << testFileUrl("requiredGroup.bad.qml") << false << "Required property group was not initialized"; QTest::addRow("required itself set (group)") << testFileUrl("requiredGroup.good.qml") << true << ""; QTest::addRow("required not set (group)") << testFileUrl("requiredPropertiesInGroup.bad.qml") << false << "Required property index was not initialized"; QTest::addRow("required two set one (group)") << testFileUrl("requiredPropertiesInGroup.bad2.qml") << false << "Required property name was not initialized"; QTest::addRow("required two set two (group)") << testFileUrl("RequiredPropertiesInGroup.qml") << true << ""; QTest::addRow("required two set two (group indirect)") << testFileUrl("requiredPropertiesInGroupIndirect.qml") << true << ""; } void tst_qqmlcomponent::testRequiredProperties() { QQmlEngine eng; using QScopedObjPointer = QScopedPointer; QFETCH(QUrl, testFile); QFETCH(bool, shouldSucceed); QQmlComponent comp(&eng); comp.loadUrl(testFile); QScopedObjPointer obj {comp.create()}; QEXPECT_FAIL("required not set (group)", "We fail to recognize required sub-properties inside a group property when that " "group property is unused (QTBUG-96544)", Abort); QEXPECT_FAIL("required two set one (group)", "We fail to recognized required sub-properties inside a group property, even when " "that group property is used (QTBUG-96544)", Abort); if (shouldSucceed) { QVERIFY2(comp.isReady(), qPrintable(comp.errorString())); QVERIFY(obj); } else { QVERIFY2(!obj, "The object is valid when it shouldn't be"); QFETCH(QString, errorMsg); QVERIFY2(comp.errorString().contains(errorMsg), qPrintable(comp.errorString())); } } void tst_qqmlcomponent::testRequiredPropertiesFromQml() { QQmlEngine eng; { QQmlComponent comp(&eng); comp.loadUrl(testFileUrl("createdFromQml.qml")); QScopedPointer obj { comp.create() }; QVERIFY(obj); auto root = qvariant_cast(obj->property("it")); QVERIFY(root); QCOMPARE(root->property("i").toInt(), 42); } { QTest::ignoreMessage(QtMsgType::QtWarningMsg, QRegularExpression(".*requiredNotSet.qml:4:5: Required property i was not initialized")); QQmlComponent comp(&eng); comp.loadUrl(testFileUrl("createdFromQmlFail.qml")); QScopedPointer obj { comp.create() }; QVERIFY(obj); QCOMPARE(qvariant_cast(obj->property("it")), nullptr); } } struct ComponentWithPublicSetInitial : QQmlComponent { using QQmlComponent::QQmlComponent; void setInitialProperties(QObject *o, QVariantMap map) { QQmlComponent::setInitialProperties(o, map); } }; void tst_qqmlcomponent::testSetInitialProperties() { QQmlEngine eng; { // QVariant ComponentWithPublicSetInitial comp(&eng); comp.loadUrl(testFileUrl("variantBasedInitialization.qml")); QScopedPointer obj { comp.beginCreate(eng.rootContext()) }; QVERIFY(obj); QUrl myurl = comp.url(); QFont myfont; QDateTime mydate = QDateTime::currentDateTime(); QPoint mypoint {1,2}; QSizeF mysize {0.5, 0.3}; QMatrix4x4 matrix {}; QQuaternion quat {5.0f, 0.3f, 0.2f, 0.1f}; QVector2D vec2 {2.0f, 3.1f}; QVector3D vec3 {1.0f, 2.0, 3.0f}; QVector4D vec4 {1.0f, 2.0f, 3.0f, 4.0f}; #define ASJSON(NAME) {QLatin1String(#NAME), NAME} comp.setInitialProperties(obj.get(), QVariantMap { {QLatin1String("i"), 42}, {QLatin1String("b"), true}, {QLatin1String("d"), 3.1416}, {QLatin1String("s"), QLatin1String("hello world")}, {QLatin1String("nothing"), QVariant::fromValue( nullptr)}, ASJSON(myurl), ASJSON(myfont), ASJSON(mydate), ASJSON(mypoint), ASJSON(mysize), ASJSON(matrix), ASJSON(quat), ASJSON(vec2), ASJSON(vec3), ASJSON(vec4) }); #undef ASJSON comp.completeCreate(); QVERIFY(comp.errors().empty()); QCOMPARE(obj->property("i"), 42); QCOMPARE(obj->property("b"), true); QCOMPARE(obj->property("d"), 3.1416); QCOMPARE(obj->property("s"), QLatin1String("hello world")); QCOMPARE(obj->property("nothing"), QVariant::fromValue(nullptr)); #define COMPARE(NAME) QCOMPARE(obj->property(#NAME), NAME) COMPARE(myurl); COMPARE(myfont); COMPARE(mydate); QCOMPARE(obj->property("mypoint"), QPointF(mypoint)); COMPARE(mysize); COMPARE(matrix); COMPARE(quat); COMPARE(vec2); COMPARE(vec3); COMPARE(vec4); #undef COMPARE } { // setInitialProperties: reject setting nested properties auto r = QRegularExpression(".*Setting initial properties failed: Cannot initialize " "nested property. To set a.b as an initial property, create a" ", set its property b, and pass a as an initial property."); QTest::ignoreMessage(QtWarningMsg, r); ComponentWithPublicSetInitial comp(&eng); comp.loadUrl(testFileUrl("allJSONTypes.qml")); // Any valid QML file QScopedPointer obj { comp.beginCreate(eng.rootContext()) }; comp.setInitialProperties(obj.get(), { { "a.b", 1 } }); comp.completeCreate(); } { // createWithInitialProperties convenience function QQmlComponent comp(&eng); comp.loadUrl(testFileUrl("requiredNotSet.qml")); QScopedPointer obj {comp.createWithInitialProperties( QVariantMap { {QLatin1String("i"), QJsonValue{42}} })}; QVERIFY(obj); QCOMPARE(obj->property("i"), 42); } { // createWithInitialProperties: setting a nonexistent property QQmlComponent comp(&eng); comp.loadUrl(testFileUrl("allJSONTypes.qml")); const QRegularExpression errorMessage { QStringLiteral( ".*allJSONTypes.qml: Setting initial properties failed: Item does not have a " "property called notThePropertiesYoureLookingFor") }; QTest::ignoreMessage(QtMsgType::QtWarningMsg, errorMessage); QScopedPointer obj { comp.createWithInitialProperties(QVariantMap { {"notThePropertiesYoureLookingFor", 42} }) }; QVERIFY(obj); QVERIFY(comp.isReady()); // despite the error, the component is still ready // QTBUG-101439: repeated creation succeeds as well QScopedPointer objEmpty { comp.create() }; QVERIFY(objEmpty); } { QQmlComponent comp(&eng); comp.loadUrl(testFileUrl("requiredNotSet.qml")); QTest::ignoreMessage( QtMsgType::QtWarningMsg, QRegularExpression(".*requiredNotSet.qml: Setting initial properties failed: Item " "does not have a property called not_i")); QScopedPointer obj { comp.createWithInitialProperties( QVariantMap { { QLatin1String("not_i"), QJsonValue { 42 } } }) }; QVERIFY(!obj); QVERIFY(comp.isError()); QVERIFY(comp.errorString().contains("Required property i was not initialized")); QScopedPointer objGood { comp.createWithInitialProperties( QVariantMap { { QLatin1String("i"), QJsonValue { 42 } } }) }; QVERIFY2(objGood, qPrintable(comp.errorString())); QCOMPARE(objGood->property("i"), 42); } // QJSValue unpacking - QTBUG-101440 { QQmlComponent comp(&eng); comp.setData(R"( import QtQml QtObject { property int x property int y: func ? func() : -1 property var func // special } )", QUrl()); QJSValue data = eng.evaluate("({ \"x\": 42, \"func\": (function() { return 42; }) })"); QVERIFY(data.isObject()); QVariant var = data.toVariant(); QCOMPARE(var.typeId(), QMetaType::QVariantMap); QVariantMap properties = var.toMap(); QScopedPointer object { comp.createWithInitialProperties(properties) }; QVERIFY(object); QCOMPARE(object->property("x"), 42); QCOMPARE(object->property("y"), 42); QJSValue func = eng.toScriptValue(object->property("func")); QVERIFY(func.isCallable()); QCOMPARE(func.call().toInt(), 42); } } void tst_qqmlcomponent::createInsideJSModule() { QQmlEngine engine; QString prefix; #if defined(Q_OS_ANDROID) || defined(BUILTIN_TESTDATA) prefix = "qrc:/"; #endif QQmlComponent component(&engine, testFileUrl("jsmodule/test.qml")); QScopedPointer root(component.createWithInitialProperties({{"prefix", prefix}})); QVERIFY2(root, qPrintable(component.errorString())); QVERIFY(root->property("ok").toBool()); } void tst_qqmlcomponent::qmlErrorIsReported() { struct LogControl { LogControl() { QLoggingCategory::setFilterRules("qt.qml.diskcache.debug=true"); } ~LogControl() { QLoggingCategory::setFilterRules(QString()); } }; LogControl lc; Q_UNUSED(lc); QRegularExpression errorMessage( R"(.*Cannot assign to non-existent property.*onSomePropertyChanged.*)"); QTest::ignoreMessage(QtDebugMsg, errorMessage); QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("qmlWithError.qml")); QScopedPointer root(component.create()); QVERIFY(root == nullptr); QVERIFY(component.isError()); const auto componentErrors = component.errors(); QVERIFY(std::any_of(componentErrors.begin(), componentErrors.end(), [&](const QQmlError &e) { return errorMessage.match(e.toString()).hasMatch(); })); } void tst_qqmlcomponent::initJSValueProp() { qmlRegisterType("ComponentTest", 1, 0, "WithQJSValue"); QQmlEngine engine; QQmlComponent component(&engine); component.setData("import ComponentTest\nWithQJSValue {}", QUrl()); QVERIFY2(component.isReady(), qPrintable(component.errorString())); QScopedPointer o(component.createWithInitialProperties({{ u"v"_s, 5}})); QVERIFY(!o.isNull()); WithQJSValue *withQJSValue = qobject_cast(o.data()); QVERIFY(withQJSValue); const QJSValue jsValue = withQJSValue->v(); QVERIFY(jsValue.isNumber()); QCOMPARE(jsValue.toInt(), 5); } void tst_qqmlcomponent::qmlPropertySignalExists() { QQmlEngine engine; QQmlComponent component(&engine); component.setData("import QtQml; QtObject { property int p: 41; function doStuff() { p++; } }", QUrl()); QVERIFY2(component.isReady(), qPrintable(component.errorString())); QScopedPointer o(component.create()); QVERIFY(!o.isNull()); QSignalSpy changeSignalSpy(o.get(), SIGNAL(pChanged())); QVERIFY(QMetaObject::invokeMethod(o.get(), "doStuff")); QCOMPARE(changeSignalSpy.size(), 1); QCOMPARE(o->property("p").toInt(), 42); } void tst_qqmlcomponent::componentTypes() { { QQmlEngine engine; QQmlComponent component(&engine); // not allowed: "Cannot create empty component specification" component.setData("import QtQml; Component { }", QUrl()); QVERIFY(!component.isReady()); } { QQmlEngine engine; QQmlComponent component(&engine); component.loadUrl(testFileUrl("ComponentType.qml")); QScopedPointer o(component.create()); QVERIFY2(!o.isNull(), qPrintable(component.errorString())); QQmlComponent *oComponent = qobject_cast(o.get()); QVERIFY(oComponent); QScopedPointer enclosed(oComponent->create()); QVERIFY(!enclosed.isNull()); QCOMPARE(enclosed->objectName(), u"enclosed"_s); } { QQmlEngine engine; QQmlComponent component(&engine); component.loadUrl(testFileUrl("componentTypes.qml")); QScopedPointer o(component.create()); QVERIFY2(!o.isNull(), qPrintable(component.errorString())); QQmlContext *ctx = engine.contextForObject(o.get()); QObject *normal = ctx->objectForName(u"normal"_s); QVERIFY(normal); QCOMPARE(normal->property("text").toString(), u"indirect component"_s); // check (and thus "document" in code) various ways of how ids work QVERIFY(ctx->objectForName(u"accessibleNormal"_s)); QVERIFY(!ctx->objectForName(u"inaccessibleNormal"_s)); QVERIFY(ctx->objectForName(u"accessible"_s)); QVERIFY(!ctx->objectForName(u"inaccessible"_s)); QVERIFY(ctx->objectForName(u"accessibleDelegate"_s)); QVERIFY(!ctx->objectForName(u"inaccessibleDelegate"_s)); QCOMPARE(qvariant_cast(o->property("p2"))->property("text").toString(), u"foo"_s); auto p3Object = qvariant_cast(o->property("p3")); QVERIFY(p3Object); QVERIFY(p3Object->property("text").toString().isEmpty()); QQmlComponent *normalComponent = qobject_cast(normal); QVERIFY(normalComponent); QScopedPointer enclosed(normalComponent->create()); QVERIFY(enclosed); QCOMPARE(enclosed->objectName(), u"enclosed"_s); } } void tst_qqmlcomponent::boundComponent() { QQmlEngine engine; { QQmlComponent component(&engine, testFileUrl("nestedBoundComponent.qml")); QVERIFY2(component.isReady(), qPrintable(component.errorString())); QVERIFY(component.isBound()); QScopedPointer o(component.create()); QVERIFY(!o.isNull()); QQmlComponent *nestedComponent = o->property("c").value(); QVERIFY(nestedComponent != nullptr); QVERIFY(nestedComponent->isBound()); QObject *nestedObject = o->property("o").value(); QVERIFY(nestedObject != nullptr); QCOMPARE(nestedObject->objectName(), QLatin1String("bound")); QScopedPointer contextedObject(nestedComponent->create(qmlContext(o.data()))); QVERIFY(!contextedObject.isNull()); QScopedPointer uncontextedObject(nestedComponent->create()); QVERIFY(uncontextedObject.isNull()); QVERIFY(nestedComponent->errorString().contains( QLatin1String("Cannot instantiate bound component outside its creation context"))); } { QQmlComponent component(&engine, testFileUrl("BoundInlineComponent.qml")); QVERIFY2(component.isReady(), qPrintable(component.errorString())); QVERIFY(component.isBound()); QScopedPointer o(component.create()); QVERIFY2(!o.isNull(), qPrintable(component.errorString())); QObject *nestedObject = o->property("o").value(); QVERIFY(nestedObject != nullptr); QCOMPARE(nestedObject->objectName(), QLatin1String("inline")); } { QQmlComponent component(&engine, testFileUrl("boundInlineComponentUser.qml")); QVERIFY2(component.isReady(), qPrintable(component.errorString())); QVERIFY(!component.isBound()); QScopedPointer o(component.create()); QVERIFY(o.isNull()); QVERIFY(component.errorString().contains( QLatin1String("Cannot instantiate bound inline component in different file"))); } { QQmlComponent component(&engine); QVERIFY(!component.isBound()); component.setData("pragma ComponentBehavior: Bound\nsyntax error", QUrl()); QCOMPARE(component.errorString(), ":2 Syntax error\n"_L1); QVERIFY(!component.isBound()); } } void tst_qqmlcomponent::loadFromModule_data() { using namespace Qt::StringLiterals; QTest::addColumn("uri"); QTest::addColumn("typeName"); QTest::addColumn("classNameRe"); QTest::addRow("Item") << u"QtQuick"_s << u"Item"_s << u"QQuickItem"_s; #if defined(HAS_CONTROLS) QTest::addRow("Button") << u"QtQuick.Controls"_s << u"Button"_s << u"Button_QMLTYPE_\\d+"_s; QTest::addRow("Basic.Button") << u"QtQuick.Controls.Basic"_s << u"Button"_s << u"Button_QMLTYPE_\\d+"_s; #endif QTest::addRow("IC") << u"test"_s << u"TestComponentWithIC"_s << u"TestComponentWithIC"_s; // sanity check for next test QTest::addRow("InnerIC") << u"test"_s << u"TestComponentWithIC.InnerIC"_s << u"InnerIC"_s; QTest::addRow("plainQML") << u"plainqml"_s << u"Plain"_s << u"Plain"_s; } void tst_qqmlcomponent::loadFromModule() { QFETCH(QString, uri); QFETCH(QString, typeName); QFETCH(QString, classNameRe); QQmlEngine engine; QQmlComponent component(&engine); QCOMPARE(component.progress(), 0); QSignalSpy progressSpy(&component, &QQmlComponent::progressChanged); component.loadFromModule(uri, typeName); QVERIFY2(component.isReady(), qPrintable(component.errorString())); // verify that we changed the progress correctly to 1 QVERIFY(!progressSpy.isEmpty() || progressSpy.wait()); QCOMPARE(progressSpy.last().at(0).toDouble(), 1.0); std::unique_ptr object(component.create()); QVERIFY(object); QRegularExpression classNameMatcher(classNameRe); const char *name = object->metaObject()->className(); QVERIFY2(classNameMatcher.match(name).hasMatch(), name); } void tst_qqmlcomponent::loadFromModuleSyncAndAsync_data() { loadFromModule_data(); } void tst_qqmlcomponent::loadFromModuleSyncAndAsync() { QFETCH(QString, uri); QFETCH(QString, typeName); QQmlEngine engine; QQmlComponent syncAndAsync(&engine); syncAndAsync.loadFromModule(uri, typeName, QQmlComponent::Asynchronous); QVERIFY(syncAndAsync.isLoading()); syncAndAsync.loadFromModule(uri, typeName, QQmlComponent::PreferSynchronous); QVERIFY2(syncAndAsync.isReady(), qPrintable(syncAndAsync.errorString())); std::unique_ptr object(syncAndAsync.create()); QVERIFY(object); } void tst_qqmlcomponent::loadFromModuleLifecycle() { QQmlEngine engine; const QString text = "text"_L1; const QList expected {1, 2, 3}; { QQmlComponent component(&engine); component.loadFromModule("test", "LifeCycleWatcher"); QVERIFY2(component.isReady(), qPrintable(component.errorString())); std::unique_ptr root{ component.create() }; LifeCycleWatcher *watcher = qobject_cast(root.get()); QVERIFY(watcher); QCOMPARE(watcher->states, expected); QCOMPARE(watcher->observedTexts, QStringList(3)); const QString loaded = "load from module"_L1; root.reset(component.createWithInitialProperties(QVariantMap{{text, loaded}})); watcher = qobject_cast(root.get()); QVERIFY(watcher); QCOMPARE(watcher->states, expected); QCOMPARE(watcher->observedTexts, QStringList({QString(), loaded, loaded})); } { QQmlComponent component(&engine); component.setData("import test; LifeCycleWatcher {}", {}); QVERIFY2(component.isReady(), qPrintable(component.errorString())); std::unique_ptr root{ component.create() }; LifeCycleWatcher *watcher = qobject_cast(root.get()); QVERIFY(watcher); QCOMPARE(watcher->states, expected); QCOMPARE(watcher->observedTexts, QStringList(3)); const QString loaded = "load from data"_L1; root.reset(component.createWithInitialProperties(QVariantMap{{text, loaded}})); watcher = qobject_cast(root.get()); QVERIFY(watcher); QCOMPARE(watcher->states, expected); QCOMPARE(watcher->observedTexts, QStringList({QString(), loaded, loaded})); } { QQmlComponent component(&engine); const QString compiled = "inline"_L1; component.setData("import test; LifeCycleWatcher { text: 'inline' }", {}); QVERIFY2(component.isReady(), qPrintable(component.errorString())); std::unique_ptr root{ component.create() }; LifeCycleWatcher *watcher = qobject_cast(root.get()); QVERIFY(watcher); QCOMPARE(watcher->states, expected); QCOMPARE(watcher->observedTexts, QStringList({QString(), compiled, compiled})); const QString loaded = "overridden"_L1; std::unique_ptr withProperties( component.createWithInitialProperties(QVariantMap{{text, loaded}})); watcher = qobject_cast(withProperties.get()); QVERIFY(watcher); QCOMPARE(watcher->states, expected); QCOMPARE(watcher->observedTexts, QStringList({QString(), loaded, loaded})); } } struct CallVerifyingIncubtor : QQmlIncubator { void setInitialState(QObject *) override { setInitialStateCalled = true; } void statusChanged(QQmlIncubator::Status status) override { lastStatus = status; } QQmlIncubator::Status lastStatus = QQmlIncubator::Null; bool setInitialStateCalled = false; }; void tst_qqmlcomponent::loadFromModuleThenCreateWithIncubator() { QQmlEngine engine; QQmlComponent comp(&engine); comp.loadFromModule("QtQuick", "Rectangle"); CallVerifyingIncubtor incubator; comp.create(incubator); std::unique_ptr object { incubator.object() }; QVERIFY(incubator.setInitialStateCalled); QVERIFY(incubator.isReady()); QCOMPARE(incubator.lastStatus, QQmlIncubator::Ready); QCOMPARE(object->metaObject()->className(), "QQuickRectangle"); } void tst_qqmlcomponent::loadFromModuleFailures_data() { QTest::addColumn("uri"); QTest::addColumn("typeName"); QTest::addColumn("errorMsg"); QTest::addRow("noSuchModule") << "Does.Not.Exist" << "Type" << "No module named \"Does.Not.Exist\" found"; QTest::addRow("noSuchType") << "QtQml" << "NoSuchType" << "Module \"QtQml\" contains no type named \"NoSuchType\""; QTest::addRow("CppSingleton") << u"QtQuick"_s << u"Application"_s << u"Application is a singleton, and cannot be loaded"_s; QTest::addRow("passedFileName") << "plainqml" << "Plain.qml" << R"(Type "Plain" from module "plainqml" contains no inline component named "qml". )" R"(To load the type "Plain", drop the ".qml" extension.)"; } void tst_qqmlcomponent::loadFromModuleFailures() { QFETCH(QString, uri); QFETCH(QString, typeName); QFETCH(QString, errorMsg); QQmlEngine engine; QQmlComponent component(&engine); QSignalSpy errorSpy(&component, &QQmlComponent::statusChanged); QSignalSpy progressSpy(&component, &QQmlComponent::progressChanged); component.loadFromModule(uri, typeName); // verify that we changed the progress correctly to 1 QTRY_VERIFY(!progressSpy.isEmpty()); QTRY_COMPARE(progressSpy.last().at(0).toDouble(), 1.0); QVERIFY(!errorSpy.isEmpty()); QCOMPARE(errorSpy.first().first().value(), QQmlComponent::Error); QVERIFY(!component.errors().isEmpty()); QCOMPARE(component.errors().constFirst().description(), errorMsg); } struct SingleRequiredProperty : QObject { Q_OBJECT Q_PROPERTY(int i MEMBER i REQUIRED) int i = 42; }; struct SingleRequiredPropertyDynamic : QObject { Q_OBJECT Q_PROPERTY(int i MEMBER i REQUIRED) int i = 42; class QObjectDynamicMetaObject : public QDynamicMetaObjectData { public: #if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0) const QMetaObject *toDynamicMetaObject(QObject *) const final { return &SingleRequiredPropertyDynamic::staticMetaObject; } #else QMetaObject *toDynamicMetaObject(QObject *) final { return const_cast(&SingleRequiredPropertyDynamic::staticMetaObject); } #endif int metaCall(QObject *o, QMetaObject::Call c, int id, void **argv) final { return o->qt_metacall(c, id, argv); } }; public: SingleRequiredPropertyDynamic() { auto priv = QObjectPrivate::get(this); priv->metaObject = new QObjectDynamicMetaObject; } ~SingleRequiredPropertyDynamic() { auto priv = QObjectPrivate::get(this); delete priv->metaObject; priv->metaObject = nullptr ; } }; void tst_qqmlcomponent::loadFromModuleRequired() { QQmlEngine engine; qmlRegisterType("qqmlcomponenttest", 1, 0, "SingleRequiredProperty"); qmlRegisterType("qqmlcomponenttest", 1, 0, "SingleRequiredPropertyDynamic"); const QString error = QStringLiteral("Required property i was not initialized"); { QQmlComponent component(&engine, "qqmlcomponenttest", "SingleRequiredProperty"); QVERIFY2(!component.isError(), qPrintable(component.errorString())); QScopedPointer root(component.create()); QVERIFY(!root); QVERIFY(component.isError()); QCOMPARE(component.errorString(), qPrintable(":-1 " + error + "\n")); } { QQmlComponent component(&engine, "qqmlcomponenttest", "SingleRequiredProperty"); QVERIFY2(!component.isError(), qPrintable(component.errorString())); QScopedPointer root(component.beginCreate(engine.rootContext())); QVERIFY(!root.isNull()); QVERIFY2(!component.isError(), qPrintable(component.errorString())); // forgetting to set the required properties ... component.completeCreate(); // ... produces an error. QVERIFY(component.isError()); QCOMPARE(component.errorString(), qPrintable(":-1 " + error + "\n")); } { QQmlComponent component(&engine, "qqmlcomponenttest", "SingleRequiredPropertyDynamic"); QVERIFY2(!component.isError(), qPrintable(component.errorString())); QScopedPointer root(component.create()); QEXPECT_FAIL("", "Can't check required properties when there's a dynamic metaobject", Continue); QVERIFY(!root); } } void tst_qqmlcomponent::loadUrlRequired() { QQmlEngine engine; qmlRegisterType("qqmlcomponenttest2", 1, 0, "SingleRequiredProperty"); const QUrl url = testFileUrl("loadUrlRequired.qml"); const QString error = QStringLiteral("Required property i was not initialized"); { QQmlComponent component(&engine); component.loadUrl(url); QVERIFY2(!component.isError(), qPrintable(component.errorString())); QScopedPointer root(component.create()); QVERIFY(!root); QVERIFY(component.isError()); QCOMPARE(component.errorString(), qPrintable(url.toString() + ":2 " + error + "\n")); } { QQmlComponent component(&engine); component.loadUrl(url); QVERIFY2(!component.isError(), qPrintable(component.errorString())); QScopedPointer root(component.beginCreate(engine.rootContext())); QVERIFY(!root.isNull()); QVERIFY2(!component.isError(), qPrintable(component.errorString())); // forgetting to set the required properties ... component.completeCreate(); // ... produces an error. QVERIFY(component.isError()); QCOMPARE(component.errorString(), qPrintable(url.toString() + ":2 " + error + "\n")); } } void tst_qqmlcomponent::loadFromQrc() { QQmlEngine engine; QQmlComponent component(&engine, QStringLiteral(":/qt/qml/test/data/withAot.qml")); QVERIFY2(component.isReady(), qPrintable(component.errorString())); QQmlComponentPrivate *p = QQmlComponentPrivate::get(&component); QVERIFY(p); QVERIFY(p->compilationUnit()); QVERIFY(p->compilationUnit()->baseCompilationUnit()->aotCompiledFunctions); } void tst_qqmlcomponent::loadFromRelativeQrcPath() { QQmlEngine e; QQmlComponent c(&e); const QUrl url = QUrl("qrc:main.qml"); QTest::ignoreMessage( QtMsgType::QtWarningMsg, "QQmlComponent: attempted to load via a relative URL '%1' in resource file system. " "This is not fully supported and may not work"_L1.arg(url.toString()) .toLocal8Bit() .data()); c.loadUrl(url); } void tst_qqmlcomponent::removeBinding() { QQmlEngine e; const QUrl url = testFileUrl("removeBinding.qml"); QQmlComponent c(&e, url); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QTest::ignoreMessage( QtWarningMsg, qPrintable(url.toString() + QStringLiteral(":7:27: QML Component: Unsuitable arguments " "passed to createObject(). The first argument " "should be a QObject* or null, and the second " "argument should be a JavaScript object or a " "QVariantMap"))); QScopedPointer o(c.create()); QVERIFY(!o.isNull()); QCOMPARE(o->property("result"), QStringLiteral("42")); QCOMPARE(o->property("result2"), QStringLiteral("43")); } void tst_qqmlcomponent::complexObjectArgument() { QQmlEngine e; QQmlComponent c(&e, testFileUrl("complexObjectArgument.qml")); QVERIFY2(c.isReady(), qPrintable(c.errorString())); QScopedPointer o(c.create()); QVERIFY(!o.isNull()); QCOMPARE(o->objectName(), QStringLiteral("26 - 25")); } void tst_qqmlcomponent::bindingEvaluationOrder() { // Note: This test explicitly tests the order in which bindings are // evaluated, which is generally unspecified. This, however, exists // as a regression test for QQmlObjectCreator code that is supposed // to *not* mess with the QmlIR given to it. QQmlEngine engine; QQmlComponent component(&engine); component.setData(R"( import QtQml QtObject { property var myList: ["dummy"] property int p1: { myList.push("p1"); return 0; } property int p2: { myList.push("p2"); return 0; } })", QUrl()); QVERIFY2(component.isReady(), qPrintable(component.errorString())); QScopedPointer o(component.create()); QVERIFY(!o.isNull()); const QList myList = o->property("myList").toList(); QCOMPARE(myList.size(), 3); QCOMPARE(myList[0].toString(), u"dummy"_s); QCOMPARE(myList[1].toString(), u"p1"_s); QCOMPARE(myList[2].toString(), u"p2"_s); } void tst_qqmlcomponent::compilationUnitsWithSameUrl() { QQmlEngine engine; engine.setUiLanguage("de_CH"); std::vector> objects; for (int i = 0; i < 10; ++i) { QQmlComponent component(&engine); component.setData(R"( import QtQml QtObject { function returnThing() : string { return Qt.uiLanguage } } )", QUrl("duplicate.qml")); QVERIFY2(component.isReady(), qPrintable(component.errorString())); std::unique_ptr o(component.create()); QVERIFY(o.get()); QString result; QMetaObject::invokeMethod(o.get(), "returnThing", Q_RETURN_ARG(QString, result)); QCOMPARE(result, "de_CH"); objects.push_back(std::move(o)); } gc(engine); for (const auto &o: objects) { QString result; QMetaObject::invokeMethod(o.get(), "returnThing", Q_RETURN_ARG(QString, result)); QCOMPARE(result, "de_CH"); } } void tst_qqmlcomponent::bindingInRequired() { QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("bindingInRequired.qml")); QVERIFY2(component.isReady(), qPrintable(component.errorString())); QScopedPointer object(component.create()); QVERIFY(!object.isNull()); QObject *outer = object->property("outer").value(); QVERIFY(outer); QObject *inner = object->property("inner").value(); QVERIFY(inner); QCOMPARE(inner, outer->property("obj").value()); QVERIFY(!inner->property("obj").value()); } QTEST_MAIN(tst_qqmlcomponent) #include "tst_qqmlcomponent.moc"