/**************************************************************************** ** ** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the test suite of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include "../../shared/util.h" #include #include class tst_qmlcompiler_manual : public QQmlDataTest { Q_OBJECT private slots: void cppBinding(); void signalHandlers(); void signalHandlers_qmlcachegen(); void jsFunctions(); void changingBindings(); void propertyAlias(); void propertyChangeHandler(); void propertyReturningFunction(); void locallyImported(); void localImport(); void neighbors(); private: void signalHandlers_impl(const QUrl &url); }; // test workaround: hardcode runtime function indices. because they could be // rather unexpected and passing wrong ones leads to UB and flakiness. // // NB: if you update the QML files that are used by the QQmlEngine runtime // function execution, make sure that the hardcoded values are in sync with // those changes! An example of when things could go wrong: adding new, removing // old or changing the order of e.g. bindings on properties, signal handlers, JS // functions namespace FunctionIndices { static constexpr int HELLO_WORLD_GREETING_BINDING = 0; static constexpr int SIGNAL_HANDLERS_ON_SIGNAL1 = 1; static constexpr int SIGNAL_HANDLERS_ON_SIGNAL2 = 3; static constexpr int SIGNAL_HANDLERS_QML_EMIT_SIGNAL1 = 4; static constexpr int SIGNAL_HANDLERS_QML_EMIT_SIGNAL2 = 5; static constexpr int SIGNAL_HANDLERS_QML_EMIT_SIGNAL2_WITH_ARGS = 6; static constexpr int JS_FUNCTIONS_FUNC1 = 0; static constexpr int JS_FUNCTIONS_FUNC2 = 1; static constexpr int JS_FUNCTIONS_FUNC3 = 2; static constexpr int CHANGING_BINDINGS_P2_BINDING = 0; static constexpr int CHANGING_BINDINGS_RESET_TO_CONSTANT = 1; static constexpr int CHANGING_BINDINGS_RESET_TO_NEW_BINDING = 2; static constexpr int PROPERTY_ALIAS_ORIGIN_BINDING = 0; static constexpr int PROPERTY_ALIAS_RESET_ALIAS_TO_CONSTANT = 1; static constexpr int PROPERTY_ALIAS_RESET_ORIGIN_TO_CONSTANT = 2; static constexpr int PROPERTY_ALIAS_RESET_ALIAS_TO_NEW_BINDING = 3; static constexpr int PROPERTY_ALIAS_RESET_ORIGIN_TO_NEW_BINDING = 5; static constexpr int PROPERTY_ALIAS_GET_ALIAS_VALUE = 7; 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 // auto-generated by the compiler template static void typeEraseArguments(std::array &a, std::array &t, std::nullptr_t, IOArgs &&... args) { a = { /* return value */ nullptr, /* rest */ const_cast( reinterpret_cast(std::addressof(std::forward(args))))... }; t = { /* return type */ QMetaType::fromType(), /* types */ QMetaType::fromType>()... }; } template static void typeEraseArguments(std::array &a, std::array &t, IOArgs &&... args) { a = { /* all values, including return value */ const_cast( reinterpret_cast(std::addressof(std::forward(args))))... }; t = { /* types */ QMetaType::fromType>()... }; } // utility class that sets up QQmlContext for passed QObject. can be used as a // base class to ensure that qmlEngine(object) is valid during initializer list // evaluation struct ContextRegistrator { ContextRegistrator(QQmlEngine *engine, QObject *This) { Q_ASSERT(engine && This); 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 *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 { Q_OBJECT QML_NAMED_ELEMENT(HelloWorld); Q_PROPERTY(QString hello READ getHello WRITE setHello BINDABLE bindableHello) Q_PROPERTY(QString greeting READ getGreeting WRITE setGreeting BINDABLE bindableGreeting) 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; HelloWorld(QQmlEngine *e, QObject *parent = nullptr) : QObject(parent), ContextRegistrator(e, this) { hello = QStringLiteral("Hello, World"); QPropertyBinding HelloWorldCpp_greeting_binding( [&]() { QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this)); const auto index = FunctionIndices::HELLO_WORLD_GREETING_BINDING; constexpr int argc = 0; QString 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); bindableGreeting().setBinding(HelloWorldCpp_greeting_binding); } QString getHello() { return hello.value(); } QString getGreeting() { return greeting.value(); } void setHello(const QString &hello_) { hello.setValue(hello_); } void setGreeting(const QString &greeting_) { greeting.setValue(greeting_); } QBindable bindableHello() { return QBindable(&hello); } QBindable bindableGreeting() { return QBindable(&greeting); } Q_OBJECT_BINDABLE_PROPERTY(HelloWorld, QString, hello); Q_OBJECT_BINDABLE_PROPERTY(HelloWorld, QString, greeting); }; QUrl HelloWorld::url = QUrl(); // workaround void tst_qmlcompiler_manual::cppBinding() { QQmlEngine e; HelloWorld::url = testFileUrl("HelloWorld.qml"); HelloWorld created(&e); QCOMPARE(created.property("hello").toString(), QStringLiteral("Hello, World")); QCOMPARE(created.getGreeting(), QStringLiteral("Hello, World!")); QCOMPARE(created.property("greeting").toString(), QStringLiteral("Hello, World!")); created.setProperty("hello", QStringLiteral("Hello, Qml")); QCOMPARE(created.property("hello").toString(), QStringLiteral("Hello, Qml")); QCOMPARE(created.property("greeting").toString(), QStringLiteral("Hello, Qml!")); QCOMPARE(created.getGreeting(), QStringLiteral("Hello, Qml!")); } class ANON_signalHandlers : public QObject, public ContextRegistrator { Q_OBJECT QML_ANONYMOUS Q_PROPERTY(int signal1P READ getSignal1P WRITE setSignal1P BINDABLE bindableSignal1P) Q_PROPERTY(QString signal2P1 READ getSignal2P1 WRITE setSignal2P1 BINDABLE bindableSignal2P1) Q_PROPERTY(int signal2P2 READ getSignal2P2 WRITE setSignal2P2 BINDABLE bindableSignal2P2) Q_PROPERTY(QString signal2P3 READ getSignal2P3 WRITE setSignal2P3 BINDABLE bindableSignal2P3) 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_signalHandlers(QQmlEngine *e, QObject *parent = nullptr) : QObject(parent), ContextRegistrator(e, this) { signal1P = 0; signal2P1 = QStringLiteral(""); signal2P2 = 0; signal2P3 = QStringLiteral(""); QObject::connect(this, &ANON_signalHandlers::signal1, this, &ANON_signalHandlers::onSignal1); QObject::connect(this, &ANON_signalHandlers::signal2, this, &ANON_signalHandlers::onSignal2); } int getSignal1P() { return signal1P.value(); } QString getSignal2P1() { return signal2P1.value(); } int getSignal2P2() { return signal2P2.value(); } QString getSignal2P3() { return signal2P3.value(); } void setSignal1P(const int &signal1P_) { signal1P.setValue(signal1P_); } void setSignal2P1(const QString &signal2P1_) { signal2P1.setValue(signal2P1_); } void setSignal2P2(const int &signal2P2_) { signal2P2.setValue(signal2P2_); } void setSignal2P3(const QString &signal2P3_) { signal2P3.setValue(signal2P3_); } QBindable bindableSignal1P() { return QBindable(&signal1P); } QBindable bindableSignal2P1() { return QBindable(&signal2P1); } QBindable bindableSignal2P2() { return QBindable(&signal2P2); } QBindable bindableSignal2P3() { return QBindable(&signal2P3); } Q_OBJECT_BINDABLE_PROPERTY(ANON_signalHandlers, int, signal1P); Q_OBJECT_BINDABLE_PROPERTY(ANON_signalHandlers, QString, signal2P1); Q_OBJECT_BINDABLE_PROPERTY(ANON_signalHandlers, int, signal2P2); Q_OBJECT_BINDABLE_PROPERTY(ANON_signalHandlers, QString, signal2P3); signals: void signal1(); void signal2(QString x, int y); public slots: void onSignal1() { QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this)); const auto index = FunctionIndices::SIGNAL_HANDLERS_ON_SIGNAL1; e->executeRuntimeFunction(url, index, this); } void onSignal2(QString x, int y) { constexpr int argc = 2; std::array a {}; std::array t {}; typeEraseArguments(a, t, nullptr, x, y); QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this)); const qsizetype index = FunctionIndices::SIGNAL_HANDLERS_ON_SIGNAL2; e->executeRuntimeFunction(url, index, this, argc, a.data(), t.data()); } public: void qmlEmitSignal1() { QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this)); const auto index = FunctionIndices::SIGNAL_HANDLERS_QML_EMIT_SIGNAL1; e->executeRuntimeFunction(url, index, this); } void qmlEmitSignal2() { QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this)); const auto index = FunctionIndices::SIGNAL_HANDLERS_QML_EMIT_SIGNAL2; e->executeRuntimeFunction(url, index, this); } void qmlEmitSignal2WithArgs(QString x, int y) { constexpr int argc = 2; std::array a {}; std::array t {}; typeEraseArguments(a, t, nullptr, x, y); QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this)); const auto index = FunctionIndices::SIGNAL_HANDLERS_QML_EMIT_SIGNAL2_WITH_ARGS; e->executeRuntimeFunction(url, index, this, argc, a.data(), t.data()); } }; QUrl ANON_signalHandlers::url = QUrl(); // workaround void tst_qmlcompiler_manual::signalHandlers_impl(const QUrl &url) { QQmlEngine e; ANON_signalHandlers::url = url; ANON_signalHandlers created(&e); // signal emission works from C++ emit created.signal1(); emit created.signal2(QStringLiteral("42"), 42); QCOMPARE(created.property("signal1P").toInt(), 1); QCOMPARE(created.property("signal2P1").toString(), QStringLiteral("42")); QCOMPARE(created.property("signal2P2").toInt(), 42); QCOMPARE(created.property("signal2P3").toString(), QStringLiteral("4242")); // signal emission works through meta object system QMetaObject::invokeMethod(&created, "signal1"); QMetaObject::invokeMethod(&created, "signal2", Q_ARG(QString, QStringLiteral("foo")), Q_ARG(int, 23)); QCOMPARE(created.property("signal1P").toInt(), 2); QCOMPARE(created.property("signal2P1").toString(), QStringLiteral("foo")); QCOMPARE(created.property("signal2P2").toInt(), 23); QCOMPARE(created.property("signal2P3").toString(), QStringLiteral("foo23")); // signal emission works through QML/JS created.qmlEmitSignal1(); created.qmlEmitSignal2(); QCOMPARE(created.property("signal1P").toInt(), 3); QCOMPARE(created.property("signal2P1").toString(), QStringLiteral("xyz")); QCOMPARE(created.property("signal2P2").toInt(), 123); QCOMPARE(created.property("signal2P3").toString(), QStringLiteral("xyz123")); created.qmlEmitSignal2WithArgs(QStringLiteral("abc"), 0); QCOMPARE(created.property("signal2P1").toString(), QStringLiteral("abc")); QCOMPARE(created.property("signal2P2").toInt(), 0); QCOMPARE(created.property("signal2P3").toString(), QStringLiteral("abc0")); } void tst_qmlcompiler_manual::signalHandlers() { // use QQmlTypeCompiler's compilation unit signalHandlers_impl(testFileUrl("signalHandlers.qml")); } void tst_qmlcompiler_manual::signalHandlers_qmlcachegen() { // use qmlcachegen's compilation unit signalHandlers_impl(QUrl("qrc:/data/signalHandlers.qml")); } class ANON_javaScriptFunctions : public QObject, public ContextRegistrator { Q_OBJECT QML_ANONYMOUS Q_PROPERTY(int func1P READ getFunc1P WRITE setFunc1P) Q_PROPERTY(QString func2P READ getFunc2P WRITE setFunc2P) Q_PROPERTY(bool func3P READ getFunc3P WRITE setFunc3P) 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_javaScriptFunctions(QQmlEngine *e, QObject *parent = nullptr) : QObject(parent), ContextRegistrator(e, this) { func1P = 0; func2P = QStringLiteral(""); func3P = false; } int getFunc1P() { return func1P.value(); } QString getFunc2P() { return func2P.value(); } bool getFunc3P() { return func3P.value(); } void setFunc1P(const int &func1P_) { func1P.setValue(func1P_); } void setFunc2P(const QString &func2P_) { func2P.setValue(func2P_); } void setFunc3P(const bool &func3P_) { func3P.setValue(func3P_); } // try if just QProperty works QProperty func1P; QProperty func2P; QProperty func3P; void func1() { QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this)); const auto index = FunctionIndices::JS_FUNCTIONS_FUNC1; e->executeRuntimeFunction(url, index, this); } void func2(QString x) { constexpr int argc = 1; std::array a {}; std::array t {}; typeEraseArguments(a, t, nullptr, x); QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this)); const auto index = FunctionIndices::JS_FUNCTIONS_FUNC2; e->executeRuntimeFunction(url, index, this, argc, a.data(), t.data()); } bool func3() { QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this)); const auto index = FunctionIndices::JS_FUNCTIONS_FUNC3; constexpr int argc = 0; bool 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_javaScriptFunctions::url = QUrl(); // workaround void tst_qmlcompiler_manual::jsFunctions() { QQmlEngine e; ANON_javaScriptFunctions::url = testFileUrl("javaScriptFunctions.qml"); ANON_javaScriptFunctions created(&e); created.func1(); created.func2(QStringLiteral("abc")); QCOMPARE(created.property("func1P").toInt(), 1); QCOMPARE(created.property("func2P").toString(), QStringLiteral("abc")); QCOMPARE(created.func3(), false); created.setProperty("func3P", true); QCOMPARE(created.func3(), true); } class ANON_changingBindings : public QObject, public ContextRegistrator { Q_OBJECT QML_ANONYMOUS Q_PROPERTY(int p1 READ getP1 WRITE setP1 BINDABLE bindableP1) Q_PROPERTY(int p2 READ getP2 WRITE setP2 BINDABLE bindableP2) 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; // test util to monitor binding execution int initialBindingCallCount = 0; // test util: allows to set C++ binding multiple times void resetToInitialBinding() { QPropertyBinding ANON_changingBindings_p2_binding( [&]() { initialBindingCallCount++; QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this)); const auto index = FunctionIndices::CHANGING_BINDINGS_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_changingBindings_p2_binding); } ANON_changingBindings(QQmlEngine *e, QObject *parent = nullptr) : QObject(parent), ContextRegistrator(e, this) { p1 = 1; resetToInitialBinding(); } int getP1() { return p1.value(); } int getP2() { return p2.value(); } void setP1(int p1_) { p1.setValue(p1_); } void setP2(int p2_) { p2.setValue(p2_); } QBindable bindableP1() { return QBindable(&p1); } QBindable bindableP2() { return QBindable(&p2); } Q_OBJECT_BINDABLE_PROPERTY(ANON_changingBindings, int, p1); Q_OBJECT_BINDABLE_PROPERTY(ANON_changingBindings, int, p2); void resetToConstant() { QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this)); const auto index = FunctionIndices::CHANGING_BINDINGS_RESET_TO_CONSTANT; e->executeRuntimeFunction(url, index, this); } void resetToNewBinding() { QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this)); const auto index = FunctionIndices::CHANGING_BINDINGS_RESET_TO_NEW_BINDING; e->executeRuntimeFunction(url, index, this); } }; QUrl ANON_changingBindings::url = QUrl(); // workaround void tst_qmlcompiler_manual::changingBindings() { QQmlEngine e; ANON_changingBindings::url = testFileUrl("changingBindings.qml"); ANON_changingBindings created(&e); // test initial binding QCOMPARE(created.initialBindingCallCount, 1); // eager evaluation QCOMPARE(created.property("p2").toInt(), 2); // p1 + 1 QCOMPARE(created.initialBindingCallCount, 1); // test JS constant value created.resetToConstant(); QCOMPARE(created.property("p2").toInt(), 42); // p2 = 42 QCOMPARE(created.initialBindingCallCount, 1); // test Qt.binding() created.resetToNewBinding(); created.setProperty("p1", 100); QCOMPARE(created.property("p2").toInt(), 200); // p1 * 2 QCOMPARE(created.initialBindingCallCount, 1); // test setting initial (C++) binding created.setProperty("p1", 11); created.resetToInitialBinding(); QCOMPARE(created.initialBindingCallCount, 2); // eager evaluation QCOMPARE(created.property("p2").toInt(), 12); // p1 + 1 (again) QCOMPARE(created.initialBindingCallCount, 2); // test resetting value through C++ created.setP2(0); created.setP1(-10); QCOMPARE(created.property("p2").toInt(), 0); QCOMPARE(created.initialBindingCallCount, 2); created.setProperty("p2", 1); QCOMPARE(created.property("p2").toInt(), 1); QCOMPARE(created.initialBindingCallCount, 2); // test binding can be set again even after reset from C++ created.resetToNewBinding(); QCOMPARE(created.property("p2").toInt(), -20); QCOMPARE(created.initialBindingCallCount, 2); } class ANON_propertyAlias : public QObject, public ContextRegistrator { Q_OBJECT QML_ANONYMOUS Q_PROPERTY(int dummy READ getDummy WRITE setDummy NOTIFY dummyChanged) Q_PROPERTY(int origin READ getOrigin WRITE setOrigin BINDABLE bindableOrigin) Q_PROPERTY(int aliasToOrigin READ getAliasToOrigin WRITE setAliasToOrigin BINDABLE bindableAliasToOrigin) 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; // test util: allows to set C++ binding multiple times void resetToInitialBinding() { QPropertyBinding ANON_propertyAlias_origin_binding( [&]() { QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this)); const auto index = FunctionIndices::PROPERTY_ALIAS_ORIGIN_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); bindableOrigin().setBinding(ANON_propertyAlias_origin_binding); } ANON_propertyAlias(QQmlEngine *e, QObject *parent = nullptr) : QObject(parent), ContextRegistrator(e, this) { dummy = 12; resetToInitialBinding(); } int getDummy() { return dummy.value(); } int getOrigin() { return origin.value(); } int getAliasToOrigin() { return getOrigin(); } void setDummy(int dummy_) { dummy.setValue(dummy_); // emit is essential for Qt.binding() to work correctly emit dummyChanged(); } void setOrigin(int origin_) { origin.setValue(origin_); } void setAliasToOrigin(int aliasToOrigin_) { setOrigin(aliasToOrigin_); } QBindable bindableOrigin() { return QBindable(&origin); } QBindable bindableAliasToOrigin() { return bindableOrigin(); } QProperty dummy; QProperty origin; void resetAliasToConstant() { QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this)); const auto index = FunctionIndices::PROPERTY_ALIAS_RESET_ALIAS_TO_CONSTANT; e->executeRuntimeFunction(url, index, this); } void resetOriginToConstant() { QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this)); const auto index = FunctionIndices::PROPERTY_ALIAS_RESET_ORIGIN_TO_CONSTANT; e->executeRuntimeFunction(url, index, this); } void resetAliasToNewBinding() { QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this)); const auto index = FunctionIndices::PROPERTY_ALIAS_RESET_ALIAS_TO_NEW_BINDING; e->executeRuntimeFunction(url, index, this); } void resetOriginToNewBinding() { QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this)); const auto index = FunctionIndices::PROPERTY_ALIAS_RESET_ORIGIN_TO_NEW_BINDING; e->executeRuntimeFunction(url, index, this); } int getAliasValue() { QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this)); const auto index = FunctionIndices::PROPERTY_ALIAS_GET_ALIAS_VALUE; 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; } signals: void dummyChanged(); }; QUrl ANON_propertyAlias::url = QUrl(); // workaround void tst_qmlcompiler_manual::propertyAlias() { QQmlEngine e; ANON_propertyAlias::url = testFileUrl("propertyAlias.qml"); ANON_propertyAlias created(&e); // test initial binding QCOMPARE(created.property("origin").toInt(), 6); // dummy / 2 QCOMPARE(created.property("aliasToOrigin").toInt(), 6); QCOMPARE(created.getAliasValue(), 6); QCOMPARE(created.getAliasToOrigin(), 6); created.setDummy(10); QCOMPARE(created.property("aliasToOrigin").toInt(), 5); QCOMPARE(created.getAliasValue(), 5); QCOMPARE(created.getAliasToOrigin(), 5); // test the C++ setter created.setOrigin(7); QCOMPARE(created.property("aliasToOrigin").toInt(), 7); QCOMPARE(created.getAliasValue(), 7); QCOMPARE(created.getAliasToOrigin(), 7); // test meta-object setter created.setProperty("origin", 1); QCOMPARE(created.property("aliasToOrigin").toInt(), 1); QCOMPARE(created.getAliasValue(), 1); QCOMPARE(created.getAliasToOrigin(), 1); // test QML/JS setter created.resetOriginToConstant(); QCOMPARE(created.property("aliasToOrigin").toInt(), 189); QCOMPARE(created.getAliasValue(), 189); QCOMPARE(created.getAliasToOrigin(), 189); // test QML/JS alias setter created.resetAliasToConstant(); QCOMPARE(created.property("origin").toInt(), 42); QCOMPARE(created.getOrigin(), 42); // check the alias just to make sure it also works QCOMPARE(created.property("aliasToOrigin").toInt(), 42); QCOMPARE(created.getAliasValue(), 42); QCOMPARE(created.getAliasToOrigin(), 42); // test QML/JS binding reset created.resetOriginToNewBinding(); // dummy created.setDummy(99); QCOMPARE(created.property("aliasToOrigin").toInt(), 99); QCOMPARE(created.getAliasValue(), 99); QCOMPARE(created.getAliasToOrigin(), 99); // test QML/JS binding reset through alias created.resetAliasToNewBinding(); // dummy * 3 created.setDummy(-8); QCOMPARE(created.property("origin").toInt(), -24); QCOMPARE(created.getOrigin(), -24); QCOMPARE(created.property("aliasToOrigin").toInt(), -24); QCOMPARE(created.getAliasValue(), -24); QCOMPARE(created.getAliasToOrigin(), -24); } class ANON_propertyChangeHandler : public QObject, public ContextRegistrator { Q_OBJECT QML_ANONYMOUS Q_PROPERTY(int dummy READ getDummy WRITE setDummy) Q_PROPERTY(int p READ getP WRITE setP BINDABLE bindableP) Q_PROPERTY(int watcher READ getWatcher WRITE setWatcher) 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_propertyChangeHandler(QQmlEngine *e, QObject *parent = nullptr) : QObject(parent), ContextRegistrator(e, this) { dummy = 42; QPropertyBinding ANON_propertyChangeHandler_p_binding( [&]() { QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this)); const auto index = FunctionIndices::PROPERTY_CHANGE_HANDLER_P_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); bindableP().setBinding(ANON_propertyChangeHandler_p_binding); watcher = 0; // NB: make sure property change handler appears after setBinding(). // this prevents preliminary binding evaluation (which would fail as // this object doesn't yet know about qmlEngine(this)) pChangeHandler.reset(new QPropertyChangeHandler( bindableP().onValueChanged(ANON_propertyChangeHandler_p_changeHandler(this)))); } int getDummy() { return dummy.value(); } int getP() { return p.value(); } int getWatcher() { return watcher.value(); } void setDummy(int dummy_) { dummy.setValue(dummy_); } void setP(int p_) { p.setValue(p_); } void setWatcher(int watcher_) { watcher.setValue(watcher_); } QBindable bindableP() { return QBindable(&p); } QProperty dummy; QProperty p; QProperty watcher; // property change handler: struct ANON_propertyChangeHandler_p_changeHandler { ANON_propertyChangeHandler *This = nullptr; ANON_propertyChangeHandler_p_changeHandler(ANON_propertyChangeHandler *obj) : This(obj) { } void operator()() { QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(This)); const auto index = FunctionIndices::PROPERTY_CHANGE_HANDLER_ON_P_CHANGED; e->executeRuntimeFunction(This->url, index, This); } }; // the handler object has to be alive as long as the object std::unique_ptr> pChangeHandler; }; QUrl ANON_propertyChangeHandler::url = QUrl(); // workaround void tst_qmlcompiler_manual::propertyChangeHandler() { QQmlEngine e; ANON_propertyChangeHandler::url = testFileUrl("propertyChangeHandler.qml"); ANON_propertyChangeHandler created(&e); // test that fetching "dirty" property value doesn't trigger property change // handler QCOMPARE(created.getWatcher(), 0); QCOMPARE(created.getP(), 42); // due to binding QCOMPARE(created.getWatcher(), 0); QCOMPARE(created.property("watcher").toInt(), 0); // test that binding triggers property change handler created.setDummy(20); QCOMPARE(created.getWatcher(), 20); QCOMPARE(created.property("watcher").toInt(), 20); // test that property setting (through C++) triggers property change handler created.setWatcher(-100); created.setProperty("p", 18); QCOMPARE(created.getWatcher(), 18); // test that property setting triggers property change handler created.setWatcher(-47); created.setP(96); QCOMPARE(created.property("watcher").toInt(), 96); } class ANON_propertyReturningFunction : public QObject, public ContextRegistrator { Q_OBJECT QML_ANONYMOUS Q_PROPERTY(int counter READ getCounter WRITE setCounter) Q_PROPERTY(QVariant f READ getF WRITE setF BINDABLE bindableF) 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_propertyReturningFunction(QQmlEngine *e, QObject *parent = nullptr) : QObject(parent), ContextRegistrator(e, this) { QPropertyBinding ANON_propertyReturningFunction_f_binding( [&]() { QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(this)); const auto index = FunctionIndices::PROPERTY_RETURNING_FUNCTION_F_BINDING; 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; }, QT_PROPERTY_DEFAULT_BINDING_LOCATION); bindableF().setBinding(ANON_propertyReturningFunction_f_binding); } int getCounter() { return counter.value(); } QVariant getF() { return f.value(); } void setCounter(int counter_) { counter.setValue(counter_); } void setF(QVariant f_) { f.setValue(f_); } QBindable bindableF() { return QBindable(&f); } QProperty counter; QProperty f; }; QUrl ANON_propertyReturningFunction::url = QUrl(); // workaround void tst_qmlcompiler_manual::propertyReturningFunction() { QQmlEngine e; ANON_propertyReturningFunction::url = testFileUrl("propertyReturningFunction.qml"); ANON_propertyReturningFunction created(&e); QCOMPARE(created.getCounter(), 0); QVariant function = created.getF(); Q_UNUSED(function); // ignored as it can't be used currently QCOMPARE(created.getCounter(), 0); created.property("f"); 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"