From be87f2ad652b1add55bb5ffe4f654416fc41ebb1 Mon Sep 17 00:00:00 2001 From: Andrei Golubev Date: Tue, 1 Jun 2021 17:05:36 +0200 Subject: [PATCH] tst_qmlcompiler_manual: Add QQmlContext-aware tests Prototype the way to handle QQmlContext shenanigans through the tests. Similar model would then be used for the (actually) generated C++ while these tests provide some idea about what works (and what doesn't), additionally showcasing the flow Potential problem: this model likely doesn't support property overrides (albeit tests for that are needed) Change-Id: I47a5317ad418eb8f34f86f06b6309803eaabbd2e Reviewed-by: Ulf Hermann --- tests/auto/qml/CMakeLists.txt | 4 +- .../qml/qmlcompiler_manual/CMakeLists.txt | 1 + .../data/LocallyImported.qml | 14 + .../qmlcompiler_manual/data/localImport.qml | 11 + .../qml/qmlcompiler_manual/data/neighbors.qml | 15 + .../tst_qmlcompiler_manual.cpp | 632 +++++++++++++++++- 6 files changed, 667 insertions(+), 10 deletions(-) create mode 100644 tests/auto/qml/qmlcompiler_manual/data/LocallyImported.qml create mode 100644 tests/auto/qml/qmlcompiler_manual/data/localImport.qml create mode 100644 tests/auto/qml/qmlcompiler_manual/data/neighbors.qml diff --git a/tests/auto/qml/CMakeLists.txt b/tests/auto/qml/CMakeLists.txt index 9a674cd24a..0044a5d817 100644 --- a/tests/auto/qml/CMakeLists.txt +++ b/tests/auto/qml/CMakeLists.txt @@ -32,7 +32,9 @@ add_subdirectory(qqmlapplicationengine) add_subdirectory(qqmlsettings) add_subdirectory(qmldiskcache) add_subdirectory(qqmlmetatype) -add_subdirectory(qmlcompiler_manual) +if(TARGET Qt::Quick) + add_subdirectory(qmlcompiler_manual) +endif() add_subdirectory(qmlbasicapp) if(TARGET Qt::Widgets) add_subdirectory(qjsengine) diff --git a/tests/auto/qml/qmlcompiler_manual/CMakeLists.txt b/tests/auto/qml/qmlcompiler_manual/CMakeLists.txt index 0afe49e760..05aff4f3ea 100644 --- a/tests/auto/qml/qmlcompiler_manual/CMakeLists.txt +++ b/tests/auto/qml/qmlcompiler_manual/CMakeLists.txt @@ -13,6 +13,7 @@ qt_internal_add_test(tst_qmlcompiler_manual LIBRARIES Qt::CorePrivate Qt::QmlPrivate + Qt::Quick TESTDATA ${test_data} ) diff --git a/tests/auto/qml/qmlcompiler_manual/data/LocallyImported.qml b/tests/auto/qml/qmlcompiler_manual/data/LocallyImported.qml new file mode 100644 index 0000000000..cb06837b4d --- /dev/null +++ b/tests/auto/qml/qmlcompiler_manual/data/LocallyImported.qml @@ -0,0 +1,14 @@ +import QtQml +QtObject { + id: foo + property int count: 0 + function getMagicValue() { + var c = foo.count; + foo.count++; + return foo.count + (c * 2); + } + + Component.onCompleted: { + ++count; + } +} diff --git a/tests/auto/qml/qmlcompiler_manual/data/localImport.qml b/tests/auto/qml/qmlcompiler_manual/data/localImport.qml new file mode 100644 index 0000000000..0477daaae7 --- /dev/null +++ b/tests/auto/qml/qmlcompiler_manual/data/localImport.qml @@ -0,0 +1,11 @@ +import QtQml +LocallyImported { + id: bar + property int p1: 41 + count: p1 + 1 + + function localGetMagicValue() { + // call base type's method + return getMagicValue(); + } +} diff --git a/tests/auto/qml/qmlcompiler_manual/data/neighbors.qml b/tests/auto/qml/qmlcompiler_manual/data/neighbors.qml new file mode 100644 index 0000000000..9b913e845e --- /dev/null +++ b/tests/auto/qml/qmlcompiler_manual/data/neighbors.qml @@ -0,0 +1,15 @@ +import QtQuick + +Item { + id: root + QtObject { + id: child1 + property int p: 41 + property int p2: child2.count * 2 + } + + LocallyImported { + id: child2 + property int p: child1.p + 1 + } +} diff --git a/tests/auto/qml/qmlcompiler_manual/tst_qmlcompiler_manual.cpp b/tests/auto/qml/qmlcompiler_manual/tst_qmlcompiler_manual.cpp index db1cd7132c..3723541163 100644 --- a/tests/auto/qml/qmlcompiler_manual/tst_qmlcompiler_manual.cpp +++ b/tests/auto/qml/qmlcompiler_manual/tst_qmlcompiler_manual.cpp @@ -30,11 +30,15 @@ #include #include +#include #include #include +#include +#include #include #include +#include #include "../../shared/util.h" @@ -54,6 +58,9 @@ private slots: void propertyAlias(); void propertyChangeHandler(); void propertyReturningFunction(); + void locallyImported(); + void localImport(); + void neighbors(); private: void signalHandlers_impl(const QUrl &url); @@ -95,6 +102,15 @@ static constexpr int PROPERTY_CHANGE_HANDLER_P_BINDING = 0; static constexpr int PROPERTY_CHANGE_HANDLER_ON_P_CHANGED = 1; static constexpr int PROPERTY_RETURNING_FUNCTION_F_BINDING = 0; + +static constexpr int LOCALLY_IMPORTED_GET_MAGIC_VALUE = 0; +static constexpr int LOCALLY_IMPORTED_ON_COMPLETED = 1; + +static constexpr int LOCAL_IMPORT_COUNT_BINDING = 0; +static constexpr int LOCAL_IMPORT_LOCAL_GET_MAGIC_VALUE = 1; + +static constexpr int NEIGHBOUR_IDS_CHILD1_P2_BINDING = 0; +static constexpr int NEIGHBOUR_IDS_CHILD2_P_BINDING = 1; }; // test utility function for type erasure. the "real" code would be @@ -127,23 +143,62 @@ struct ContextRegistrator ContextRegistrator(QQmlEngine *engine, QObject *This) { Q_ASSERT(engine && This); - // if This object has a parent, it's not considered to be a root object, - // so it must instead have a dedicated context, but what to do when we - // reparent the root item to e.g. QQuickWindow::contentItem()? + if (QQmlContext *context = engine->contextForObject(This)) // already set + return; + + // use simple form of the logic done in create() and set(). this code + // shouldn't actually be used in real generated classes. it just exists + // here for convenience Q_ASSERT(!This->parent() || engine->contextForObject(This->parent()) || engine->rootContext()); - QQmlContext *context = engine->rootContext(); - if (This->parent()) { - QQmlContext *parentContext = engine->contextForObject(This->parent()); - if (parentContext) - context = new QQmlContext(parentContext, This); - } + QQmlContext *parentContext = engine->contextForObject(This->parent()); + QQmlContext *context = + parentContext ? parentContext : new QQmlContext(engine->rootContext(), This); Q_ASSERT(context); // NB: not only sets the context, but also seeds engine into This, so // that qmlEngine(This) works engine->setContextForObject(This, context); Q_ASSERT(qmlEngine(This)); } + + static QQmlRefPointer + create(QQmlEngine *engine, const QUrl &url, + const QQmlRefPointer &parentContext, int index) + { + Q_ASSERT(index >= 0); + QQmlRefPointer context; + if (index == 0) { + // create context the same way it is done in QQmlObjectCreator::create() + context = QQmlContextData::createRefCounted(parentContext); + context->setInternal(true); + auto unit = QQmlEnginePrivate::get(engine)->compilationUnitFromUrl(url); + context->setImports(unit->typeNameCache); + context->initFromTypeCompilationUnit(unit, index); + } else { + // non-root objects adopt parent context and use that one instead of + // creating own + context = parentContext; + // assume context is initialized in the root object + } + return context; + } + + static void set(QObject *This, const QQmlRefPointer &context, + QQmlContextData::QmlObjectKind kind) + { + Q_ASSERT(This); + QQmlData *ddata = QQmlData::get(This, /*create*/ true); + + // NB: copied from QQmlObjectCreator::createInstance() + // + // the if-statement logic is: if (static_cast(index) == 0 || + // ddata->rootObjectInCreation || isInlineComponent) then + // QQmlContextData::DocumentRoot + context->installContext(ddata, kind); + if (kind == QQmlContextData::DocumentRoot) + context->setContextObject(This); + Q_ASSERT(qmlEngine(This)); + } }; class HelloWorld : public QObject, public ContextRegistrator @@ -892,6 +947,565 @@ void tst_qmlcompiler_manual::propertyReturningFunction() QCOMPARE(created.getCounter(), 0); } +class LocallyImported : public QObject +{ + Q_OBJECT + QML_ANONYMOUS + Q_PROPERTY(int count READ getCount WRITE setCount BINDABLE bindableCount) + +protected: + LocallyImported(QObject *parent = nullptr) : QObject(parent) { } + +public: + // test workaround: the url is resolved by the test base class, so use + // member variable to store the resolved url used as argument in engine + // evaluation of runtime functions + static QUrl url; + + LocallyImported(QQmlEngine *e, QObject *parent = nullptr) : LocallyImported(parent) + { + init(e, QQmlContextData::get(e->rootContext())); + } + + QQmlRefPointer init(QQmlEngine *e, + const QQmlRefPointer &parentContext) + { + // NB: this object is the root object of LocallyImported.qml + constexpr int subComponentIndex = 0; + auto context = parentContext; + context = ContextRegistrator::create(e, url, context, subComponentIndex); + ContextRegistrator::set(this, context, QQmlContextData::DocumentRoot); + context->setIdValue(0, this); + count = 0; + finalize(e); // call here because it's document root + return context; + } + + Q_INVOKABLE QVariant getMagicValue() + { + QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this)); + const auto index = FunctionIndices::LOCALLY_IMPORTED_GET_MAGIC_VALUE; + constexpr int argc = 0; + QVariant ret {}; + std::array a {}; + std::array t {}; + typeEraseArguments(a, t, ret); + e->executeRuntimeFunction(url, index, this, argc, a.data(), t.data()); + return ret; + } + + void completedSlot() { + QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this)); + const auto index = FunctionIndices::LOCALLY_IMPORTED_ON_COMPLETED; + constexpr int argc = 0; + std::array a {}; + std::array t {}; + typeEraseArguments(a, t, nullptr); + e->executeRuntimeFunction(url, index, this, argc, a.data(), t.data()); + } + + void finalize(QQmlEngine *e) + { + Q_UNUSED(e); + // 1. finalize children - no children here, so do nothing + + // 2. finalize self + completedSlot(); + } + + int getCount() { return count.value(); } + void setCount(int count_) { count.setValue(count_); } + QBindable bindableCount() { return QBindable(&count); } + QProperty count; +}; +QUrl LocallyImported::url = QUrl(); // workaround + +class ANON_localImport : public LocallyImported +{ + Q_OBJECT + QML_ANONYMOUS + Q_PROPERTY(int p1 READ getP1 WRITE setP1) + +protected: + ANON_localImport(QObject *parent = nullptr) : LocallyImported(parent) { } + +public: + // test workaround: the url is resolved by the test base class, so use + // member variable to store the resolved url used as argument in engine + // evaluation of runtime functions + static QUrl url; + + ANON_localImport(QQmlEngine *e, QObject *parent = nullptr) : ANON_localImport(parent) + { + // NB: always use e->rootContext() as parent context in the public ctor + init(e, QQmlContextData::get(e->rootContext())); + } + + QQmlRefPointer init(QQmlEngine *e, + const QQmlRefPointer &parentContext) + { + // init function is a multi-step procedure: + // + // 0. [optional] call base class' init() method (when base class is also + // generated from QML), passing parentContext and getting back + // another context + // 1. create child QQmlContextData from the context + // 2. * EITHER: patch the context to be the parent context - when this + // type is not root and it has generated C++ base class + // * OR: set the context for this object + // 3. [optional] set id by QmlIR::Object::id + // 4. do the simple initialization bits (e.g. set property values, etc.) + // - might actually slip to finalize()? + // 5. [document root only] call finalize() which finalizes all types in + // this document - after context and instances are set up correctly + + constexpr int componentIndex = 0; // root index + auto context = parentContext; + // 0. + context = LocallyImported::init(e, context); + // 1. + context = ContextRegistrator::create(e, url, context, componentIndex); + // 2. + // if not root and parent is also generated C++ object, patch context + // context = parentContext; + // else: + ContextRegistrator::set(this, context, QQmlContextData::DocumentRoot); + // 3. + context->setIdValue(0, this); + // 4. + p1 = 41; + // 5. + finalize(e); // call here because it's document root + return context; + } + + void finalize(QQmlEngine *e) + { + Q_UNUSED(e); + // 1. finalize children - no children here, so do nothing + + // 2. finalize self + QPropertyBinding ANON_localImport_count_binding( + [&]() { + QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this)); + const auto index = FunctionIndices::LOCAL_IMPORT_COUNT_BINDING; + constexpr int argc = 0; + int ret {}; + std::array a {}; + std::array t {}; + typeEraseArguments(a, t, ret); + e->executeRuntimeFunction(url, index, this, argc, a.data(), t.data()); + return ret; + }, + QT_PROPERTY_DEFAULT_BINDING_LOCATION); + bindableCount().setBinding(ANON_localImport_count_binding); + } + + int getP1() { return p1.value(); } + void setP1(int p1_) { p1.setValue(p1_); } + QProperty p1; + + Q_INVOKABLE QVariant localGetMagicValue() + { + QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this)); + const auto index = FunctionIndices::LOCAL_IMPORT_LOCAL_GET_MAGIC_VALUE; + constexpr int argc = 0; + QVariant ret {}; + std::array a {}; + std::array t {}; + typeEraseArguments(a, t, ret); + e->executeRuntimeFunction(url, index, this, argc, a.data(), t.data()); + return ret; + } +}; +QUrl ANON_localImport::url = QUrl(); // workaround + +void tst_qmlcompiler_manual::locallyImported() +{ + QQmlEngine e; + LocallyImported::url = testFileUrl("LocallyImported.qml"); + + LocallyImported created(&e); + QCOMPARE(created.getCount(), 1); + + QQmlContext *ctx = e.contextForObject(&created); + QVERIFY(ctx); + QCOMPARE(qvariant_cast(ctx->contextProperty("foo")), &created); +} + +void tst_qmlcompiler_manual::localImport() +{ + // NB: compare object creation compiler against QQmlComponent + { + QQmlEngine e; + QQmlComponent comp(&e); + comp.loadUrl(testFileUrl("localImport.qml")); + QScopedPointer root(comp.create()); + QVERIFY2(root, qPrintable(comp.errorString())); + + QQmlContext *ctx = e.contextForObject(root.get()); + QVERIFY(ctx); + QVERIFY(ctx->parentContext()); + QCOMPARE(ctx->contextProperty("foo"), QVariant()); + QCOMPARE(qvariant_cast(ctx->contextProperty("bar")), root.get()); + QCOMPARE(ctx->objectForName("foo"), nullptr); + QCOMPARE(ctx->objectForName("bar"), root.get()); + QCOMPARE(QQmlContextData::get(ctx)->parent(), QQmlContextData::get(e.rootContext())); + + int count = root->property("count").toInt(); + QVariant magicValue {}; + QMetaObject::invokeMethod(root.get(), "getMagicValue", Q_RETURN_ARG(QVariant, magicValue)); + QCOMPARE(magicValue.toInt(), (count * 3 + 1)); + + count = root->property("count").toInt(); + magicValue = QVariant(); + QMetaObject::invokeMethod(root.get(), "localGetMagicValue", + Q_RETURN_ARG(QVariant, magicValue)); + QCOMPARE(magicValue.toInt(), (count * 3 + 1)); + } + + // In this case, the context hierarchy is the following: + // * LocallyImported: rootContext -> locallyImportedContext + // * ANON_localImport: ... -> locallyImportedContext -> localImportContext + // + // this resembles the object hierarchy where LocallyImported is a base class + // of ANON_localImport. having an explicit parent context (from + // LocallyImported) guarantees that e.g. parent id / base class methods are + // found during JavaScript (property) lookups + { + QQmlEngine e; + LocallyImported::url = testFileUrl("LocallyImported.qml"); + ANON_localImport::url = testFileUrl("localImport.qml"); + + ANON_localImport created(&e); + QCOMPARE(created.getP1(), 41); + QCOMPARE(created.getCount(), 42); + + QQmlContext *ctx = e.contextForObject(&created); + QVERIFY(ctx); + QVERIFY(ctx->parentContext()); + QEXPECT_FAIL("", + "Inconsistent with QQmlComponent: 'foo' could actually be found in generated " + "C++ base class context", + Continue); + QCOMPARE(ctx->contextProperty("foo"), QVariant()); + QCOMPARE(qvariant_cast(ctx->contextProperty("bar")), &created); + // NB: even though ctx->contextProperty("foo") finds the object, + // objectForName("foo") still returns nullptr as "foo" exists in the + // ctx->parent() + QCOMPARE(ctx->objectForName("foo"), nullptr); + QCOMPARE(ctx->objectForName("bar"), &created); + QEXPECT_FAIL("", + "Inconsistent with QQmlComponent: LocallyImported is a _visible_ parent of " + "ANON_localImport, same stays true for context", + Continue); + QCOMPARE(QQmlContextData::get(ctx)->parent(), QQmlContextData::get(e.rootContext())); + QCOMPARE(QQmlContextData::get(ctx)->parent()->parent(), + QQmlContextData::get(e.rootContext())); + + int count = created.getCount(); + QCOMPARE(created.getMagicValue().toInt(), (count * 3 + 1)); + count = created.getCount(); + QCOMPARE(created.localGetMagicValue().toInt(), (count * 3 + 1)); + } +} + +class ANON_neighbors_QtObject : public QObject +{ + Q_OBJECT + QML_ANONYMOUS + Q_PROPERTY(int p READ getP WRITE setP BINDABLE bindableP) + Q_PROPERTY(int p2 READ getP2 WRITE setP2 BINDABLE bindableP2) + +protected: + ANON_neighbors_QtObject(QObject *parent = nullptr) : QObject(parent) { } + +public: + // test workaround: the url is resolved by the test base class, so use + // member variable to store the resolved url used as argument in engine + // evaluation of runtime functions + static QUrl url; + + ANON_neighbors_QtObject(QQmlEngine *e, QObject *parent = nullptr) + : ANON_neighbors_QtObject(parent) + { + // NB: non-root of the document + init(e, QQmlContextData::get(e->contextForObject(parent))); + } + + QQmlRefPointer init(QQmlEngine *e, + const QQmlRefPointer &parentContext) + { + constexpr int componentIndex = 1; + auto context = ContextRegistrator::create(e, url, parentContext, componentIndex); + ContextRegistrator::set(this, context, QQmlContextData::OrdinaryObject); + context->setIdValue(componentIndex, this); + + p = 41; + return context; + } + + void finalize(QQmlEngine *e) // called by the document root + { + Q_UNUSED(e); + // 1. finalize children - empty as we don't have children here + + // 2. finalize self - call all "dynamic" code - e.g. script bindings, + // Component.onCompleted and so on - everything that may reference + // some random part of the document and thus needs to be delayed + // until all objects in the document are initialized + QPropertyBinding ANON_neighbors_QtObject_p2_binding( + [&]() { + QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this)); + const auto index = FunctionIndices::NEIGHBOUR_IDS_CHILD1_P2_BINDING; + constexpr int argc = 0; + int ret {}; + std::array a {}; + std::array t {}; + typeEraseArguments(a, t, ret); + e->executeRuntimeFunction(url, index, this, argc, a.data(), t.data()); + return ret; + }, + QT_PROPERTY_DEFAULT_BINDING_LOCATION); + bindableP2().setBinding(ANON_neighbors_QtObject_p2_binding); + } + + int getP() { return p.value(); } + void setP(int p_) { p.setValue(p_); } + QBindable bindableP() { return QBindable(&p); } + QProperty p; + + int getP2() { return p2.value(); } + void setP2(int p2_) { p2.setValue(p2_); } + QBindable bindableP2() { return QBindable(&p2); } + QProperty p2; +}; +QUrl ANON_neighbors_QtObject::url = QUrl(); // workaround + +class ANON_neighbors_LocallyImported : public LocallyImported +{ + Q_OBJECT + QML_ANONYMOUS + Q_PROPERTY(int p READ getP WRITE setP BINDABLE bindableP) + +protected: + ANON_neighbors_LocallyImported(QObject *parent = nullptr) : LocallyImported(parent) { } + +public: + // test workaround: the url is resolved by the test base class, so use + // member variable to store the resolved url used as argument in engine + // evaluation of runtime functions + static QUrl url2; + + ANON_neighbors_LocallyImported(QQmlEngine *e, QObject *parent = nullptr) + : ANON_neighbors_LocallyImported(parent) + { + // NB: non-root of the document + init(e, QQmlContextData::get(e->contextForObject(parent))); + } + + QQmlRefPointer init(QQmlEngine *e, + const QQmlRefPointer &parentContext) + { + constexpr int componentIndex = 2; + auto context = LocallyImported::init(e, parentContext); + context = ContextRegistrator::create(e, url2, context, componentIndex); + // if not root and parent is also generated C++ object, patch context + context = parentContext; + // else: ContextRegistrator::set(this, context, QQmlContextData::OrdinaryObject); + context->setIdValue(componentIndex, this); + return context; + } + + void finalize(QQmlEngine *e) // called by the document root + { + Q_UNUSED(e); + // 1. finalize children - empty as we don't have children here + + // 2. finalize self - call all "dynamic" code - e.g. script bindings, + // Component.onCompleted and so on - everything that may reference + // some random part of the document and thus needs to be delayed + // until all objects in the document are initialized + QPropertyBinding ANON_neighbors_LocallyImported_p_binding( + [&]() { + QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this)); + const auto index = FunctionIndices::NEIGHBOUR_IDS_CHILD2_P_BINDING; + constexpr int argc = 0; + int ret {}; + std::array a {}; + std::array t {}; + typeEraseArguments(a, t, ret); + e->executeRuntimeFunction(url2, index, this, argc, a.data(), t.data()); + return ret; + }, + QT_PROPERTY_DEFAULT_BINDING_LOCATION); + bindableP().setBinding(ANON_neighbors_LocallyImported_p_binding); + } + + int getP() { return p.value(); } + void setP(int p_) { p.setValue(p_); } + QBindable bindableP() { return QBindable(&p); } + QProperty p; +}; +QUrl ANON_neighbors_LocallyImported::url2 = QUrl(); // workaround + +// NB: only root subclasses the helper - as it initiates the finalization +class ANON_neighbors : public QQuickItem +{ + Q_OBJECT + QML_ANONYMOUS + +protected: + ANON_neighbors(QObject *parent = nullptr) : QQuickItem() { setParent(parent); } + +public: + // test workaround: the url is resolved by the test base class, so use + // member variable to store the resolved url used as argument in engine + // evaluation of runtime functions + static QUrl url; + + ANON_neighbors(QQmlEngine *e, QObject *parent = nullptr) : ANON_neighbors(parent) + { + // NB: use e->rootContext() as this object is document root + init(e, QQmlContextData::get(e->rootContext())); + } + + QQmlRefPointer init(QQmlEngine *e, + const QQmlRefPointer &parentContext) + { + constexpr int componentIndex = 0; // root index + auto context = ContextRegistrator::create(e, url, parentContext, componentIndex); + ContextRegistrator::set(this, context, QQmlContextData::DocumentRoot); + context->setIdValue(componentIndex, this); + + finalize(e); // call here because it's document root + return context; + } + + void finalize(QQmlEngine *e) + { + Q_UNUSED(e); + // 0. set up object bindings and record all new objects for further + // finalization + QList objectsToFinalize; + objectsToFinalize.reserve(2); // we know it's 2 at compile time + QQmlListReference listrefData(this, "data"); + { + auto o = new ANON_neighbors_QtObject(e, this); + listrefData.append(o); + objectsToFinalize.append(o); + } + { + auto o = new ANON_neighbors_LocallyImported(e, this); + listrefData.append(o); + objectsToFinalize.append(o); + } + + // 1. finalize children + // use static_cast instead of polymorphism as we know the types at + // compile-time + static_cast(objectsToFinalize.at(0))->finalize(e); + static_cast(objectsToFinalize.at(1))->finalize(e); + + // 2. finalize self - empty as we don't have any bindings here + } +}; +QUrl ANON_neighbors::url = QUrl(); // workaround + +void tst_qmlcompiler_manual::neighbors() +{ + { + QQmlEngine e; + QQmlComponent comp(&e); + comp.loadUrl(testFileUrl("neighbors.qml")); + QScopedPointer root(comp.create()); + QVERIFY2(root, qPrintable(comp.errorString())); + + auto rootCtx = QQmlContextData::get(e.contextForObject(root.get())); + QQmlListReference children(root.get(), "data"); + QCOMPARE(children.size(), 2); + auto child1Ctx = QQmlContextData::get(e.contextForObject(children.at(0))); + auto child2Ctx = QQmlContextData::get(e.contextForObject(children.at(1))); + + QCOMPARE(rootCtx->parent(), QQmlContextData::get(e.rootContext())); + QCOMPARE(child1Ctx, rootCtx); + QCOMPARE(child2Ctx, rootCtx); + QCOMPARE(child2Ctx->parent(), QQmlContextData::get(e.rootContext())); + + QQmlContext *rootQmlCtx = rootCtx->asQQmlContext(); + QCOMPARE(rootQmlCtx->objectForName("root"), root.get()); + QCOMPARE(rootQmlCtx->objectForName("child1"), children.at(0)); + QCOMPARE(rootQmlCtx->objectForName("child2"), children.at(1)); + } + + // this case is different from tst_qmlcompiler_manual::localImport() as + // LocallyImported is not a parent of a document root. Thus, the context + // hierarchy: + // * ANON_neighbors: rootContext -> neighborsContext + // * ANON_neighbors_QtObject: rootContext -> neighborsContext + // * LocallyImported: ... -> neighborsContext -> locallyImportedContext + // * ANON_neighbors_LocallyImported: ... -> locallyImportedContext + // + // this should resemble the context hierarchy that QQmlObjectCreator + // assembles, but here the outer context of ANON_neighbors_LocallyImported + // remains to be the one from LocallyImported base class, which guarantees + // that we can lookup stuff that originates from LocallyImported. + { + QQmlEngine e; + LocallyImported::url = testFileUrl("LocallyImported.qml"); + ANON_neighbors::url = testFileUrl("neighbors.qml"); + ANON_neighbors_QtObject::url = testFileUrl("neighbors.qml"); + ANON_neighbors_LocallyImported::url2 = testFileUrl("neighbors.qml"); + + ANON_neighbors created(&e); + QQmlListReference children(&created, "data"); + QCOMPARE(children.size(), 2); + ANON_neighbors_QtObject *child1 = qobject_cast(children.at(0)); + ANON_neighbors_LocallyImported *child2 = + qobject_cast(children.at(1)); + QVERIFY(child1 && child2); + + auto rootCtx = QQmlContextData::get(e.contextForObject(&created)); + auto child1Ctx = QQmlContextData::get(e.contextForObject(child1)); + auto child2Ctx = QQmlContextData::get(e.contextForObject(child2)); + + QCOMPARE(rootCtx->parent(), QQmlContextData::get(e.rootContext())); + QCOMPARE(child1Ctx, rootCtx); + QEXPECT_FAIL("", + "Inconsistent with QQmlComponent: non-root object with generated C++ base has " + "the context of that base", + Continue); + QCOMPARE(child2Ctx, rootCtx); + QEXPECT_FAIL("", + "Inconsistent with QQmlComponent: non-root object with generated C++ base has " + "the context of that base", + Continue); + QCOMPARE(child2Ctx->parent(), QQmlContextData::get(e.rootContext())); + // the rootCtx is actually a parent in this case + QCOMPARE(child2Ctx->parent(), rootCtx); + + QQmlContext *rootQmlCtx = rootCtx->asQQmlContext(); + QCOMPARE(rootQmlCtx->objectForName("root"), &created); + QCOMPARE(rootQmlCtx->objectForName("child1"), child1); + QCOMPARE(rootQmlCtx->objectForName("child2"), child2); + + QCOMPARE(child1->getP(), 41); + QCOMPARE(child1->getP2(), child2->getCount() * 2); + QCOMPARE(child2->getP(), child1->getP() + 1); + + child1->setP(44); + QCOMPARE(child2->getP(), 45); + + child2->setCount(4); + QCOMPARE(child1->getP2(), 8); + + int count = child2->getCount(); + QVariant magicValue {}; + QMetaObject::invokeMethod(child2, "getMagicValue", Q_RETURN_ARG(QVariant, magicValue)); + QCOMPARE(magicValue.toInt(), (count * 3 + 1)); + } +} + QTEST_MAIN(tst_qmlcompiler_manual) #include "tst_qmlcompiler_manual.moc"