diff options
Diffstat (limited to 'tests/auto/qml/qqmlcomponent')
-rw-r--r-- | tests/auto/qml/qqmlcomponent/CMakeLists.txt | 11 | ||||
-rw-r--r-- | tests/auto/qml/qqmlcomponent/data/complexObjectArgument.qml | 28 | ||||
-rw-r--r-- | tests/auto/qml/qqmlcomponent/data/createObject.qml | 3 | ||||
-rw-r--r-- | tests/auto/qml/qqmlcomponent/data/createObjectWithScript.qml | 14 | ||||
-rw-r--r-- | tests/auto/qml/qqmlcomponent/data/createQmlObject.qml | 21 | ||||
-rw-r--r-- | tests/auto/qml/qqmlcomponent/data/dynamic.qml | 6 | ||||
-rw-r--r-- | tests/auto/qml/qqmlcomponent/data/removeBinding.qml | 32 | ||||
-rw-r--r-- | tests/auto/qml/qqmlcomponent/lifecyclewatcher.h | 24 | ||||
-rw-r--r-- | tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp | 186 |
9 files changed, 287 insertions, 38 deletions
diff --git a/tests/auto/qml/qqmlcomponent/CMakeLists.txt b/tests/auto/qml/qqmlcomponent/CMakeLists.txt index cab87ac08d..874c7f0cc3 100644 --- a/tests/auto/qml/qqmlcomponent/CMakeLists.txt +++ b/tests/auto/qml/qqmlcomponent/CMakeLists.txt @@ -7,6 +7,12 @@ ## tst_qqmlcomponent Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qqmlcomponent LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + # Collect test data file(GLOB_RECURSE test_data_glob RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} @@ -36,10 +42,13 @@ if(TARGET Qt::QuickControls2) target_compile_definitions(tst_qqmlcomponent PRIVATE HAS_CONTROLS) endif() +qt_policy(SET QTP0001 NEW) + qt_add_qml_module( tst_qqmlcomponent + SOURCES + lifecyclewatcher.h URI test - AUTO_RESOURCE_PREFIX QML_FILES "data/TestComponentWithIC.qml" "data/withAot.qml" diff --git a/tests/auto/qml/qqmlcomponent/data/complexObjectArgument.qml b/tests/auto/qml/qqmlcomponent/data/complexObjectArgument.qml new file mode 100644 index 0000000000..71676a4415 --- /dev/null +++ b/tests/auto/qml/qqmlcomponent/data/complexObjectArgument.qml @@ -0,0 +1,28 @@ +import QtQml 2.15 + +QtObject { + id: root + Component.onCompleted: { + function WithPrototype(refMsgSeqNr) { + this.init(refMsgSeqNr) + }; + + WithPrototype.prototype = { + init: function(refMsgSeqNr) { + this.testObj = { + has: function(a) { return a === refMsgSeqNr } + } + + this.protocolSubTypeID = 2 + this.messageControl = 0 + this.referredMsgSequenceNumber = refMsgSeqNr + } + }; + + let comp = Qt.createComponent("dynamic.qml"); + let inst1 = comp.createObject(root, { testObj: new Set(), }); + let inst2 = comp.createObject(root, new WithPrototype(1)); + + objectName = inst1.use() + " - " + inst2.use(); + } +} diff --git a/tests/auto/qml/qqmlcomponent/data/createObject.qml b/tests/auto/qml/qqmlcomponent/data/createObject.qml index afd9e71229..c9ca605f8f 100644 --- a/tests/auto/qml/qqmlcomponent/data/createObject.qml +++ b/tests/auto/qml/qqmlcomponent/data/createObject.qml @@ -1,5 +1,4 @@ -import QtQuick 2.0 -import QtQuick.Window 2.0 +import QtQuick Item { property QtObject qtobjectParent: QtObject { } diff --git a/tests/auto/qml/qqmlcomponent/data/createObjectWithScript.qml b/tests/auto/qml/qqmlcomponent/data/createObjectWithScript.qml index 3391b3a266..63aeb9415e 100644 --- a/tests/auto/qml/qqmlcomponent/data/createObjectWithScript.qml +++ b/tests/auto/qml/qqmlcomponent/data/createObjectWithScript.qml @@ -11,6 +11,9 @@ Item{ property QtObject badRequired: null property QtObject goodRequired: null + property QtObject bindingAsInitial: null + property bool bindingUsed: false + Component{ id: a Rectangle { @@ -21,7 +24,7 @@ Item{ id: b Item{ property bool testBool: false - property int testInt: null + property int testInt: { return null; } property QtObject testObject: null } } @@ -43,6 +46,11 @@ Item{ } } + Component { + id: e + Rectangle {} + } + Component.onCompleted: { root.declarativerectangle = a.createObject(root, {"x":17,"y":17, "color":"white", "border.width":3, "innerRect.border.width": 20}); root.declarativeitem = b.createObject(root, {"x":17,"y":17,"testBool":true,"testInt":17,"testObject":root}); @@ -52,5 +60,9 @@ Item{ root.badRequired = d.createObject(root, { "not_i": 42 }); root.goodRequired = d.createObject(root, { "i": 42 }); + + root.bindingAsInitial = e.createObject(root, {color: Qt.binding(() => { + root.bindingUsed = true; return '#ff0000' + })}); } } diff --git a/tests/auto/qml/qqmlcomponent/data/createQmlObject.qml b/tests/auto/qml/qqmlcomponent/data/createQmlObject.qml index 282ab509f0..480835a5b1 100644 --- a/tests/auto/qml/qqmlcomponent/data/createQmlObject.qml +++ b/tests/auto/qml/qqmlcomponent/data/createQmlObject.qml @@ -1,5 +1,4 @@ -import QtQuick 2.0 -import QtQuick.Window 2.0 +import QtQuick Item { property QtObject qtobjectParent: QtObject { } @@ -19,14 +18,14 @@ Item { property QtObject window_window : null Component.onCompleted: { - qtobject_qtobject = Qt.createQmlObject("import QtQuick 2.0; QtObject{}", qtobjectParent); - qtobject_item = Qt.createQmlObject("import QtQuick 2.0; Item{}", qtobjectParent); - qtobject_window = Qt.createQmlObject("import QtQuick.Window 2.0; Window{}", qtobjectParent); - item_qtobject = Qt.createQmlObject("import QtQuick 2.0; QtObject{}", itemParent); - item_item = Qt.createQmlObject("import QtQuick 2.0; Item{}", itemParent); - item_window = Qt.createQmlObject("import QtQuick.Window 2.0; Window{}", itemParent); - window_qtobject = Qt.createQmlObject("import QtQuick 2.0; QtObject{}", windowParent); - window_item = Qt.createQmlObject("import QtQuick 2.0; Item{}", windowParent); - window_window = Qt.createQmlObject("import QtQuick.Window 2.0; Window{}", windowParent); + qtobject_qtobject = Qt.createQmlObject("import QtQuick; QtObject{}", qtobjectParent); + qtobject_item = Qt.createQmlObject("import QtQuick; Item{}", qtobjectParent); + qtobject_window = Qt.createQmlObject("import QtQuick; Window{}", qtobjectParent); + item_qtobject = Qt.createQmlObject("import QtQuick; QtObject{}", itemParent); + item_item = Qt.createQmlObject("import QtQuick; Item{}", itemParent); + item_window = Qt.createQmlObject("import QtQuick; Window{}", itemParent); + window_qtobject = Qt.createQmlObject("import QtQuick; QtObject{}", windowParent); + window_item = Qt.createQmlObject("import QtQuick; Item{}", windowParent); + window_window = Qt.createQmlObject("import QtQuick; Window{}", windowParent); } } diff --git a/tests/auto/qml/qqmlcomponent/data/dynamic.qml b/tests/auto/qml/qqmlcomponent/data/dynamic.qml new file mode 100644 index 0000000000..b9a54d53ff --- /dev/null +++ b/tests/auto/qml/qqmlcomponent/data/dynamic.qml @@ -0,0 +1,6 @@ +import QtQml + +QtObject { + property var testObj + function use() { return testObj.has(1) ? 25 : 26; } +} diff --git a/tests/auto/qml/qqmlcomponent/data/removeBinding.qml b/tests/auto/qml/qqmlcomponent/data/removeBinding.qml new file mode 100644 index 0000000000..091f6991be --- /dev/null +++ b/tests/auto/qml/qqmlcomponent/data/removeBinding.qml @@ -0,0 +1,32 @@ +import QtQml 2.15 + +QtObject { + id: root + objectName: "400" + + property Component c: Component { + id: customItem + QtObject { + objectName: root.objectName + } + } + + property string result: { + const properties = { + "objectName": "42", + } + const item = customItem.createObject(root, properties) + return item.objectName; + } + + property string result2: { + const properties = { + "objectName": "43", + } + + // add some junk argument to trigger the QQmlV4Function overload + const item = customItem.createObject(root, properties, 13) + + return item.objectName; + } +} diff --git a/tests/auto/qml/qqmlcomponent/lifecyclewatcher.h b/tests/auto/qml/qqmlcomponent/lifecyclewatcher.h new file mode 100644 index 0000000000..e974681d25 --- /dev/null +++ b/tests/auto/qml/qqmlcomponent/lifecyclewatcher.h @@ -0,0 +1,24 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef LIFECYCLEWATCHER_H +#define LIFECYCLEWATCHER_H + +#include <QtQml/qqmlparserstatus.h> +#include <private/qqmlfinalizer_p.h> +#include <QtCore/qobject.h> +#include <QtQml/qqml.h> + +class LifeCycleWatcher : public QObject, public QQmlParserStatus, public QQmlFinalizerHook +{ + Q_OBJECT + QML_ELEMENT + Q_INTERFACES(QQmlParserStatus) + Q_INTERFACES(QQmlFinalizerHook) +public: + void classBegin() override {states.push_back(1); } + void componentComplete() override {states.push_back(2);}; + void componentFinalized() override { states.push_back(3); } + QList<int> states; +}; +#endif diff --git a/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp b/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp index 5203ba9615..ea06a11006 100644 --- a/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp +++ b/tests/auto/qml/qqmlcomponent/tst_qqmlcomponent.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <qtest.h> #include <QDebug> @@ -21,7 +21,7 @@ #include <private/qv4executablecompilationunit_p.h> #include <qcolor.h> #include <qsignalspy.h> - +#include "lifecyclewatcher.h" #include <algorithm> using namespace Qt::StringLiterals; @@ -90,13 +90,6 @@ public slots: } }; -static void gc(QQmlEngine &engine) -{ - engine.collectGarbage(); - QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - QCoreApplication::processEvents(); -} - class tst_qqmlcomponent : public QQmlDataTest { Q_OBJECT @@ -140,11 +133,16 @@ private slots: void boundComponent(); void loadFromModule_data(); void loadFromModule(); + void loadFromModuleLifecycle(); void loadFromModuleThenCreateWithIncubator(); void loadFromModuleFailures_data(); void loadFromModuleFailures(); void loadFromModuleRequired(); void loadFromQrc(); + void removeBinding(); + void complexObjectArgument(); + void bindingEvaluationOrder(); + void compilationUnitsWithSameUrl(); private: QQmlEngine engine; @@ -182,14 +180,12 @@ void tst_qqmlcomponent::loadEmptyUrl() void tst_qqmlcomponent::qmlIncubateObject() { QQmlComponent component(&engine, testFileUrl("incubateObject.qml")); - QObject *object = component.create(); + 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()); - - delete object; } void tst_qqmlcomponent::qmlCreateWindow() @@ -282,7 +278,7 @@ void tst_qqmlcomponent::qmlCreateObjectWithProperties() QTest::ignoreMessage( QtMsgType::QtWarningMsg, QRegularExpression( - ".*createObjectWithScript.qml:42:13: Required property i was not initialized")); + ".*createObjectWithScript.qml:45:13: Required property i was not initialized")); QQmlComponent component(&engine, testFileUrl("createObjectWithScript.qml")); QVERIFY2(component.errorString().isEmpty(), component.errorString().toUtf8()); @@ -342,6 +338,12 @@ void tst_qqmlcomponent::qmlCreateObjectWithProperties() QCOMPARE(goodRequired->parent(), object.data()); QCOMPARE(goodRequired->property("i").value<int>(), 42); } + + { + QScopedPointer<QObject> bindingAsInitial(object->property("bindingAsInitial").value<QObject *>()); + QVERIFY(bindingAsInitial); + QVERIFY(object->property("bindingUsed").toBool()); + } } void tst_qqmlcomponent::qmlCreateObjectClean() @@ -390,11 +392,11 @@ void tst_qqmlcomponent::qmlCreateParentReference() QQmlComponent component(&engine, testFileUrl("createParentReference.qml")); QVERIFY2(component.errorString().isEmpty(), component.errorString().toUtf8()); - QObject *object = component.create(); + std::unique_ptr<QObject> object { component.create() }; QVERIFY(object != nullptr); - QVERIFY(QMetaObject::invokeMethod(object, "createChild")); - delete object; + QVERIFY(QMetaObject::invokeMethod(object.get(), "createChild")); + object.reset(); engine.setOutputWarningsToStandardError(false); QCOMPARE(engine.outputWarningsToStandardError(), false); @@ -416,10 +418,8 @@ void tst_qqmlcomponent::async() QCOMPARE(watcher.ready, 1); QCOMPARE(watcher.error, 0); - QObject *object = component.create(); + std::unique_ptr<QObject> object { component.create() }; QVERIFY(object != nullptr); - - delete object; } void tst_qqmlcomponent::asyncHierarchy() @@ -437,7 +437,7 @@ void tst_qqmlcomponent::asyncHierarchy() QCOMPARE(watcher.ready, 1); QCOMPARE(watcher.error, 0); - QObject *root = component.create(); + std::unique_ptr<QObject> root { component.create() }; QVERIFY(root != nullptr); // ensure that the parent-child relationship hierarchy is correct @@ -461,8 +461,6 @@ void tst_qqmlcomponent::asyncHierarchy() // ensure that values and bindings are assigned correctly QVERIFY(root->property("success").toBool()); - - delete root; } void tst_qqmlcomponent::asyncForceSync() @@ -1245,12 +1243,14 @@ void tst_qqmlcomponent::boundComponent() { 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); @@ -1269,6 +1269,7 @@ void tst_qqmlcomponent::boundComponent() { 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())); @@ -1282,11 +1283,22 @@ void tst_qqmlcomponent::boundComponent() { 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(), ":2 Syntax error\n"_L1); + QVERIFY(!component.isBound()); } } @@ -1334,6 +1346,34 @@ void tst_qqmlcomponent::loadFromModule() name); } +void tst_qqmlcomponent::loadFromModuleLifecycle() +{ + QQmlEngine engine; + QList<int> loadFromModuleOrder; + QList<int> plainLoadOrder; + const QList<int> expected {1, 2, 3}; + { + QQmlComponent component(&engine); + component.loadFromModule("test", "LifeCycleWatcher"); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + std::unique_ptr<QObject> root{ component.create() }; + LifeCycleWatcher *watcher = qobject_cast<LifeCycleWatcher *>(root.get()); + QVERIFY(watcher); + loadFromModuleOrder = watcher->states; + QCOMPARE(loadFromModuleOrder, expected); + } + { + QQmlComponent component(&engine); + component.setData("import test; LifeCycleWatcher {}", {}); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + std::unique_ptr<QObject> root{ component.create() }; + LifeCycleWatcher *watcher = qobject_cast<LifeCycleWatcher *>(root.get()); + QVERIFY(watcher); + plainLoadOrder = watcher->states; + } + QCOMPARE(loadFromModuleOrder, plainLoadOrder); +} + struct CallVerifyingIncubtor : QQmlIncubator { void setInitialState(QObject *) override { setInitialStateCalled = true; } @@ -1373,6 +1413,10 @@ void tst_qqmlcomponent::loadFromModuleFailures_data() 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() @@ -1384,7 +1428,11 @@ void tst_qqmlcomponent::loadFromModuleFailures() 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); @@ -1422,7 +1470,99 @@ void tst_qqmlcomponent::loadFromQrc() QQmlComponentPrivate *p = QQmlComponentPrivate::get(&component); QVERIFY(p); QVERIFY(p->compilationUnit); - QVERIFY(p->compilationUnit->aotCompiledFunctions); + QVERIFY(p->compilationUnit->baseCompilationUnit()->aotCompiledFunctions); +} + +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"); + } } QTEST_MAIN(tst_qqmlcomponent) |