diff options
Diffstat (limited to 'tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp')
-rw-r--r-- | tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp | 1082 |
1 files changed, 1032 insertions, 50 deletions
diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index fee65fcb17..a87aabafda 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -1,5 +1,6 @@ // 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 <QtQml/qqmlengine.h> #include <QtQml/qqmlcomponent.h> @@ -316,6 +317,7 @@ private slots: void inlineComponentFoundBeforeOtherImports(); void inlineComponentDuplicateNameError(); void inlineComponentWithAliasInstantiatedWithNewProperties(); + void inlineComponentWithImplicitComponent(); void selfReference(); void selfReferencingSingleton(); @@ -358,6 +360,8 @@ private slots: void hangOnWarning(); + void groupPropertyFromNonExposedBaseClass(); + void listEnumConversion(); void deepInlineComponentScriptBinding(); @@ -402,10 +406,59 @@ private slots: void importPrecedence(); void nullIsNull(); void multiRequired(); + void isNullOrUndefined(); void objectAndGadgetMethodCallsRejectThisObject(); void objectAndGadgetMethodCallsAcceptThisObject(); + void asValueType(); + void asValueTypeGood(); + + void longConversion(); + + void enumPropsManyUnderylingTypes(); + + void typedEnums_data(); + void typedEnums(); + + void objectMethodClone(); + void unregisteredValueTypeConversion(); + void retainThis(); + + void variantObjectList(); + void jitExceptions(); + + void attachedInCtor(); + void byteArrayConversion(); + void propertySignalNames_data(); + void propertySignalNames(); + void signalNames_data(); + void signalNames(); + + void callMethodOfAttachedDerived(); + + void multiVersionSingletons(); + void typeAnnotationCycle(); + void corpseInQmlList(); + void objectInQmlListAndGc(); + void asCastToInlineComponent(); + void deepAliasOnICOrReadonly(); + + void optionalChainCallOnNullProperty(); + + void ambiguousComponents(); + + void writeNumberToEnumAlias(); + void badInlineComponentAnnotation(); + void manuallyCallSignalHandler(); + void overrideDefaultProperty(); + void enumScopes(); + + void typedObjectList(); + void invokableCtors(); + + void jsonArrayPropertyBehavesLikeAnArray(); + private: QQmlEngine engine; QStringList defaultImportPathList; @@ -492,11 +545,11 @@ void tst_qqmllanguage::insertedSemicolon() QQmlComponent component(&engine, testFileUrl(file)); - QScopedPointer<QObject> object; + std::unique_ptr<QObject> object; if(create) { object.reset(component.create()); - QVERIFY(object.isNull()); + QVERIFY(object.get()); } VERIFY_ERRORS(errorFile.toLatin1().constData()); @@ -1381,13 +1434,13 @@ void tst_qqmllanguage::rootItemIsComponent() QtWarningMsg, QRegularExpression( ".*/rootItemIsComponent\\.qml:3:1: Using a Component as the root of " - "a qmldocument is deprecated: types defined in qml documents are automatically " - "wrapped into Components when needed\\.")); + "a QML document is deprecated: types defined in qml documents are " + "automatically wrapped into Components when needed\\.")); QTest::ignoreMessage( QtWarningMsg, QRegularExpression( ".*/EvilComponentType\\.qml:3:1: Using a Component as the root of a " - "qmldocument is deprecated: types defined in qml documents are automatically " + "QML document is deprecated: types defined in qml documents are automatically " "wrapped into Components when needed\\.")); QTest::ignoreMessage( QtWarningMsg, @@ -1707,8 +1760,8 @@ void tst_qqmllanguage::propertyValueSource() QVERIFY(object != nullptr); QList<QObject *> valueSources; - QObjectList allChildren = object->findChildren<QObject*>(); - foreach (QObject *child, allChildren) { + const QObjectList allChildren = object->findChildren<QObject*>(); + for (QObject *child : allChildren) { if (qobject_cast<QQmlPropertyValueSource *>(child)) valueSources.append(child); } @@ -1728,8 +1781,8 @@ void tst_qqmllanguage::propertyValueSource() QVERIFY(object != nullptr); QList<QObject *> valueSources; - QObjectList allChildren = object->findChildren<QObject*>(); - foreach (QObject *child, allChildren) { + const QObjectList allChildren = object->findChildren<QObject*>(); + for (QObject *child : allChildren) { if (qobject_cast<QQmlPropertyValueSource *>(child)) valueSources.append(child); } @@ -2039,7 +2092,7 @@ void tst_qqmllanguage::aliasProperties() MyQmlObject *o = qvariant_cast<MyQmlObject*>(v); QCOMPARE(o->value(), 10); - delete o; + delete o; //intentional delete v = object->property("otherAlias"); QCOMPARE(v.typeId(), qMetaTypeId<MyQmlObject *>()); @@ -2074,7 +2127,7 @@ void tst_qqmllanguage::aliasProperties() QObject *alias = qvariant_cast<QObject *>(object->property("aliasedObject")); QCOMPARE(alias, object2); - delete object1; + delete object1; //intentional delete QObject *alias2 = object.data(); // "Random" start value int status = -1; @@ -2583,7 +2636,7 @@ void tst_qqmllanguage::scriptStringWithoutSourceCode() Q_ASSERT(td); QVERIFY(!td->backupSourceCode().isValid()); - QQmlRefPointer<QV4::ExecutableCompilationUnit> compilationUnit = td->compilationUnit(); + QQmlRefPointer<QV4::CompiledData::CompilationUnit> compilationUnit = td->compilationUnit(); readOnlyQmlUnit.reset(compilationUnit->unitData()); Q_ASSERT(readOnlyQmlUnit); QV4::CompiledData::Unit *qmlUnit = reinterpret_cast<QV4::CompiledData::Unit *>(malloc(readOnlyQmlUnit->unitSize)); @@ -2863,7 +2916,8 @@ void tst_qqmllanguage::testType(const QString& qml, const QString& type, const Q if (type.isEmpty()) { QVERIFY(component.isError()); QString actualerror; - foreach (const QQmlError e, component.errors()) { + const auto errors = component.errors(); + for (const QQmlError &e : errors) { if (!actualerror.isEmpty()) actualerror.append("; "); actualerror.append(e.description()); @@ -3861,6 +3915,7 @@ void tst_qqmllanguage::initTestCase() qmlRegisterType(testFileUrl("invalidRoot.1.qml"), "Test", 1, 0, "RegisteredCompositeType3"); qmlRegisterType(testFileUrl("CompositeTypeWithEnum.qml"), "Test", 1, 0, "RegisteredCompositeTypeWithEnum"); qmlRegisterType(testFileUrl("CompositeTypeWithAttachedProperty.qml"), "Test", 1, 0, "RegisteredCompositeTypeWithAttachedProperty"); + qmlRegisterType(testFileUrl("CompositeTypeWithEnumSelfReference.qml"), "Test", 1, 0, "CompositeTypeWithEnumSelfReference"); // Registering the TestType class in other modules should have no adverse effects qmlRegisterType<TestType>("org.qtproject.TestPre", 1, 0, "Test"); @@ -3889,6 +3944,7 @@ void tst_qqmllanguage::initTestCase() // Register a Composite Singleton. qmlRegisterSingletonType(testFileUrl("singleton/RegisteredCompositeSingletonType.qml"), "org.qtproject.Test", 1, 0, "RegisteredSingleton"); + qmlRegisterType(testFileUrl("Comps/OverlayDrawer.qml"), "Comps", 2, 0, "OverlayDrawer"); } void tst_qqmllanguage::aliasPropertyChangeSignals() @@ -4036,6 +4092,17 @@ void tst_qqmllanguage::registeredCompositeTypeWithEnum() QCOMPARE(o->property("enumValue0").toInt(), static_cast<int>(MyCompositeBaseType::EnumValue0)); QCOMPARE(o->property("enumValue42").toInt(), static_cast<int>(MyCompositeBaseType::EnumValue42)); QCOMPARE(o->property("enumValue15").toInt(), static_cast<int>(MyCompositeBaseType::ScopedCompositeEnum::EnumValue15)); + + { + QQmlComponent component(&engine); + component.setData("import Test\nCompositeTypeWithEnumSelfReference {}", QUrl()); + VERIFY_ERRORS(0); + QScopedPointer<QObject> o(component.create()); + QVERIFY(o != nullptr); + + QCOMPARE(o->property("e").toInt(), 1); + QCOMPARE(o->property("f").toInt(), 2); + } } // QTBUG-43581 @@ -4212,7 +4279,8 @@ void tst_qqmllanguage::lowercaseEnumRuntime() QQmlComponent component(&engine, testFileUrl(file)); VERIFY_ERRORS(0); - delete component.create(); + std::unique_ptr<QObject> root { component.create() }; + QVERIFY(root); } void tst_qqmllanguage::lowercaseEnumCompileTime_data() @@ -4229,7 +4297,8 @@ void tst_qqmllanguage::lowercaseEnumCompileTime() QQmlComponent component(&engine, testFileUrl(file)); VERIFY_ERRORS(0); - delete component.create(); + std::unique_ptr<QObject> root { component.create() }; + QVERIFY(root); } void tst_qqmllanguage::scopedEnum() @@ -4473,7 +4542,6 @@ void tst_qqmllanguage::groupAssignmentFailure() { auto ep = std::make_unique<QQmlEngine>(); QTest::failOnWarning("QQmlComponent: Component destroyed while completion pending"); - QTest::ignoreMessage(QtMsgType::QtWarningMsg, QRegularExpression(".*Invalid property assignment: url expected - Assigning null to incompatible properties in QML is deprecated. This will become a compile error in future versions of Qt..*")); QQmlComponent component(ep.get(), testFileUrl("groupFailure.qml")); QScopedPointer<QObject> o(component.create()); QVERIFY(!o); @@ -5348,24 +5416,30 @@ void tst_qqmllanguage::namespacedPropertyTypes() void tst_qqmllanguage::qmlTypeCanBeResolvedByName_data() { QTest::addColumn<QUrl>("componentUrl"); + QTest::addColumn<QString>("name"); // Built-in C++ types - QTest::newRow("C++ - Anonymous") << testFileUrl("quickTypeByName_anon.qml"); - QTest::newRow("C++ - Named") << testFileUrl("quickTypeByName_named.qml"); + QTest::newRow("C++ - Anonymous") << testFileUrl("quickTypeByName_anon.qml") + << QStringLiteral("QtQuick/Item"); + QTest::newRow("C++ - Named") << testFileUrl("quickTypeByName_named.qml") + << QStringLiteral("QtQuick/Item"); // Composite types with a qmldir - QTest::newRow("QML - Anonymous - qmldir") << testFileUrl("compositeTypeByName_anon_qmldir.qml"); - QTest::newRow("QML - Named - qmldir") << testFileUrl("compositeTypeByName_named_qmldir.qml"); + QTest::newRow("QML - Anonymous - qmldir") << testFileUrl("compositeTypeByName_anon_qmldir.qml") + << QStringLiteral("SimpleType"); + QTest::newRow("QML - Named - qmldir") << testFileUrl("compositeTypeByName_named_qmldir.qml") + << QStringLiteral("SimpleType"); } void tst_qqmllanguage::qmlTypeCanBeResolvedByName() { QFETCH(QUrl, componentUrl); + QFETCH(QString, name); QQmlEngine engine; QQmlComponent component(&engine, componentUrl); VERIFY_ERRORS(0); - QTest::ignoreMessage(QtMsgType::QtWarningMsg, "[object Object]"); // a bit crude, but it will do + QTest::ignoreMessage(QtMsgType::QtWarningMsg, qPrintable(name)); QScopedPointer<QObject> o(component.create()); QVERIFY(!o.isNull()); @@ -5749,7 +5823,7 @@ void tst_qqmllanguage::selfReference() const QMetaObject *metaObject = o->metaObject(); QMetaProperty selfProperty = metaObject->property(metaObject->indexOfProperty("self")); - QCOMPARE(selfProperty.metaType().id(), compilationUnit->typeIds.id.id()); + QCOMPARE(selfProperty.metaType().id(), compilationUnit->metaType().id()); QByteArray typeName = selfProperty.typeName(); QVERIFY(typeName.endsWith('*')); @@ -5758,7 +5832,7 @@ void tst_qqmllanguage::selfReference() QMetaMethod selfFunction = metaObject->method(metaObject->indexOfMethod("returnSelf()")); QVERIFY(selfFunction.isValid()); - QCOMPARE(selfFunction.returnType(), compilationUnit->typeIds.id.id()); + QCOMPARE(selfFunction.returnType(), compilationUnit->metaType().id()); QMetaMethod selfSignal; @@ -5772,7 +5846,7 @@ void tst_qqmllanguage::selfReference() QVERIFY(selfSignal.isValid()); QCOMPARE(selfSignal.parameterCount(), 1); - QCOMPARE(selfSignal.parameterType(0), compilationUnit->typeIds.id.id()); + QCOMPARE(selfSignal.parameterType(0), compilationUnit->metaType().id()); } void tst_qqmllanguage::selfReferencingSingleton() @@ -5809,10 +5883,10 @@ void tst_qqmllanguage::listContainingDeletedObject() QVERIFY(root); auto cmp = root->property("a").value<QQmlComponent*>(); - auto o = cmp->create(); + std::unique_ptr<QObject> o { cmp->create() }; - QMetaObject::invokeMethod(root.get(), "doAssign", Q_ARG(QVariant, QVariant::fromValue(o))); - delete o; + QMetaObject::invokeMethod(root.get(), "doAssign", Q_ARG(QVariant, QVariant::fromValue(o.get()))); + o.reset(); QMetaObject::invokeMethod(root.get(), "use"); } @@ -6096,6 +6170,17 @@ void tst_qqmllanguage::inlineComponentWithAliasInstantiatedWithNewProperties() QCOMPARE(root->property("result").toString(), "Bar"); } +void tst_qqmllanguage::inlineComponentWithImplicitComponent() +{ + QQmlEngine engine; + QQmlComponent component(&engine, testFileUrl("inlineComponentWithImplicitComponent.qml")); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> root(component.create()); + QVERIFY(root); + + QCOMPARE(root->objectName(), "green blue"_L1); +} + struct QJSValueConvertible { Q_GADGET @@ -6392,23 +6477,15 @@ void tst_qqmllanguage::extendedSingleton() void tst_qqmllanguage::qtbug_85932() { - QString warning1 = QLatin1String("%1:10:9: id is not unique").arg(testFileUrl("SingletonTest.qml").toString()); - QString warning2 = QLatin1String("%1:4: Error: Due to the preceding error(s), Singleton \"SingletonTest\" could not be loaded.").arg(testFileUrl("qtbug_85932.qml").toString()); - - QTest::ignoreMessage(QtMsgType::QtWarningMsg, qPrintable(warning1)); - QTest::ignoreMessage(QtMsgType::QtWarningMsg, qPrintable(warning2)); - QQmlEngine engine; - QList<QQmlError> allWarnings; - QObject::connect(&engine, &QQmlEngine::warnings, [&allWarnings](const QList<QQmlError> &warnings) { - allWarnings.append(warnings); - }); - QQmlComponent c(&engine, testFileUrl("qtbug_85932.qml")); - QScopedPointer<QObject> obj(c.create()); - QTRY_COMPARE(allWarnings.size(), 2); - QCOMPARE(allWarnings.at(0).toString(), warning1); - QCOMPARE(allWarnings.at(1).toString(), warning2); + QQmlComponent c(&engine, testFileUrl("badSingleton/qtbug_85932.qml")); + QVERIFY(c.isError()); + + const QString error = c.errorString(); + QVERIFY(error.contains(QLatin1String("Type SingletonTest unavailable"))); + QVERIFY(error.contains(QLatin1String("%1:10 id is not unique") + .arg(testFileUrl("badSingleton/SingletonTest.qml").toString()))); } void tst_qqmllanguage::multiExtension() @@ -6758,15 +6835,22 @@ void tst_qqmllanguage::bareInlineComponent() if (type.elementName() == QStringLiteral("Tab1")) { QVERIFY(type.module().isEmpty()); tab1Found = true; - const auto ics = type.priv()->objectIdToICType; - QVERIFY(ics.size() > 0); - for (const QQmlType &ic : ics) - QVERIFY(ic.containingType() == type); + + const QQmlType leftTab = QQmlMetaType::inlineComponentType(type, "LeftTab"); + QUrl leftUrl = leftTab.sourceUrl(); + leftUrl.setFragment(QString()); + QCOMPARE(leftUrl, type.sourceUrl()); + + const QQmlType rightTab = QQmlMetaType::inlineComponentType(type, "RightTab"); + QUrl rightUrl = rightTab.sourceUrl(); + rightUrl.setFragment(QString()); + QCOMPARE(rightUrl, type.sourceUrl()); } } QVERIFY(tab1Found); } +#if QT_CONFIG(qml_debug) struct DummyDebugger : public QV4::Debugging::Debugger { bool pauseAtNextOpportunity() const final { return false; } @@ -6775,6 +6859,9 @@ struct DummyDebugger : public QV4::Debugging::Debugger void leavingFunction(const QV4::ReturnedValue &) final { } void aboutToThrow() final { } }; +#else +using DummyDebugger = QV4::Debugging::Debugger; // it's already dummy +#endif void tst_qqmllanguage::hangOnWarning() { @@ -6792,6 +6879,25 @@ void tst_qqmllanguage::hangOnWarning() QVERIFY(object != nullptr); } +void tst_qqmllanguage::groupPropertyFromNonExposedBaseClass() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("derivedFromUnexposedBase.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + auto root = qobject_cast<DerivedFromUnexposedBase *>(o.get()); + QVERIFY(root); + QVERIFY(root->group); + QCOMPARE(root->group->value, 42); + QCOMPARE(root->groupGadget.value, 42); + + c.loadUrl(testFileUrl("dynamicGroupPropertyRejected.qml")); + QVERIFY(c.isError()); + QVERIFY2(c.errorString().contains("Unsupported grouped property access"), qPrintable(c.errorString())); +} + void tst_qqmllanguage::listEnumConversion() { QQmlEngine e; @@ -7424,6 +7530,33 @@ LeakingForeignerForeign { QVERIFY(o->property("anotherAbc").isValid()); QVERIFY(!o->property("abc").isValid()); } + + { + QQmlComponent c(&engine); + c.setData(R"( +import StaticTest +import QtQml +QtObject { + objectName: 'b' + ForeignNamespaceForeign.B +})", QUrl()); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); + QCOMPARE(o->objectName(), "b1"); + } + { + QQmlComponent c(&engine); + c.setData(R"( +import StaticTest +import QtQml +QtObject { + objectName: 'b' + LeakingForeignNamespaceForeign.B +})", QUrl()); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); + QCOMPARE(o->objectName(), "b2"); + } } void tst_qqmllanguage::attachedOwnProperties() @@ -7711,12 +7844,20 @@ void tst_qqmllanguage::functionSignatureEnforcement() QCOMPARE(ignored->property("m").toInt(), 77); QCOMPARE(ignored->property("n").toInt(), 67); - QQmlComponent c2(&engine, testFileUrl("signatureEnforced.qml")); + const QUrl url2 = testFileUrl("signatureEnforced.qml"); + QQmlComponent c2(&engine, url2); QVERIFY2(c2.isReady(), qPrintable(c2.errorString())); + QTest::ignoreMessage( + QtCriticalMsg, + qPrintable(url2.toString() + u":36: 15 should be coerced to void because the function " + "called is insufficiently annotated. The original value " + "is retained. " + "This will change in a future version of Qt."_s)); + QScopedPointer<QObject> enforced(c2.create()); QCOMPARE(enforced->property("l").toInt(), 2); // strlen("no") - QCOMPARE(enforced->property("m").toInt(), 12); + QCOMPARE(enforced->property("m").toInt(), 77); QCOMPARE(enforced->property("n").toInt(), 99); QCOMPARE(enforced->property("o").toInt(), 77); } @@ -7766,6 +7907,30 @@ void tst_qqmllanguage::multiRequired() qPrintable(url.toString() + ":5 Required property description was not initialized\n")); } +// QTBUG-111088 +void tst_qqmllanguage::isNullOrUndefined() +{ + { + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("isNullOrUndefined_interpreter.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVariant result = o.data()->property("result"); + QVERIFY(result.isValid()); + QCOMPARE(result.toInt(), 3); + } + + { + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("isNullOrUndefined_jit.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVariant result = o.data()->property("result"); + QVERIFY(result.isValid()); + QCOMPARE(result.toInt(), 150); + } +} + void tst_qqmllanguage::objectAndGadgetMethodCallsRejectThisObject() { QQmlEngine engine; @@ -7810,6 +7975,15 @@ void tst_qqmllanguage::objectAndGadgetMethodCallsAcceptThisObject() QQmlComponent c(&engine, testFileUrl("objectAndGadgetMethodCallsAcceptThisObject.qml")); QVERIFY2(c.isReady(), qPrintable(c.errorString())); + // Explicitly retrieve the metaobject for the Qt singleton so that the proxy data is created. + // This way the inheritance analysis we do when figuring out what toString() means is somewhat + // more interesting. Also, we get a deterministic result for Qt.toString(). + const QQmlType qtType = QQmlMetaType::qmlType(QStringLiteral("Qt"), QString(), QTypeRevision()); + QVERIFY(qtType.isValid()); + const QMetaObject *qtMeta = qtType.metaObject(); + QVERIFY(qtMeta); + QCOMPARE(QString::fromUtf8(qtMeta->className()), QLatin1String("Qt")); + QTest::ignoreMessage( QtWarningMsg, QRegularExpression( "objectAndGadgetMethodCallsAcceptThisObject.qml:16: Error: " @@ -7840,7 +8014,7 @@ void tst_qqmllanguage::objectAndGadgetMethodCallsAcceptThisObject() QCOMPARE(o->property("goodString2"), QStringLiteral("27")); QCOMPARE(o->property("goodString3"), QStringLiteral("28")); - QVERIFY(o->property("goodString4").value<QString>().startsWith("QtObject"_L1)); + QVERIFY(o->property("goodString4").value<QString>().startsWith("Qt("_L1)); QCOMPARE(o->property("badString2"), QString()); QCOMPARE(o->property("badInt"), 0); @@ -7849,6 +8023,814 @@ void tst_qqmllanguage::objectAndGadgetMethodCallsAcceptThisObject() QCOMPARE(o->property("goodInt3"), 5); } +void tst_qqmllanguage::longConversion() +{ + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("longConversion.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + for (const char *prop : { + "testProp", + "testQProp", + "fromLocal", + "fromQLocal", + "fromBoolean", + "fromQBoolean"}) { + const QVariant val = o->property(prop); + QVERIFY(val.isValid()); + QCOMPARE(val.metaType(), QMetaType::fromType<bool>()); + QVERIFY(!val.toBool()); + } +} + +void tst_qqmllanguage::enumPropsManyUnderylingTypes() +{ + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("enumPropsManyUnderlyingTypes.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + auto *enumObject = qobject_cast<EnumPropsManyUnderlyingTypes *>(o.get()); + QCOMPARE(enumObject->si8prop, EnumPropsManyUnderlyingTypes::ResolvedValue); + QCOMPARE(enumObject->ui8prop, EnumPropsManyUnderlyingTypes::ResolvedValue); + QCOMPARE(enumObject->si16prop, EnumPropsManyUnderlyingTypes::ResolvedValue); + QCOMPARE(enumObject->ui16prop, EnumPropsManyUnderlyingTypes::ResolvedValue); + QCOMPARE(enumObject->si64prop, EnumPropsManyUnderlyingTypes::ResolvedValue); + QCOMPARE(enumObject->ui64prop, EnumPropsManyUnderlyingTypes::ResolvedValue); +} + +void tst_qqmllanguage::asValueType() +{ + QQmlEngine engine; + const QUrl url = testFileUrl("asValueType.qml"); + QQmlComponent c(&engine, url); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + QTest::ignoreMessage( + QtWarningMsg, + "Could not find any constructor for value type QQmlRectFValueType " + "to call with value undefined"); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":7:5: Unable to assign [undefined] to QRectF"_L1)); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":10: Coercing a value to QtQml.Base/point using a type " + "assertion. This behavior is deprecated. Add 'pragma " + "ValueTypeBehavior: Assertable' to prevent it."_L1)); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":11: Coercing a value to StaticTest/withString using a " + "type assertion. This behavior is deprecated. Add 'pragma " + "ValueTypeBehavior: Assertable' to prevent it."_L1)); + + QScopedPointer<QObject> o(c.create()); + + QCOMPARE(o->property("a"), QVariant()); + QCOMPARE(o->property("b").value<QRectF>(), QRectF()); + QVERIFY(!o->property("c").toBool()); + + const QRectF rect(1, 2, 3, 4); + o->setProperty("a", QVariant(rect)); + QCOMPARE(o->property("b").value<QRectF>(), rect); + QVERIFY(o->property("c").toBool()); + + QVERIFY(!o->property("d").toBool()); + const QPointF point = o->property("e").value<QPointF>(); + QCOMPARE(point.x(), 10.0); + QCOMPARE(point.y(), 20.0); + + const ValueTypeWithString withString = o->property("f").value<ValueTypeWithString>(); + QCOMPARE(withString.toString(), u"red"); + + const QVariant string = o->property("g"); + QCOMPARE(string.metaType(), QMetaType::fromType<QString>()); + QCOMPARE(string.toString(), u"green"); +} + +void tst_qqmllanguage::asValueTypeGood() +{ + QQmlEngine engine; + const QUrl url = testFileUrl("asValueTypeGood.qml"); + QQmlComponent c(&engine, url); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":7:5: Unable to assign [undefined] to QRectF"_L1)); + QScopedPointer<QObject> o(c.create()); + + QCOMPARE(o->property("a"), QVariant()); + QCOMPARE(o->property("b").value<QRectF>(), QRectF()); + QVERIFY(!o->property("c").toBool()); + + const QRectF rect(1, 2, 3, 4); + o->setProperty("a", QVariant(rect)); + QCOMPARE(o->property("b").value<QRectF>(), rect); + QVERIFY(o->property("c").toBool()); + + QVERIFY(!o->property("d").toBool()); + QVERIFY(!o->property("e").isValid()); + QVERIFY(!o->property("f").isValid()); + + const QVariant string = o->property("g"); + QCOMPARE(string.metaType(), QMetaType::fromType<QString>()); + QCOMPARE(string.toString(), u"green"); + + const ValueTypeWithString withString = o->property("h").value<ValueTypeWithString>(); + QCOMPARE(withString.toString(), u"red"); + + const QPointF point = o->property("i").value<QPointF>(); + QCOMPARE(point.x(), 10.0); + QCOMPARE(point.y(), 20.0); + + const QVariant j = o->property("j"); + QCOMPARE(j.metaType(), QMetaType::fromType<int>()); + QCOMPARE(j.toInt(), 4); + + QVERIFY(!o->property("k").isValid()); + QVERIFY(!o->property("l").isValid()); + + const QVariant m = o->property("m"); + QCOMPARE(m.metaType(), QMetaType::fromType<QString>()); + QCOMPARE(m.toString(), u"something"); + + QVERIFY(!o->property("n").isValid()); + QVERIFY(!o->property("o").isValid()); +} + +void tst_qqmllanguage::typedEnums_data() +{ + QTest::addColumn<QString>("property"); + QTest::addColumn<double>("value"); + const QMetaObject *mo = &TypedEnums::staticMetaObject; + for (int i = 0, end = mo->enumeratorCount(); i != end; ++i) { + const QMetaEnum e = mo->enumerator(i); + for (int k = 0, end = e.keyCount(); k != end; ++k) { + QTest::addRow("%s::%s", e.name(), e.key(k)) + << QString::fromLatin1(e.name()).toLower() + << double(e.value(k)); + } + } +} +void tst_qqmllanguage::typedEnums() +{ + QFETCH(QString, property); + QFETCH(double, value); + QQmlEngine e; + const QString qml = QLatin1String(R"( + import QtQml + import TypedEnums + ObjectWithEnums { + property real input: %2 + %1: input + g.%1: input + property real output1: %1 + property real output2: g.%1 + } + )").arg(property).arg(value, 0, 'f'); + QQmlComponent c(&engine); + c.setData(qml.toUtf8(), QUrl("enums.qml"_L1)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + // TODO: This silently fails for quint32, qint64 and quint64 because QMetaEnum cannot encode + // such values either. For the 64bit values we'll also need a better type than double + // inside QML. + QEXPECT_FAIL("E32U::E32UD", "Not supported", Abort); + QEXPECT_FAIL("E32U::E32UE", "Not supported", Abort); + QEXPECT_FAIL("E64U::E64UE", "Not supported", Abort); + + QCOMPARE(o->property("output1").toDouble(), value); + QCOMPARE(o->property("output2").toDouble(), value); +} + +void tst_qqmllanguage::objectMethodClone() +{ + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("objectMethodClone.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QTRY_COMPARE(o->property("doneClicks").toInt(), 2); +} + +void tst_qqmllanguage::unregisteredValueTypeConversion() +{ + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("unregisteredValueTypeConversion.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + UnregisteredValueTypeHandler *handler = qobject_cast<UnregisteredValueTypeHandler *>(o.data()); + Q_ASSERT(handler); + QCOMPARE(handler->consumed, 2); + QCOMPARE(handler->gadgeted, 1); +} + +void tst_qqmllanguage::retainThis() +{ + QQmlEngine e; + const QUrl url = testFileUrl("retainThis.qml"); + QQmlComponent c(&e, url); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + const QString warning = u"Calling C++ methods with 'this' objects different " + "from the one they were retrieved from is broken, due to " + "historical reasons. The original object is used as 'this' " + "object. You can allow the given 'this' object to be used " + "by setting 'pragma NativeMethodBehavior: AcceptThisObject'"_s; + + // Both cases objA because we retain the thisObject. + for (int i = 0; i < 2; ++i) { + QTest::ignoreMessage(QtWarningMsg, qPrintable(url.toString() + u":12: "_s + warning)); + QTest::ignoreMessage(QtDebugMsg, "objA says hello"); + QTest::ignoreMessage(QtWarningMsg, qPrintable(url.toString() + u":13: "_s + warning)); + QTest::ignoreMessage(QtDebugMsg, "objA says 5 + 6 = 11"); + } + + QTest::ignoreMessage(QtWarningMsg, qPrintable(url.toString() + u":22: "_s + warning)); + QTest::ignoreMessage(QtDebugMsg, "objA says hello"); + QTest::ignoreMessage(QtWarningMsg, qPrintable(url.toString() + u":22: "_s + warning)); + QTest::ignoreMessage(QtDebugMsg, "objB says hello"); + + QTest::ignoreMessage(QtDebugMsg, "objC says 7 + 7 = 14"); + QTest::ignoreMessage(QtWarningMsg, qPrintable(url.toString() + u":32: "_s + warning)); + QTest::ignoreMessage(QtDebugMsg, "objB says 7 + 7 = 14"); + + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); +} + +void tst_qqmllanguage::variantObjectList() +{ + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("variantObjectList.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + BirthdayParty *party = o->property("q").value<BirthdayParty *>(); + QCOMPARE(party->guestCount(), 3); + QCOMPARE(party->guest(0)->objectName(), "Leo Hodges"); + QCOMPARE(party->guest(1)->objectName(), "Jack Smith"); + QCOMPARE(party->guest(2)->objectName(), "Anne Brown"); +} + +void tst_qqmllanguage::jitExceptions() +{ + QQmlEngine e; + const QUrl url = testFileUrl("jitExceptions.qml"); + QQmlComponent c(&e, testFileUrl("jitExceptions.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + u":5: ReferenceError: control is not defined"_s)); + + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); +} + +void tst_qqmllanguage::attachedInCtor() +{ + QQmlEngine e; + QQmlComponent c(&e); + c.setData(R"( + import Test + AttachedInCtor {} + )", QUrl()); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + AttachedInCtor *a = qobject_cast<AttachedInCtor *>(o.data()); + QVERIFY(a->attached); + QCOMPARE(a->attached, qmlAttachedPropertiesObject<AttachedInCtor>(a, false)); +} + +void tst_qqmllanguage::byteArrayConversion() +{ + QQmlEngine e; + QQmlComponent c(&e); + c.setData(R"( + import Test + import QtQml + ByteArrayReceiver { + Component.onCompleted: { + byteArrayTest([1, 2, 3]); + byteArrayTest(Array.from('456')); + } + } + )", QUrl()); + + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + ByteArrayReceiver *receiver = qobject_cast<ByteArrayReceiver *>(o.data()); + QVERIFY(receiver); + QCOMPARE(receiver->byteArrays.length(), 2); + QCOMPARE(receiver->byteArrays[0], QByteArray("\1\2\3")); + QCOMPARE(receiver->byteArrays[1], QByteArray("\4\5\6")); +} +void tst_qqmllanguage::propertySignalNames_data() +{ + QTest::addColumn<QString>("propertyName"); + QTest::addColumn<QString>("propertyChangedSignal"); + QTest::addColumn<QString>("propertyChangedHandler"); + QTest::addRow("helloWorld") << u"helloWorld"_s << u"helloWorldChanged"_s + << u"onHelloWorldChanged"_s; + QTest::addRow("$helloWorld") << u"$helloWorld"_s << u"$helloWorldChanged"_s + << u"on$HelloWorldChanged"_s; + QTest::addRow("_helloWorld") << u"_helloWorld"_s << u"_helloWorldChanged"_s + << u"on_HelloWorldChanged"_s; + QTest::addRow("_") << u"_"_s << u"_Changed"_s << u"on_Changed"_s; + QTest::addRow("$") << u"$"_s << u"$Changed"_s << u"on$Changed"_s; + QTest::addRow("ä") << u"ä"_s << u"äChanged"_s << u"onÄChanged"_s; + QTest::addRow("___123a") << u"___123a"_s << u"___123aChanged"_s << u"on___123AChanged"_s; +} +void tst_qqmllanguage::propertySignalNames() +{ + QFETCH(QString, propertyName); + QFETCH(QString, propertyChangedSignal); + QFETCH(QString, propertyChangedHandler); + QQmlEngine e; + QQmlComponent c(&e); + c.setData(uR"( +import QtQuick +Item { + property int %1: 456 + property bool success: false + function f() { %1 = 123; } + function g() { %2(); } + %3: success = true +})"_s.arg(propertyName, propertyChangedSignal, propertyChangedHandler) + .toUtf8(), + QUrl()); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o != nullptr); + const QMetaObject *metaObject = o->metaObject(); + auto signalIndex = + metaObject->indexOfSignal(propertyChangedSignal.append("()").toStdString().c_str()); + QVERIFY(signalIndex > -1); + auto signal = metaObject->method(signalIndex); + QVERIFY(signal.isValid()); + QSignalSpy changeSignal(o.data(), signal); + QMetaObject::invokeMethod(o.data(), "f"); + QCOMPARE(o->property(propertyName.toStdString().c_str()), 123); + QVERIFY(changeSignal.size() == 1); + QCOMPARE(o->property("success"), true); + QMetaObject::invokeMethod(o.data(), "g"); + QVERIFY(changeSignal.size() == 2); +} +void tst_qqmllanguage::signalNames_data() +{ + QTest::addColumn<QString>("signalName"); + QTest::addColumn<QString>("handlerName"); + QTest::addRow("helloWorld") << u"helloWorld"_s << u"onHelloWorld"_s; + QTest::addRow("$helloWorld") << u"$helloWorld"_s << u"on$HelloWorld"_s; + QTest::addRow("_helloWorld") << u"_helloWorld"_s << u"on_HelloWorld"_s; + QTest::addRow("_") << u"_"_s << u"on_"_s; + QTest::addRow("aUmlaut") << u"ä"_s << u"onÄ"_s; + QTest::addRow("___123a") << u"___123a"_s << u"on___123A"_s; +} +void tst_qqmllanguage::signalNames() +{ + QFETCH(QString, signalName); + QFETCH(QString, handlerName); + QQmlEngine e; + QQmlComponent c(&e); + c.setData(uR"( +import QtQuick +Item { + signal %1() + property bool success: false + function f() { %1(); } + %2: success = true +})"_s.arg(signalName, handlerName) + .toUtf8(), + QUrl()); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o != nullptr); + const QMetaObject *metaObject = o->metaObject(); + auto signalIndex = metaObject->indexOfSignal(signalName.append("()").toStdString().c_str()); + QVERIFY(signalIndex > -1); + auto signal = metaObject->method(signalIndex); + QVERIFY(signal.isValid()); + QSignalSpy changeSignal(o.data(), signal); + signal.invoke(o.data()); + QVERIFY(changeSignal.size() == 1); + QCOMPARE(o->property("success"), true); + QMetaObject::invokeMethod(o.data(), "f"); + QVERIFY(changeSignal.size() == 2); +} + +void tst_qqmllanguage::callMethodOfAttachedDerived() +{ + QQmlEngine engine; + QQmlComponent c(&engine); + c.setData(R"( + import QtQml + import Test + + QtObject { + Component.onCompleted: Counter.increase() + property int v: Counter.value + } + )", QUrl()); + + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("v").toInt(), 99); +} + +void tst_qqmllanguage::multiVersionSingletons() +{ + qmlRegisterTypesAndRevisions<BareSingleton>("MultiVersionSingletons", 11); + qmlRegisterTypesAndRevisions<UncreatableSingleton>("MultiVersionSingletons", 11); + QQmlEngine engine; + + for (const char *name : { "BareSingleton", "UncreatableSingleton"}) { + const int id1 = qmlTypeId("MultiVersionSingletons", 1, 0, name); + const int id2 = qmlTypeId("MultiVersionSingletons", 11, 0, name); + QVERIFY(id1 != id2); + const QJSValue value1 = engine.singletonInstance<QJSValue>(id1); + const QJSValue value2 = engine.singletonInstance<QJSValue>(id2); + QVERIFY(value1.strictlyEquals(value2)); + } +} + +void tst_qqmllanguage::typeAnnotationCycle() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("TypeAnnotationCycle1.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QCOMPARE(o->property("b").value<QObject*>(), o.data()); +} + +void tst_qqmllanguage::corpseInQmlList() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("corpseInQmlList.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QScopedPointer<QObject> a(new QObject); + QMetaObject::invokeMethod(o.data(), "setB", Q_ARG(QObject *, a.data())); + + QJSValue b = o->property("b").value<QJSValue>(); + QQmlListProperty<QObject> list + = qjsvalue_cast<QQmlListProperty<QObject>>(b.property(QStringLiteral("b"))); + + QCOMPARE(list.count(&list), 1); + QCOMPARE(list.at(&list, 0), a.data()); + + a.reset(); + + b = o->property("b").value<QJSValue>(); + list = qjsvalue_cast<QQmlListProperty<QObject>>(b.property(QStringLiteral("b"))); + + QCOMPARE(list.count(&list), 1); + QCOMPARE(list.at(&list, 0), nullptr); + + // The list itself is still alive: + + list.append(&list, o.data()); + QCOMPARE(list.count(&list), 2); + QCOMPARE(list.at(&list, 0), nullptr); + QCOMPARE(list.at(&list, 1), o.data()); + + list.replace(&list, 0, o.data()); + QCOMPARE(list.count(&list), 2); + QCOMPARE(list.at(&list, 0), o.data()); + QCOMPARE(list.at(&list, 1), o.data()); + + list.removeLast(&list); + QCOMPARE(list.count(&list), 1); + QCOMPARE(list.at(&list, 0), o.data()); + + list.clear(&list); + QCOMPARE(list.count(&list), 0); +} + +void tst_qqmllanguage::objectInQmlListAndGc() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("objectInList.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + // Process the deletion event + QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); + QCoreApplication::processEvents(); + + QQmlListProperty<QObject> children = o->property("child").value<QQmlListProperty<QObject>>(); + QCOMPARE(children.count(&children), 1); + QObject *child = children.at(&children, 0); + QVERIFY(child); + QCOMPARE(child->objectName(), QLatin1String("child")); +} + +void tst_qqmllanguage::asCastToInlineComponent() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("asCastToInlineComponent.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QCOMPARE(o->objectName(), QLatin1String("value: 20")); +} + +void tst_qqmllanguage::deepAliasOnICOrReadonly() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("deepAliasOnICUser.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("borderColor").toString(), QLatin1String("black")); + QCOMPARE(o->property("borderObjectName").toString(), QLatin1String("theLeaf")); + + const QVariant var = o->property("borderVarvar"); + QCOMPARE(var.metaType(), QMetaType::fromType<QString>()); + QCOMPARE(var.toString(), QLatin1String("mauve")); + + QQmlComponent c2(&engine, testFileUrl("deepAliasOnReadonly.qml")); + QVERIFY(c2.isError()); + QVERIFY(c2.errorString().contains( + QLatin1String( + "Invalid property assignment: \"readonlyRectX\" is a read-only property"))); +} + +void tst_qqmllanguage::optionalChainCallOnNullProperty() +{ + QTest::failOnWarning(QRegularExpression(".*Cannot call method 'destroy' of null.*")); + + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("optionalChainCallOnNullProperty.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); +} + +void tst_qqmllanguage::ambiguousComponents() +{ + auto e1 = std::make_unique<QQmlEngine>(); + e1->addImportPath(dataDirectory()); + bool isInstanceOf = false; + + { + QQmlComponent c(e1.get()); + c.loadUrl(testFileUrl("ambiguousComponents.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + QScopedPointer<QObject> o(c.create()); + QTest::ignoreMessage(QtDebugMsg, "do"); + QMetaObject::invokeMethod(o.data(), "dodo"); + + QMetaObject::invokeMethod(o.data(), "testInstanceOf", Q_RETURN_ARG(bool, isInstanceOf)); + QVERIFY(isInstanceOf); + } + + QQmlEngine e2; + e2.addImportPath(dataDirectory()); + QQmlComponent c2(&e2); + c2.loadUrl(testFileUrl("ambiguousComponents.qml")); + QVERIFY2(c2.isReady(), qPrintable(c2.errorString())); + + QScopedPointer<QObject> o2(c2.create()); + QTest::ignoreMessage(QtDebugMsg, "do"); + QMetaObject::invokeMethod(o2.data(), "dodo"); + + isInstanceOf = false; + QMetaObject::invokeMethod(o2.data(), "testInstanceOf", Q_RETURN_ARG(bool, isInstanceOf)); + QVERIFY(isInstanceOf); + + e1.reset(); + + // We can still invoke the function. This means its CU belongs to e2. + QTest::ignoreMessage(QtDebugMsg, "do"); + QMetaObject::invokeMethod(o2.data(), "dodo"); + + isInstanceOf = false; + QMetaObject::invokeMethod(o2.data(), "testInstanceOf", Q_RETURN_ARG(bool, isInstanceOf)); + QVERIFY(isInstanceOf); +} + +void tst_qqmllanguage::writeNumberToEnumAlias() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("aliasWriter.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("strokeStyle").toInt(), 1); +} + +void tst_qqmllanguage::badInlineComponentAnnotation() +{ + QQmlEngine engine; + const QUrl url = testFileUrl("badICAnnotation.qml"); + QQmlComponent c(&engine, testFileUrl("badICAnnotation.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + QTest::ignoreMessage( + QtCriticalMsg, + qPrintable(url.toString() + ":20: 5 should be coerced to void because the function " + "called is insufficiently annotated. The original " + "value is retained. This will change in a future " + "version of Qt.")); + QTest::ignoreMessage( + QtCriticalMsg, + QRegularExpression(":22: IC\\([^\\)]+\\) should be coerced to void because the " + "function called is insufficiently annotated. The original " + "value is retained. This will change in a future version of " + "Qt\\.")); + + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("a").toInt(), 5); + + QObject *ic = o->property("ic").value<QObject *>(); + QVERIFY(ic); + + QCOMPARE(o->property("b").value<QObject *>(), ic); + QCOMPARE(o->property("c").value<QObject *>(), ic); + QCOMPARE(o->property("d").value<QObject *>(), nullptr); +} + +void tst_qqmllanguage::manuallyCallSignalHandler() +{ + // TODO: This test verifies the absence of regression legacy behavior. See QTBUG-120573 + // Once we can get rid of the legacy behavior, delete this test! + + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("manuallyCallSignalHandler.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + for (int i = 0; i < 10; ++i) { + QTest::ignoreMessage(QtWarningMsg, QRegularExpression( + "Property 'onDestruction' of object QQmlComponentAttached\\(0x[0-9a-f]+\\) is a signal " + "handler\\. You should not call it directly\\. Make it a proper function and call that " + "or emit the signal\\.")); + QTest::ignoreMessage(QtDebugMsg, "evil!"); + QScopedPointer<QObject> o(c.create()); + QTest::ignoreMessage(QtDebugMsg, "evil!"); + } +} + +void tst_qqmllanguage::overrideDefaultProperty() +{ + QQmlEngine e; + const QUrl url = testFileUrl("overrideDefaultProperty.qml"); + + // Should not crash here! + + QQmlComponent c(&e, url); + QVERIFY(c.isError()); + QCOMPARE(c.errorString(), + url.toString() + QLatin1String(":5 Cannot assign object to list property \"data\"\n")); +} + +void tst_qqmllanguage::enumScopes() +{ + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("enumScopes.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("singletonUnscoped"), false); + QCOMPARE(o->property("singletonScoped"), true); + QCOMPARE(o->property("nonSingletonUnscoped"), false); + QCOMPARE(o->property("nonSingletonScoped"), true); + + QCOMPARE(o->property("singletonScopedValue").toInt(), int(EnumProviderSingleton::Expected::Value)); + QCOMPARE(o->property("singletonUnscopedValue").toInt(), int(EnumProviderSingleton::Expected::Value)); +} + +void tst_qqmllanguage::typedObjectList() +{ + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("typedObjectList.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QJSValue b = o->property("b").value<QJSValue>(); + auto list = qjsvalue_cast<QQmlListProperty<QQmlComponent>>(b.property(QStringLiteral("b"))); + + QCOMPARE(list.count(&list), 1); + QVERIFY(list.at(&list, 0) != nullptr); +} + +void tst_qqmllanguage::jsonArrayPropertyBehavesLikeAnArray() { + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("jsonArrayProperty.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("concatedJsonArray"), o->property("concatedJsArray")); + QVERIFY(o->property("entriesMatch").toBool()); + QCOMPARE(o->property("jsonArrayEvery"), o->property("jsArrayEvery")); + QCOMPARE(o->property("jsonArrayFiltered"), o->property("jsArrayFiltered")); + QCOMPARE(o->property("jsonArrayFind"), o->property("jsArrayFind")); + QCOMPARE(o->property("jsonArrayFindIndex"), o->property("jsArrayFindIndex")); + QCOMPARE(o->property("jsonArrayForEach"), o->property("jsArrayForEach")); + QCOMPARE(o->property("jsonArrayIncludes"), o->property("jsArrayIncludes")); + QCOMPARE(o->property("jsonArrayIndexOf"), o->property("jsArrayIndexOf")); + QCOMPARE(o->property("jsonArrayJoin"), o->property("jsArrayJoin")); + QVERIFY(o->property("keysMatch").toBool()); + QCOMPARE(o->property("jsonArrayLastIndexOf"), o->property("jsArrayLastIndexOf")); + QCOMPARE(o->property("jsonArrayMap"), o->property("jsArrayMap")); + QCOMPARE(o->property("jsonArrayReduce"), o->property("jsArrayReduce")); + QCOMPARE(o->property("jsonArrayReduceRight"), o->property("jsArrayReduceRight")); + QCOMPARE(o->property("jsonArraySlice"), o->property("jsArraySlice")); + QCOMPARE(o->property("jsonArraySome"), o->property("jsArraySome")); + QCOMPARE(o->property("stringifiedLocaleJsonArray"), o->property("stringifiedLocaleJsArray")); + QCOMPARE(o->property("stringifiedJsonArray"), o->property("stringifiedJsArray")); + QVERIFY(o->property("valuesMatch").toBool()); + + QVERIFY(o->property("jsonArrayWasCopiedWithin").toBool()); + QVERIFY(o->property("jsonArrayWasFilled").toBool()); + QVERIFY(o->property("jsonArrayWasPopped").toBool()); + QVERIFY(o->property("jsonArrayWasPushed").toBool()); + QVERIFY(o->property("jsonArrayWasReversed").toBool()); + QVERIFY(o->property("jsonArrayWasShifted").toBool()); + QVERIFY(o->property("jsonArrayWasSpliced").toBool()); + QVERIFY(o->property("jsonArrayWasUnshifted").toBool()); + QEXPECT_FAIL( + "", + "The sort method for sequences will not currently work with QJsonArray. See QTBUG-125400.", + Continue + ); + QVERIFY(o->property("jsonArrayWasSorted").toBool()); +} + +void tst_qqmllanguage::invokableCtors() +{ + QQmlEngine e; + + const QUrl url = testFileUrl("invokableCtors.qml"); + + QQmlComponent c(&e, url); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + const QString urlString = url.toString(); + QTest::ignoreMessage(QtWarningMsg, qPrintable( + urlString + ":9: You are calling a Q_INVOKABLE constructor of " + "InvokableSingleton which is a singleton in QML.")); + + // Extended types look like types without any constructors. + // Therefore they aren't even FunctionObjects. + QTest::ignoreMessage(QtWarningMsg, qPrintable( + urlString + ":10: TypeError: Type error")); + + QTest::ignoreMessage(QtWarningMsg, qPrintable( + urlString + ":11: You are calling a Q_INVOKABLE constructor of " + "InvokableUncreatable which is uncreatable in QML.")); + + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QObject *oo = qvariant_cast<QObject *>(o->property("oo")); + QVERIFY(oo); + QObject *pp = qvariant_cast<QObject *>(o->property("pp")); + QVERIFY(pp); + QCOMPARE(pp->parent(), oo); + + InvokableValueType vv = qvariant_cast<InvokableValueType>(o->property("v")); + QCOMPARE(vv.m_s, "green"); + + InvokableSingleton *i = qvariant_cast<InvokableSingleton *>(o->property("i")); + QVERIFY(i); + QCOMPARE(i->m_a, 5); + QCOMPARE(i->parent(), oo); + + QVariant k = o->property("k"); + QCOMPARE(k.metaType(), QMetaType::fromType<InvokableExtended *>()); + QCOMPARE(k.value<InvokableExtended *>(), nullptr); + + InvokableUncreatable *l = qvariant_cast<InvokableUncreatable *>(o->property("l")); + QVERIFY(l); +} + QTEST_MAIN(tst_qqmllanguage) #include "tst_qqmllanguage.moc" |