Revision control
Copy as Markdown
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <qtest.h>
#include <QDebug>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlcomponent.h>
#include <QtQml/qqmlproperty.h>
#include <QtQml/qqmlincubator.h>
#include <QtQuick>
#include <QtQuick/private/qquickrectangle_p.h>
#include <QtQuick/private/qquickmousearea_p.h>
#include <QtQuick/private/qquickpalette_p.h>
#include <QtQuickTestUtils/private/qmlutils_p.h>
#include <QtQuickTestUtils/private/testhttpserver_p.h>
#include <private/qqmlcomponent_p.h>
#include <private/qqmldata_p.h>
#include <private/qqmlguardedcontextdata_p.h>
#include <private/qv4qmlcontext_p.h>
#include <private/qv4qmlcontext_p.h>
#include <private/qv4scopedvalue_p.h>
#include <private/qv4executablecompilationunit_p.h>
#include <qcolor.h>
#include <qsignalspy.h>
#include "lifecyclewatcher.h"
#include <QtGui/qquaternion.h>
#include <algorithm>
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<qreal> 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();
void repeatedSetDataWithInlineComponent();
void setInitialPropertyInteraction();
void invalidBaseUrl();
void uselessGroupedProperty();
void componentCompilationUnitSetter();
void setDataSynchronous();
void setDataAsynchronous();
void createWithInitialPropertiesRequiredAlias();
void groupedPropertyDoesNotOverwriteCompilationUnit();
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<QObject> 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<QQuickWindow> window(qobject_cast<QQuickWindow *>(component.create()));
QVERIFY(!window.isNull());
}
void tst_qqmlcomponent::qmlCreateObjectAutoParent_data()
{
QTest::addColumn<QString>("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<QObject> root(qobject_cast<QQuickItem *>(component.create()));
QVERIFY(!root.isNull());
QObject *qtobjectParent = root->property("qtobjectParent").value<QObject*>();
QQuickItem *itemParent = qobject_cast<QQuickItem *>(root->property("itemParent").value<QObject*>());
QQuickWindow *windowParent = qobject_cast<QQuickWindow *>(root->property("windowParent").value<QObject*>());
QVERIFY(qtobjectParent);
QVERIFY(itemParent);
QVERIFY(windowParent);
QObject *qtobject_qtobject = root->property("qtobject_qtobject").value<QObject*>();
QObject *qtobject_item = root->property("qtobject_item").value<QObject*>();
QObject *qtobject_window = root->property("qtobject_window").value<QObject*>();
QObject *item_qtobject = root->property("item_qtobject").value<QObject*>();
QObject *item_item = root->property("item_item").value<QObject*>();
QObject *item_window = root->property("item_window").value<QObject*>();
QObject *window_qtobject = root->property("window_qtobject").value<QObject*>();
QObject *window_item = root->property("window_item").value<QObject*>();
QObject *window_window = root->property("window_window").value<QObject*>();
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<QQuickItem *>(qtobject_item)->parentItem(), (QQuickItem *)nullptr);
QCOMPARE(qobject_cast<QQuickWindow *>(qtobject_window)->transientParent(), (QQuickWindow *)nullptr);
QCOMPARE(qobject_cast<QQuickItem *>(item_item)->parentItem(), itemParent);
QCOMPARE(qobject_cast<QQuickWindow *>(item_window)->transientParent(), itemParent->window());
QCOMPARE(qobject_cast<QQuickItem *>(window_item)->parentItem(), windowParent->contentItem());
QCOMPARE(qobject_cast<QQuickWindow *>(window_window)->transientParent(), windowParent);
}
void tst_qqmlcomponent::qmlCreateObjectWithProperties()
{
QQmlEngine engine;
QTest::failOnWarning();
QTest::ignoreMessage(
QtMsgType::QtWarningMsg,
QRegularExpression(
".*createObjectWithScript.qml:46:13: Required property i was not initialized"));
QQmlComponent component(&engine, testFileUrl("createObjectWithScript.qml"));
QVERIFY2(component.errorString().isEmpty(), component.errorString().toUtf8());
QScopedPointer<QObject> object(component.create());
QVERIFY(!object.isNull());
{
QScopedPointer<QObject> testObject1(object->property("declarativerectangle")
.value<QObject*>());
QVERIFY(testObject1);
QCOMPARE(testObject1->parent(), object.data());
QCOMPARE(testObject1->property("x").value<int>(), 17);
QCOMPARE(testObject1->property("y").value<int>(), 17);
QCOMPARE(testObject1->property("color").value<QColor>(), QColor(255,255,255));
QCOMPARE(QQmlProperty::read(testObject1.data(),"border.width").toInt(), 3);
QCOMPARE(QQmlProperty::read(testObject1.data(),"innerRect.border.width").toInt(), 20);
}
{
QScopedPointer<QObject> testObject2(object->property("declarativeitem").value<QObject*>());
QVERIFY(testObject2);
QCOMPARE(testObject2->parent(), object.data());
//QCOMPARE(testObject2->metaObject()->className(), "QDeclarativeItem_QML_2");
QCOMPARE(testObject2->property("x").value<int>(), 17);
QCOMPARE(testObject2->property("y").value<int>(), 17);
QCOMPARE(testObject2->property("testBool").value<bool>(), true);
QCOMPARE(testObject2->property("testInt").value<int>(), 17);
QCOMPARE(testObject2->property("testObject").value<QObject*>(), object.data());
}
{
QScopedPointer<QObject> testBindingObj(object->property("bindingTestObject")
.value<QObject*>());
QVERIFY(testBindingObj);
QCOMPARE(testBindingObj->parent(), object.data());
QCOMPARE(testBindingObj->property("testValue").value<int>(), 300);
object->setProperty("width", 150);
QCOMPARE(testBindingObj->property("testValue").value<int>(), 150 * 3);
}
{
QScopedPointer<QObject> testBindingThisObj(object->property("bindingThisTestObject")
.value<QObject*>());
QVERIFY(testBindingThisObj);
QCOMPARE(testBindingThisObj->parent(), object.data());
QCOMPARE(testBindingThisObj->property("testValue").value<int>(), 900);
testBindingThisObj->setProperty("width", 200);
QCOMPARE(testBindingThisObj->property("testValue").value<int>(), 200 * 3);
}
{
QScopedPointer<QObject> badRequired(object->property("badRequired").value<QObject *>());
QVERIFY(!badRequired);
QScopedPointer<QObject> goodRequired(object->property("goodRequired").value<QObject *>());
QVERIFY(goodRequired);
QCOMPARE(goodRequired->parent(), object.data());
QCOMPARE(goodRequired->property("i").value<int>(), 42);
QScopedPointer<QObject> goodRequired2(object->property("goodRequired2").value<QObject *>());
QVERIFY(goodRequired2);
QCOMPARE(goodRequired2->parent(), object.data());
QCOMPARE(goodRequired2->property("i").value<int>(), 43);
}
{
QScopedPointer<QObject> bindingAsInitial(object->property("bindingAsInitial").value<QObject *>());
QVERIFY(bindingAsInitial);
QVERIFY(object->property("bindingUsed").toBool());
}
}
void tst_qqmlcomponent::qmlCreateObjectClean()
{
QQmlEngine engine;
QVERIFY(engine.outputWarningsToStandardError());
QObject::connect(&engine, &QQmlEngine::warnings, this, [](const QList<QQmlError> &) {
QFAIL("Calls with suitable parameters should not generate any warnings.");
});
QQmlComponent component(&engine, testFileUrl("createObjectClean.qml"));
QVERIFY2(component.errorString().isEmpty(), component.errorString().toUtf8());
QScopedPointer<QObject> object(component.create());
QVERIFY(!object.isNull());
QVERIFY(qvariant_cast<QObject *>(object->property("a")) != nullptr);
QVERIFY(qvariant_cast<QObject *>(object->property("b")) != nullptr);
QVERIFY(qvariant_cast<QObject *>(object->property("c")) != nullptr);
QVERIFY(qvariant_cast<QObject *>(object->property("d")) != nullptr);
}
void tst_qqmlcomponent::qmlCreateObjectDirty()
{
QQmlEngine engine;
engine.setOutputWarningsToStandardError(false);
QObject::connect(&engine, &QQmlEngine::warnings, this, [](const QList<QQmlError> &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<QObject> object(component.create());
QVERIFY(!object.isNull());
QVERIFY(qvariant_cast<QObject *>(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<QObject> 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<qsizetype>("chunkSize");
QTest::addRow("all in one") << std::numeric_limits<qsizetype>::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("/TestComponent.qml"), QQmlComponent::Asynchronous);
QCOMPARE(watcher.loading, 1);
QTRY_VERIFY_WITH_TIMEOUT(component.isReady(), 3s);
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<QObject> 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("/TestComponent.2.qml"), QQmlComponent::Asynchronous);
QCOMPARE(watcher.loading, 1);
QTRY_VERIFY(component.isReady());
QCOMPARE(watcher.ready, 1);
QCOMPARE(watcher.error, 0);
std::unique_ptr<QObject> 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<QQuickItem*>("c1", Qt::FindDirectChildrenOnly);
QVERIFY(c1);
QQuickItem *c1c1 = c1->findChild<QQuickItem*>("c1c1", Qt::FindDirectChildrenOnly);
QVERIFY(c1c1);
QQuickItem *c1c2 = c1->findChild<QQuickItem*>("c1c2", Qt::FindDirectChildrenOnly);
QVERIFY(c1c2);
QQuickItem *c1c2c3 = c1c2->findChild<QQuickItem*>("c1c2c3", Qt::FindDirectChildrenOnly);
QVERIFY(c1c2c3);
QQuickItem *c2 = root->findChild<QQuickItem*>("c2", Qt::FindDirectChildrenOnly);
QVERIFY(c2);
QQuickItem *c2c1 = c2->findChild<QQuickItem*>("c2c1", Qt::FindDirectChildrenOnly);
QVERIFY(c2c1);
QQuickItem *c2c1c1 = c2c1->findChild<QQuickItem*>("c2c1c1", Qt::FindDirectChildrenOnly);
QVERIFY(c2c1c1);
QQuickItem *c2c1c2 = c2c1->findChild<QQuickItem*>("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("/TestComponent.2.qml"), QQmlComponent::Asynchronous);
QCOMPARE(component.status(), QQmlComponent::Loading);
QQmlComponent component2(&engine, server.url("/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<QObject> 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<QObject> 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<QObject> 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<QObject> 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<QObject> object(component.create());
QVERIFY(object.isNull());
}
}
void tst_qqmlcomponent::onDestructionLookup()
{
QQmlEngine engine;
QQmlComponent component(&engine, testFileUrl("onDestructionLookup.qml"));
QScopedPointer<QObject> 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<QObject> 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<QObject> 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<QObject> 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<QQmlComponent> components[maxCount];
QScopedPointer<QObject> 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<QV4::QmlContext> qmlContext(scope, v4->qmlContext());
if (!qmlContext)
return;
callingContextData = qmlContext->qmlContext();
scopeObject = qmlContext->qmlScope();
}
int m_value;
QQmlGuardedContextData callingContextData;
QPointer<QObject> scopeObject;
};
void tst_qqmlcomponent::callingContextForInitialProperties()
{
qmlRegisterType<CallingContextCheckingClass>("qqmlcomponenttest", 1, 0, "CallingContextCheckingClass");
QQmlComponent testFactory(&engine, testFileUrl("callingQmlContextComponent.qml"));
QQmlComponent component(&engine, testFileUrl("callingQmlContext.qml"));
QScopedPointer<QObject> root(component.beginCreate(engine.rootContext()));
QVERIFY(!root.isNull());
root->setProperty("factory", QVariant::fromValue(&testFactory));
component.completeCreate();
QTRY_VERIFY(qvariant_cast<QObject *>(root->property("incubatedObject")));
QObject *o = qvariant_cast<QObject *>(root->property("incubatedObject"));
CallingContextCheckingClass *checker = qobject_cast<CallingContextCheckingClass*>(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<QObject> obj(component.create());
QVERIFY(!obj.isNull());
QMetaObject::invokeMethod(obj.data(), "startIncubation");
QJSValue incubatorStatus = obj->property("incubator").value<QJSValue>();
incubatorStatus.property("forceCompletion").callWithInstance(incubatorStatus);
QJSValue objectWrapper = incubatorStatus.property("object");
QVERIFY(objectWrapper.isQObject());
QPointer<QObject> object(objectWrapper.toQObject());
QVERIFY(object->property("ok").toBool());
}
void tst_qqmlcomponent::relativeUrl_data()
{
QTest::addColumn<QUrl>("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<RequiredDefaultCpp>("qt.test", 1, 0, "RequiredDefaultCpp");
qmlRegisterType<TwoRequiredProperties>("qt.test", 1, 0, "TwoRequiredProperties");
qmlRegisterType<ShadowedRequiredProperty>("qt.test", 1, 0, "ShadowedRequiredProperty");
qmlRegisterUncreatableType<AttachedRequiredProperties>(
"qt.test", 1, 0, "AttachedRequiredProperty",
tr("AttachedRequiredProperties is an attached property"));
qmlRegisterType<GroupedRequiredProperties>("qt.test", 1, 0, "GroupedRequiredProperty");
qmlRegisterType<RequiredGroup>("qt.test", 1, 0, "RequiredGroup");
QTest::addColumn<QUrl>("testFile");
QTest::addColumn<bool>("shouldSucceed");
QTest::addColumn<QString>("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<QObject>;
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 grouped property when that "
"grouped property is unused (QTBUG-96544)",
Abort);
QEXPECT_FAIL("required two set one (group)",
"We fail to recognized required sub-properties inside a grouped property, even when "
"that grouped 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<QObject> obj { comp.create() };
QVERIFY(obj);
auto root = qvariant_cast<QQuickItem*>(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<QObject> obj { comp.create() };
QVERIFY(obj);
QCOMPARE(qvariant_cast<QQuickItem *>(obj->property("it")), nullptr);
}
}
struct ComponentWithPublicSetInitial : QQmlComponent
{
using QQmlComponent::QQmlComponent;
void setInitialProperties(QObject *o, QVariantMap map)
{
QQmlComponent::setInitialProperties(o, map);
}
};
class MyObject : public QObject
{
public:
enum E { };
Q_ENUM(E)
Q_OBJECT
Q_PROPERTY(int resettable MEMBER m_resettable RESET resetIt NOTIFY resettableChanged)
Q_PROPERTY(QObject * resettableObject MEMBER m_resettableObj RESET resetObject NOTIFY resettableChanged)
Q_PROPERTY(MyObject::E resettableEnum MEMBER m_resettableEnum RESET resetEnum NOTIFY resettableChanged)
void resetIt() { resetCalled = true; }
void resetObject() { resetObjectCalled = true; }
void resetEnum() { resetEnumCalled = true; }
public:
MyObject(QObject *parent = nullptr) : QObject(parent) { }
int m_resettable;
QObject *m_resettableObj = nullptr;
E m_resettableEnum = {};
bool resetCalled = false;
bool resetObjectCalled = false;
bool resetEnumCalled = false;
signals:
void resettableChanged();
};
void tst_qqmlcomponent::testSetInitialProperties()
{
QQmlEngine eng;
qmlRegisterType<MyObject>("ResetTest", 1, 0, "MyObject");
// reset logic
{
QQmlComponent comp(&eng);
comp.loadFromModule("ResetTest", "MyObject");
std::unique_ptr<QObject> obj {comp.createWithInitialProperties({{u"resettable"_s, QVariant() }}) };
QVERIFY(obj);
QVERIFY(qobject_cast<MyObject *>(obj.get())->resetCalled);
}
// reset logic - works also with object properties
{
QQmlComponent comp(&eng);
comp.loadFromModule("ResetTest", "MyObject");
std::unique_ptr<QObject> obj {comp.createWithInitialProperties({{u"resettableObject"_s, QVariant() }}) };
QVERIFY(obj);
QVERIFY(qobject_cast<MyObject *>(obj.get())->resetObjectCalled);
}
// reset logic - works also with enums
{
QQmlComponent comp(&eng);
comp.loadFromModule("ResetTest", "MyObject");
std::unique_ptr<QObject> obj {comp.createWithInitialProperties({{u"resettableEnum"_s, QVariant() }}) };
QVERIFY(obj);
QVERIFY(qobject_cast<MyObject *>(obj.get())->resetEnumCalled);
}
// reset logic - null != undefined
{
QQmlComponent comp(&eng);
comp.loadFromModule("ResetTest", "MyObject");
QVariant var = QVariant::fromValue(nullptr);
QVERIFY(var.isNull() && var.isValid());
QTest::ignoreMessage(QtMsgType::QtWarningMsg, QRegularExpression(".*Could not set initial property resettable"_L1));
std::unique_ptr<QObject> obj {comp.createWithInitialProperties({{u"resettable"_s, var }}) };
QVERIFY(obj);
QVERIFY(!qobject_cast<MyObject *>(obj.get())->resetCalled);
}
{
// QVariant
ComponentWithPublicSetInitial comp(&eng);
comp.loadUrl(testFileUrl("variantBasedInitialization.qml"));
QScopedPointer<QObject> 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<QObject> 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<QObject> 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"));
QScopedPointer<QObject> 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<QObject> objEmpty { comp.create() };
QVERIFY(objEmpty);
}
{
QQmlComponent comp(&eng);
comp.loadUrl(testFileUrl("requiredNotSet.qml"));
QScopedPointer<QObject> 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<QObject> 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<QObject> 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<QObject> 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<QObject> 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<WithQJSValue>("ComponentTest", 1, 0, "WithQJSValue");
QQmlEngine engine;
QQmlComponent component(&engine);
component.setData("import ComponentTest\nWithQJSValue {}", QUrl());
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
QScopedPointer<QObject> o(component.createWithInitialProperties({{ u"v"_s, 5}}));
QVERIFY(!o.isNull());
WithQJSValue *withQJSValue = qobject_cast<WithQJSValue *>(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<QObject> 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<QObject> o(component.create());
QVERIFY2(!o.isNull(), qPrintable(component.errorString()));
QQmlComponent *oComponent = qobject_cast<QQmlComponent *>(o.get());
QVERIFY(oComponent);
QScopedPointer<QObject> enclosed(oComponent->create());
QVERIFY(!enclosed.isNull());
QCOMPARE(enclosed->objectName(), u"enclosed"_s);
}
{
QQmlEngine engine;
QQmlComponent component(&engine);
component.loadUrl(testFileUrl("componentTypes.qml"));
QScopedPointer<QObject> 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<QObject *>(o->property("p2"))->property("text").toString(),
u"foo"_s);
auto p3Object = qvariant_cast<QObject *>(o->property("p3"));
QVERIFY(p3Object);
QVERIFY(p3Object->property("text").toString().isEmpty());
QQmlComponent *normalComponent = qobject_cast<QQmlComponent *>(normal);
QVERIFY(normalComponent);
QScopedPointer<QObject> 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<QObject> o(component.create());
QVERIFY(!o.isNull());
QQmlComponent *nestedComponent = o->property("c").value<QQmlComponent *>();
QVERIFY(nestedComponent != nullptr);
QVERIFY(nestedComponent->isBound());
QObject *nestedObject = o->property("o").value<QObject *>();
QVERIFY(nestedObject != nullptr);
QCOMPARE(nestedObject->objectName(), QLatin1String("bound"));
QScopedPointer<QObject> contextedObject(nestedComponent->create(qmlContext(o.data())));
QVERIFY(!contextedObject.isNull());
QScopedPointer<QObject> 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<QObject> o(component.create());
QVERIFY2(!o.isNull(), qPrintable(component.errorString()));
QObject *nestedObject = o->property("o").value<QObject *>();
QVERIFY(nestedObject != nullptr);
QCOMPARE(nestedObject->objectName(), QLatin1String("inline"));
}
{
QQmlComponent component(&engine, testFileUrl("boundInlineComponentUser.qml"));
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
QVERIFY(!component.isBound());
QScopedPointer<QObject> 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(), "<Unknown File>:2:8: Syntax error\n"_L1);
QVERIFY(!component.isBound());
}
}
void tst_qqmlcomponent::loadFromModule_data()
{
using namespace Qt::StringLiterals;
QTest::addColumn<QString>("uri");
QTest::addColumn<QString>("typeName");
QTest::addColumn<QString>("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"QQmlComponentTestHelpers"_s << u"TestComponentWithIC"_s << u"TestComponentWithIC"_s; // sanity check for next test
QTest::addRow("InnerIC") << u"QQmlComponentTestHelpers"_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<QObject> 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<QObject> object(syncAndAsync.create());
QVERIFY(object);
}
void tst_qqmlcomponent::loadFromModuleLifecycle()
{
QQmlEngine engine;
const QString text = "text"_L1;
const QList<int> expected {1, 2, 3};
{
QQmlComponent component(&engine);
component.loadFromModule("QQmlComponentTestHelpers", "LifeCycleWatcher");
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
std::unique_ptr<QObject> root{ component.create() };
LifeCycleWatcher *watcher = qobject_cast<LifeCycleWatcher *>(root.get());
QVERIFY(watcher);
QCOMPARE(watcher->states, expected);
QCOMPARE(watcher->observedTexts, QStringList(3));
QVERIFY(watcher->hasContextInClassBegin);
QVERIFY(watcher->hasEngineInClassBegin);
const QString loaded = "load from module"_L1;
root.reset(component.createWithInitialProperties(QVariantMap{{text, loaded}}));
watcher = qobject_cast<LifeCycleWatcher *>(root.get());
QVERIFY(watcher);
QCOMPARE(watcher->states, expected);
QCOMPARE(watcher->observedTexts, QStringList({QString(), loaded, loaded}));
}
{
QQmlComponent component(&engine);
component.setData("import QQmlComponentTestHelpers; LifeCycleWatcher {}", {});
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
std::unique_ptr<QObject> root{ component.create() };
LifeCycleWatcher *watcher = qobject_cast<LifeCycleWatcher *>(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<LifeCycleWatcher *>(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 QQmlComponentTestHelpers; LifeCycleWatcher { text: 'inline' }", {});
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
std::unique_ptr<QObject> root{ component.create() };
LifeCycleWatcher *watcher = qobject_cast<LifeCycleWatcher *>(root.get());
QVERIFY(watcher);
QCOMPARE(watcher->states, expected);
QCOMPARE(watcher->observedTexts, QStringList({QString(), compiled, compiled}));
const QString loaded = "overridden"_L1;
std::unique_ptr<QObject> withProperties(
component.createWithInitialProperties(QVariantMap{{text, loaded}}));
watcher = qobject_cast<LifeCycleWatcher *>(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<QObject> 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<QString>("uri");
QTest::addColumn<QString>("typeName");
QTest::addColumn<QString>("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::Status>(),
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<QMetaObject *>(&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<SingleRequiredProperty>("qqmlcomponenttest", 1, 0, "SingleRequiredProperty");
qmlRegisterType<SingleRequiredPropertyDynamic>("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<QObject> root(component.create());
QVERIFY(!root);
QVERIFY(component.isError());
QCOMPARE(component.errorString(), qPrintable("<Unknown File>: " + error + "\n"));
}
{
QQmlComponent component(&engine, "qqmlcomponenttest", "SingleRequiredProperty");
QVERIFY2(!component.isError(), qPrintable(component.errorString()));
QScopedPointer<QObject> 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("<Unknown File>: " + error + "\n"));
}
{
QQmlComponent component(&engine, "qqmlcomponenttest", "SingleRequiredPropertyDynamic");
QVERIFY2(!component.isError(), qPrintable(component.errorString()));
QScopedPointer<QObject> 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<SingleRequiredProperty>("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<QObject> root(component.create());
QVERIFY(!root);
QVERIFY(component.isError());
QCOMPARE(component.errorString(), qPrintable(url.toString() + ":2:1: " + error + "\n"));
}
{
QQmlComponent component(&engine);
component.loadUrl(url);
QVERIFY2(!component.isError(), qPrintable(component.errorString()));
QScopedPointer<QObject> 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:1: " + error + "\n"));
}
}
void tst_qqmlcomponent::loadFromQrc()
{
QQmlEngine engine;
QQmlComponent component(&engine, QStringLiteral(":/qt/qml/QQmlComponentTestHelpers/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<QObject> 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<QObject> 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<QObject> o(component.create());
QVERIFY(!o.isNull());
const QList<QVariant> 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<std::unique_ptr<QObject>> 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<QObject> 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<QObject> object(component.create());
QVERIFY(!object.isNull());
QObject *outer = object->property("outer").value<QObject *>();
QVERIFY(outer);
QObject *inner = object->property("inner").value<QObject *>();
QVERIFY(inner);
QCOMPARE(inner, outer->property("obj").value<QObject *>());
QVERIFY(!inner->property("obj").value<QObject *>());
}
void tst_qqmlcomponent::repeatedSetDataWithInlineComponent()
{
QQmlEngine engine;
int counter = 0;
const QByteArray code = R"(
import QtQml
QtObject {
component Info : QtObject {}
property Info info : Info {}
})";
for (int i = 0; i < 5; ++i) {
QQmlComponent firstComp(&engine);
firstComp.setData(code, QUrl{});
QVERIFY2(firstComp.isReady(), qPrintable(firstComp.errorString()));
QScopedPointer<QObject> firstObject(firstComp.create());
QVERIFY2(!firstObject.isNull(), qPrintable(firstComp.errorString()));
QQmlComponent secondComp(&engine);
secondComp.setData(code, QUrl( QString("qrc:/test") + QString::number(counter++)));
QVERIFY2(secondComp.isReady(), qPrintable(secondComp.errorString()));
QScopedPointer<QObject> secondObject(secondComp.create());
QVERIFY2(!secondObject.isNull(), qPrintable(secondComp.errorString()));
}
}
void tst_qqmlcomponent::setInitialPropertyInteraction()
{
QQmlEngine engine;
{
QQmlComponent prequisiteTester(&engine);
prequisiteTester.loadFromModule("QtQuick", "Item");
if (!prequisiteTester.isReady())
QSKIP("QtQuick is not available");
prequisiteTester.loadFromModule("QtQuick.Layouts", "RowLayout");
if (!prequisiteTester.isReady())
QSKIP("QtQuick.Layouts is not available");
}
const QByteArray code = R"(
import QtQuick
import QtQuick.Layouts
Item {
})";
QQmlComponent comp(&engine);
auto compPriv = QQmlComponentPrivate::get(&comp);
comp.setData(code, QUrl{});
QScopedPointer<QObject> obj(comp.beginCreate(engine.rootContext()));
QVERIFY(obj);
{
bool couldSetProperty = compPriv->setInitialProperty(
obj.get(), "Layout.fillWidth"_L1, true);
QVERIFY(couldSetProperty);
}
{
bool couldSetProperty = compPriv->setInitialProperty(
obj.get(), "parent.doesNotExist.shouldNotCrash"_L1, true);
QVERIFY(!couldSetProperty);
}
comp.completeCreate();
}
void tst_qqmlcomponent::invalidBaseUrl()
{
QQmlEngine engine;
QQmlComponent component(&engine);
component.setData(R"(
import "."
FooBar {}
)", QUrl());
QVERIFY(component.isError());
QVERIFY(component.errorString().contains(
"Can't resolve relative qmldir URL ./qmldir on invalid base URL"_L1));
}
void tst_qqmlcomponent::uselessGroupedProperty()
{
QQmlEngine engine;
QQmlComponent component(&engine, testFileUrl("uselessGroupedProperty.qml"));
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
std::unique_ptr<QObject> object(component.create());
QVERIFY(!object);
QVERIFY(component.errorString().contains(
"Using grouped property syntax on restoreMode which has no properties"));
}
void tst_qqmlcomponent::componentCompilationUnitSetter()
{
QQmlComponent component(&engine, testFileUrl("Simple.qml"));
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
auto *priv = QQmlComponentPrivate::get(&component);
QVERIFY(priv);
auto origCU = priv->compilationUnit();
QVERIFY(origCU);
// Round-trip.
priv->setCompilationUnit(origCU);
QCOMPARE(priv->compilationUnit().data(), origCU.data());
}
void tst_qqmlcomponent::setDataSynchronous()
{
QByteArray code = "import QtQuick\n"
"Rectangle { width: 123; height: 345 }";
QQmlEngine engine;
QQmlComponent component(&engine);
component.setData(code, QUrl{});
QCOMPARE(component.progress(), 1.0);
QCOMPARE(component.status(), QQmlComponent::Ready);
std::unique_ptr<QQuickRectangle> rectangle(qobject_cast<QQuickRectangle *>(component.create(engine.rootContext())));
QVERIFY(rectangle != nullptr);
QCOMPARE(rectangle->width(), 123);
QCOMPARE(rectangle->height(), 345);
}
void tst_qqmlcomponent::setDataAsynchronous()
{
QByteArray code = "import QtQuick\n"
"Rectangle { width: 123; height: 345 }";
QQmlEngine engine;
QQmlComponent component(&engine);
QCOMPARE(component.progress(), 0.0);
QSignalSpy progressSpy(&component, &QQmlComponent::progressChanged);
{
QQmlComponentPrivate *d = QQmlComponentPrivate::get(&component);
d->setData(code, QUrl{}, QQmlComponent::Asynchronous);
emit component.statusChanged(component.status());
}
QTRY_VERIFY(!progressSpy.isEmpty());
QTRY_COMPARE(progressSpy.last().at(0).toDouble(), 1.0);
QCOMPARE(component.progress(), 1.0);
QCOMPARE(component.status(), QQmlComponent::Ready);
std::unique_ptr<QQuickRectangle> rectangle(qobject_cast<QQuickRectangle *>(component.create(engine.rootContext())));
QVERIFY(rectangle != nullptr);
QCOMPARE(rectangle->width(), 123);
QCOMPARE(rectangle->height(), 345);
}
void tst_qqmlcomponent::createWithInitialPropertiesRequiredAlias()
{
// QTBUG-123771
// Rules:
// - A required alias is satisfied only by setting that alias or an alias to it
// (not the underlying property).
// - A required property is satisfied by setting the property itself OR any alias to it.
// Case 1: required alias satisfied by setting the alias → OK
{
QQmlEngine eng;
QQmlComponent comp(&eng);
comp.setData(R"(
import QtQml
QtObject {
id: self
property int foo
property alias aliasToFoo: self.foo
required aliasToFoo
}
)", QUrl());
QVERIFY2(comp.isReady(), qPrintable(comp.errorString()));
QScopedPointer<QObject> obj(comp.createWithInitialProperties({{ u"aliasToFoo"_s, 42 }}));
QVERIFY2(!obj.isNull(), qPrintable(comp.errorString()));
}
// Case 2: required alias NOT satisfied by setting the underlying property directly → fail
{
QQmlEngine eng;
QQmlComponent comp(&eng);
comp.setData(R"(
import QtQml
QtObject {
id: self
property int foo
property alias aliasToFoo: self.foo
required aliasToFoo
}
)", QUrl());
QVERIFY2(comp.isReady(), qPrintable(comp.errorString()));
QScopedPointer<QObject> obj(comp.createWithInitialProperties({{ u"foo"_s, 42 }}));
QVERIFY(obj.isNull()); // required aliasToFoo was not satisfied
QVERIFY(comp.errorString().contains(u"aliasToFoo"_s));
}
// Case 3: required property satisfied by setting the property directly → OK
{
QQmlEngine eng;
QQmlComponent comp(&eng);
comp.setData(R"(
import QtQml
QtObject {
id: self
required property int foo
property alias aliasToFoo: self.foo
}
)", QUrl());
QVERIFY2(comp.isReady(), qPrintable(comp.errorString()));
QScopedPointer<QObject> obj(comp.createWithInitialProperties({{ u"foo"_s, 42 }}));
QVERIFY2(!obj.isNull(), qPrintable(comp.errorString()));
}
// Case 4: required property satisfied by setting an alias to it → OK
{
QQmlEngine eng;
QQmlComponent comp(&eng);
comp.setData(R"(
import QtQml
QtObject {
id: self
required property int foo
property alias aliasToFoo: self.foo
}
)", QUrl());
QVERIFY2(comp.isReady(), qPrintable(comp.errorString()));
QScopedPointer<QObject> obj(comp.createWithInitialProperties({{ u"aliasToFoo"_s, 42 }}));
QVERIFY2(!obj.isNull(), qPrintable(comp.errorString()));
}
}
void tst_qqmlcomponent::groupedPropertyDoesNotOverwriteCompilationUnit()
{
// A derived type applying grouped property bindings to a child object defined in
// the base type must not overwrite the child's ddata->compilationUnit/cuObjectIndex.
// Those fields should reflect the actual instantiation point (the base type's CU).
QQmlEngine eng;
QQmlComponent component(&eng, testFileUrl("GroupedPropertyDerived.qml"));
QVERIFY2(component.isReady(), qPrintable(component.errorString()));
std::unique_ptr<QObject> root(component.create());
QVERIFY(root);
QObject *child = root->property("child").value<QObject *>();
QVERIFY(child);
QCOMPARE(child->objectName(), "modified");
QQmlData *ddata = QQmlData::get(child);
QVERIFY(ddata);
QVERIFY(ddata->compilationUnit);
// The child's compilationUnit must point to GroupedPropertyBase.qml (where it's
// actually instantiated), not GroupedPropertyDerived.qml (which merely applies
// a grouped property binding to it).
const QUrl childCuUrl = ddata->compilationUnit->finalUrl();
QVERIFY2(childCuUrl.toString().contains("GroupedPropertyBase.qml"),
qPrintable("Expected GroupedPropertyBase.qml but got: " + childCuUrl.toString()));
}
QTEST_MAIN(tst_qqmlcomponent)
#include "tst_qqmlcomponent.moc"