aboutsummaryrefslogtreecommitdiffstats
path: root/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp')
-rw-r--r--tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp2735
1 files changed, 2572 insertions, 163 deletions
diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
index 78aa38a89c..2f382e8d8e 100644
--- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
+++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
@@ -1,30 +1,6 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 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$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
#include <qtest.h>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlcomponent.h>
@@ -36,6 +12,8 @@
#include <QtCore/qfileinfo.h>
#include <QtCore/qdir.h>
#include <QtCore/qscopeguard.h>
+#include <QtCore/qrandom.h>
+#include <QtGui/qevent.h>
#include <QSignalSpy>
#include <QFont>
#include <QQmlFileSelector>
@@ -49,11 +27,14 @@
#include <private/qqmlscriptstring_p.h>
#include <private/qqmlvmemetaobject_p.h>
#include <private/qqmlcomponent_p.h>
+#include <private/qqmltype_p_p.h>
+#include <private/qv4debugging_p.h>
+#include <private/qqmlcomponentattached_p.h>
+#include <QtQml/private/qqmlexpression_p.h>
#include "testtypes.h"
-#include "testhttpserver.h"
-
-#include "../../shared/util.h"
+#include <QtQuickTestUtils/private/qmlutils_p.h>
+#include <QtQuickTestUtils/private/testhttpserver_p.h>
#include <deque>
@@ -61,6 +42,8 @@
#include <unistd.h>
#endif
+using namespace Qt::StringLiterals;
+
DEFINE_BOOL_CONFIG_OPTION(qmlCheckTypes, QML_CHECK_TYPES)
static inline bool isCaseSensitiveFileSystem(const QString &path) {
@@ -84,6 +67,9 @@ class tst_qqmllanguage : public QQmlDataTest
{
Q_OBJECT
+public:
+ tst_qqmllanguage();
+
private slots:
void initTestCase() override;
void cleanupTestCase();
@@ -102,7 +88,7 @@ private slots:
void assignObjectToVariant();
void assignLiteralSignalProperty();
void assignQmlComponent();
- void assignBasicTypes();
+ void assignValueTypes();
void assignTypeExtremes();
void assignCompositeToType();
void assignLiteralToVar();
@@ -141,7 +127,6 @@ private slots:
void requiredPropertyFromCpp();
void attachedProperties();
void dynamicObjects();
- void customVariantTypes();
void valueTypes();
void cppnamespace();
void aliasProperties();
@@ -158,6 +143,8 @@ private slots:
void scriptStringWithoutSourceCode();
void scriptStringComparison();
void defaultPropertyListOrder();
+ void defaultPropertyWithInitializer_data();
+ void defaultPropertyWithInitializer();
void declaredPropertyValues();
void dontDoubleCallClassBegin();
void reservedWords_data();
@@ -242,6 +229,8 @@ private slots:
void deepProperty();
+ void groupAssignmentFailure();
+
void compositeSingletonProperties();
void compositeSingletonSameEngine();
void compositeSingletonDifferentEngine();
@@ -276,6 +265,7 @@ private slots:
void lazyDeferredSubObject();
void deferredProperties();
void executeDeferredPropertiesOnce();
+ void deferredProperties_extra();
void noChildEvents();
@@ -326,6 +316,8 @@ private slots:
void nonExistingInlineComponent();
void inlineComponentFoundBeforeOtherImports();
void inlineComponentDuplicateNameError();
+ void inlineComponentWithAliasInstantiatedWithNewProperties();
+ void inlineComponentWithImplicitComponent();
void selfReference();
void selfReferencingSingleton();
@@ -337,23 +329,131 @@ private slots:
void arrayToContainer();
void qualifiedScopeInCustomParser();
void accessNullPointerPropertyCache();
+ void bareInlineComponent();
void checkUncreatableNoReason();
void checkURLtoURLObject();
void registerValueTypes();
void extendedNamespace();
+ void extendedNamespaceByObject();
+ void extendedByAttachedType();
void factorySingleton();
void extendedSingleton();
void qtbug_85932();
void qtbug_86482();
void multiExtension();
+ void multiExtensionExtra();
+ void multiExtensionIndirect();
+ void multiExtensionQmlTypes();
+ void extensionSpecial();
+ void extensionRevision();
+ void extendedGroupProperty();
void invalidInlineComponent();
void warnOnInjectedParameters();
+#if QT_CONFIG(wheelevent)
+ void warnOnInjectedParametersFromCppSignal();
+#endif
void qtbug_85615();
+ void hangOnWarning();
+
+ void groupPropertyFromNonExposedBaseClass();
+
+ void listEnumConversion();
+ void deepInlineComponentScriptBinding();
+
+ void propertyObserverOnReadonly();
+ void valueTypeWithEnum();
+ void enumsFromRelatedTypes();
+
+ void propertyAndAliasMustHaveDistinctNames_data();
+ void propertyAndAliasMustHaveDistinctNames();
+
+ void variantListConversion();
+ void thisInArrowFunction();
+
+ void jittedAsCast();
+ void propertyNecromancy();
+ void generalizedGroupedProperty();
+
+ void groupedAttachedProperty_data();
+ void groupedAttachedProperty();
+
+ void ambiguousContainingType();
+ void objectAsBroken();
+ void customValueTypes();
+ void valueTypeList();
+ void componentMix();
+ void uncreatableAttached();
+ void resetGadgetProperty();
+ void leakingAttributesQmlAttached();
+ void leakingAttributesQmlSingleton();
+ void leakingAttributesQmlForeign();
+ void attachedOwnProperties();
+ void bindableOnly();
+ void v4SequenceMethods();
+ void v4SequenceMethodsWithParams_data();
+ void v4SequenceMethodsWithParams();
+ void jsFunctionOverridesImport();
+ void bindingAliasToComponentUrl();
+ void badGroupedProperty();
+ void functionInGroupedProperty();
+ void signalInlineComponentArg();
+ void functionSignatureEnforcement();
+ void importPrecedence();
+ void nullIsNull();
+ void multiRequired();
+ void isNullOrUndefined();
+
+ void objectAndGadgetMethodCallsRejectThisObject();
+ void objectAndGadgetMethodCallsAcceptThisObject();
+ void asValueType();
+
+ 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();
+
private:
QQmlEngine engine;
QStringList defaultImportPathList;
@@ -440,11 +540,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());
@@ -526,6 +626,10 @@ void tst_qqmllanguage::errors_data()
QTest::newRow("nullishCoalescing_RHS_Or") << "nullishCoalescing_RHS_Or.qml" << "nullishCoalescing_RHS_Or.errors.txt" << false;
QTest::newRow("nullishCoalescing_RHS_And") << "nullishCoalescing_RHS_And.qml" << "nullishCoalescing_RHS_And.errors.txt" << false;
+ QTest::newRow("questionDotEOF") << "questionDotEOF.qml" << "questionDotEOF.errors.txt" << false;
+ QTest::newRow("optionalChaining.LHS") << "optionalChaining.LHS.qml" << "optionalChaining.LHS.errors.txt" << false;
+
+
QTest::newRow("invalidGroupedProperty.1") << "invalidGroupedProperty.1.qml" << "invalidGroupedProperty.1.errors.txt" << false;
QTest::newRow("invalidGroupedProperty.2") << "invalidGroupedProperty.2.qml" << "invalidGroupedProperty.2.errors.txt" << false;
QTest::newRow("invalidGroupedProperty.3") << "invalidGroupedProperty.3.qml" << "invalidGroupedProperty.3.errors.txt" << false;
@@ -617,8 +721,6 @@ void tst_qqmllanguage::errors_data()
QTest::newRow("invalidAttachedProperty.9") << "invalidAttachedProperty.9.qml" << "invalidAttachedProperty.9.errors.txt" << false;
QTest::newRow("invalidAttachedProperty.10") << "invalidAttachedProperty.10.qml" << "invalidAttachedProperty.10.errors.txt" << false;
QTest::newRow("invalidAttachedProperty.11") << "invalidAttachedProperty.11.qml" << "invalidAttachedProperty.11.errors.txt" << false;
- QTest::newRow("invalidAttachedProperty.12") << "invalidAttachedProperty.12.qml" << "invalidAttachedProperty.12.errors.txt" << false;
- QTest::newRow("invalidAttachedProperty.13") << "invalidAttachedProperty.13.qml" << "invalidAttachedProperty.13.errors.txt" << false;
QTest::newRow("assignValueToSignal") << "assignValueToSignal.qml" << "assignValueToSignal.errors.txt" << false;
QTest::newRow("emptySignal") << "emptySignal.qml" << "emptySignal.errors.txt" << false;
@@ -732,7 +834,7 @@ void tst_qqmllanguage::simpleContainer()
VERIFY_ERRORS(0);
QScopedPointer<MyContainer> container(qobject_cast<MyContainer*>(component.create()));
QVERIFY(container != nullptr);
- QCOMPARE(container->getChildren()->count(),2);
+ QCOMPARE(container->getChildren()->size(),2);
}
void tst_qqmllanguage::interfaceProperty()
@@ -751,7 +853,7 @@ void tst_qqmllanguage::interfaceQList()
VERIFY_ERRORS(0);
QScopedPointer<MyContainer> container(qobject_cast<MyContainer*>(component.create()));
QVERIFY(container != nullptr);
- QCOMPARE(container->getQListInterfaces()->count(), 2);
+ QCOMPARE(container->getQListInterfaces()->size(), 2);
for(int ii = 0; ii < 2; ++ii)
QCOMPARE(container->getQListInterfaces()->at(ii)->id, 913);
}
@@ -792,16 +894,16 @@ void tst_qqmllanguage::assignQmlComponent()
VERIFY_ERRORS(0);
QScopedPointer<MyContainer> object(qobject_cast<MyContainer *>(component.create()));
QVERIFY(object != nullptr);
- QCOMPARE(object->getChildren()->count(), 1);
+ QCOMPARE(object->getChildren()->size(), 1);
QObject *child = object->getChildren()->at(0);
QCOMPARE(child->property("x"), QVariant(10));
QCOMPARE(child->property("y"), QVariant(11));
}
-// Test literal assignment to all the basic types
-void tst_qqmllanguage::assignBasicTypes()
+// Test literal assignment to all the value types
+void tst_qqmllanguage::assignValueTypes()
{
- QQmlComponent component(&engine, testFileUrl("assignBasicTypes.qml"));
+ QQmlComponent component(&engine, testFileUrl("assignValueTypes.qml"));
VERIFY_ERRORS(0);
QScopedPointer<MyTypeObject> object(qobject_cast<MyTypeObject *>(component.create()));
QVERIFY(object != nullptr);
@@ -903,7 +1005,7 @@ void tst_qqmllanguage::assignLiteralToVar()
QCOMPARE(object->property("test9"), QVariant(QString(QLatin1String("#FF008800"))));
QCOMPARE(object->property("test10"), QVariant(bool(true)));
QCOMPARE(object->property("test11"), QVariant(bool(false)));
- QCOMPARE(object->property("test12"), QVariant(QColor::fromRgbF(0.2, 0.3, 0.4, 0.5)));
+ QCOMPARE(object->property("test12"), QVariant(QColor::fromRgbF(0.2f, 0.3f, 0.4f, 0.5f)));
QCOMPARE(object->property("test13"), QVariant(QRectF(10, 10, 10, 10)));
QCOMPARE(object->property("test14"), QVariant(QPointF(10, 10)));
QCOMPARE(object->property("test15"), QVariant(QSizeF(10, 10)));
@@ -1058,7 +1160,7 @@ void tst_qqmllanguage::bindJSValueToVar()
QCOMPARE(object->property("test9"), QVariant(QString(QLatin1String("#FF008800"))));
QCOMPARE(object->property("test10"), QVariant(bool(true)));
QCOMPARE(object->property("test11"), QVariant(bool(false)));
- QCOMPARE(object->property("test12"), QVariant(QColor::fromRgbF(0.2, 0.3, 0.4, 0.5)));
+ QCOMPARE(object->property("test12"), QVariant(QColor::fromRgbF(0.2f, 0.3f, 0.4f, 0.5f)));
QCOMPARE(object->property("test13"), QVariant(QRectF(10, 10, 10, 10)));
QCOMPARE(object->property("test14"), QVariant(QPointF(10, 10)));
QCOMPARE(object->property("test15"), QVariant(QSizeF(10, 10)));
@@ -1107,7 +1209,7 @@ void tst_qqmllanguage::bindJSValueToVariant()
QCOMPARE(object->property("test9"), QVariant(QString(QLatin1String("#FF008800"))));
QCOMPARE(object->property("test10"), QVariant(bool(true)));
QCOMPARE(object->property("test11"), QVariant(bool(false)));
- QCOMPARE(object->property("test12"), QVariant(QColor::fromRgbF(0.2, 0.3, 0.4, 0.5)));
+ QCOMPARE(object->property("test12"), QVariant(QColor::fromRgbF(0.2f, 0.3f, 0.4f, 0.5f)));
QCOMPARE(object->property("test13"), QVariant(QRectF(10, 10, 10, 10)));
QCOMPARE(object->property("test14"), QVariant(QPointF(10, 10)));
QCOMPARE(object->property("test15"), QVariant(QSizeF(10, 10)));
@@ -1131,7 +1233,7 @@ void tst_qqmllanguage::bindJSValueToType()
QCOMPARE(object->doubleProperty(), double(1.7));
QCOMPARE(object->stringProperty(), QString(QLatin1String("Hello world!")));
QCOMPARE(object->boolProperty(), true);
- QCOMPARE(object->colorProperty(), QColor::fromRgbF(0.2, 0.3, 0.4, 0.5));
+ QCOMPARE(object->colorProperty(), QColor::fromRgbF(0.2f, 0.3f, 0.4f, 0.5f));
QCOMPARE(object->rectFProperty(), QRectF(10, 10, 10, 10));
QCOMPARE(object->pointFProperty(), QPointF(10, 10));
QCOMPARE(object->sizeFProperty(), QSizeF(10, 10));
@@ -1318,20 +1420,29 @@ void tst_qqmllanguage::rootAsQmlComponent()
QScopedPointer<MyContainer> object(qobject_cast<MyContainer *>(component.create()));
QVERIFY(object != nullptr);
QCOMPARE(object->property("x"), QVariant(11));
- QCOMPARE(object->getChildren()->count(), 2);
+ QCOMPARE(object->getChildren()->size(), 2);
}
void tst_qqmllanguage::rootItemIsComponent()
{
+ QTest::ignoreMessage(
+ QtWarningMsg,
+ QRegularExpression(
+ ".*/rootItemIsComponent\\.qml:3:1: Using a Component as the root of "
+ "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 "
+ "QML document is deprecated: types defined in qml documents are automatically "
+ "wrapped into Components when needed\\."));
+ QTest::ignoreMessage(
+ QtWarningMsg,
+ QRegularExpression(".*/rootItemIsComponent\\.qml:7:36: Using a Component as the root "
+ "of an inline component is deprecated: inline components are "
+ "automatically wrapped into Components when needed\\."));
QQmlComponent component(&engine, testFileUrl("rootItemIsComponent.qml"));
- VERIFY_ERRORS(0);
- QScopedPointer<QObject> root(component.create());
- QVERIFY(qobject_cast<QQmlComponent*>(root.data()));
- QScopedPointer<QObject> other(qobject_cast<QQmlComponent*>(root.data())->create());
- QVERIFY(!other.isNull());
- QQmlContext *context = qmlContext(other.data());
- QVERIFY(context);
- QCOMPARE(context->nameForObject(other.data()), QStringLiteral("blah"));
}
// Tests that components can be specified inline
@@ -1341,7 +1452,7 @@ void tst_qqmllanguage::inlineQmlComponents()
VERIFY_ERRORS(0);
QScopedPointer<MyContainer> object(qobject_cast<MyContainer *>(component.create()));
QVERIFY(object != nullptr);
- QCOMPARE(object->getChildren()->count(), 1);
+ QCOMPARE(object->getChildren()->size(), 1);
QQmlComponent *comp = qobject_cast<QQmlComponent *>(object->getChildren()->at(0));
QVERIFY(comp != nullptr);
QScopedPointer<MyQmlObject> compObject(qobject_cast<MyQmlObject *>(comp->create()));
@@ -1357,7 +1468,7 @@ void tst_qqmllanguage::idProperty()
VERIFY_ERRORS(0);
QScopedPointer<MyContainer> object(qobject_cast<MyContainer *>(component.create()));
QVERIFY(object != nullptr);
- QCOMPARE(object->getChildren()->count(), 2);
+ QCOMPARE(object->getChildren()->size(), 2);
MyTypeObject *child =
qobject_cast<MyTypeObject *>(object->getChildren()->at(0));
QVERIFY(child != nullptr);
@@ -1376,7 +1487,8 @@ void tst_qqmllanguage::idProperty()
QVERIFY(!root.isNull());
QQmlContext *ctx = qmlContext(root.data());
QVERIFY(ctx);
- QCOMPARE(ctx->nameForObject(root.data()), QString("root"));
+ QCOMPARE(ctx->nameForObject(root.data()), QStringLiteral("root"));
+ QCOMPARE(ctx->objectForName(QStringLiteral("root")), root.data());
}
}
@@ -1643,13 +1755,13 @@ 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);
}
- QCOMPARE(valueSources.count(), 1);
+ QCOMPARE(valueSources.size(), 1);
MyPropertyValueSource *valueSource =
qobject_cast<MyPropertyValueSource *>(valueSources.at(0));
QVERIFY(valueSource != nullptr);
@@ -1664,13 +1776,13 @@ 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);
}
- QCOMPARE(valueSources.count(), 1);
+ QCOMPARE(valueSources.size(), 1);
MyPropertyValueSource *valueSource =
qobject_cast<MyPropertyValueSource *>(valueSources.at(0));
QVERIFY(valueSource != nullptr);
@@ -1809,6 +1921,14 @@ void tst_qqmllanguage::attachedProperties()
QVERIFY(attached != nullptr);
QCOMPARE(attached->property("value"), QVariant(10));
QCOMPARE(attached->property("value2"), QVariant(13));
+
+ {
+ QQmlComponent component(&engine, testFileUrl("attachedPropertyDerived.qml"));
+ VERIFY_ERRORS(0);
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(object != nullptr);
+ QCOMPARE(MyQmlObjectWithAttachedCounter::attachedCount, 1);
+ }
}
// Tests non-static object properties
@@ -1820,16 +1940,6 @@ void tst_qqmllanguage::dynamicObjects()
QVERIFY(object != nullptr);
}
-// Tests the registration of custom variant string converters
-void tst_qqmllanguage::customVariantTypes()
-{
- QQmlComponent component(&engine, testFileUrl("customVariantTypes.qml"));
- VERIFY_ERRORS(0);
- QScopedPointer<MyQmlObject> object(qobject_cast<MyQmlObject*>(component.create()));
- QVERIFY(object != nullptr);
- QCOMPARE(object->customType().a, 10);
-}
-
void tst_qqmllanguage::valueTypes()
{
QQmlComponent component(&engine, testFileUrl("valueTypes.qml"));
@@ -1977,7 +2087,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 *>());
@@ -2012,7 +2122,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;
@@ -2134,6 +2244,22 @@ void tst_qqmllanguage::aliasProperties()
QCOMPARE(subItem->property("y").toInt(), 1);
}
+ // Nested property bindings on group properties that are actually aliases (QTBUG-94983)
+ {
+ QQmlComponent component(&engine, testFileUrl("alias.15a.qml"));
+ VERIFY_ERRORS(0);
+
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+
+ QPointer<QObject> subItem = qvariant_cast<QObject*>(object->property("symbol"));
+ QVERIFY(!subItem.isNull());
+
+ QPointer<QObject> subSubItem = qvariant_cast<QObject*>(subItem->property("layer"));
+
+ QCOMPARE(subSubItem->property("enabled").value<bool>(), true);
+ }
+
// Alias to sub-object with binding (QTBUG-57041)
{
// This is shold *not* crash.
@@ -2211,6 +2337,16 @@ void tst_qqmllanguage::aliasProperties()
QQmlComponent component(&engine, testFileUrl("alias.18.qml"));
VERIFY_ERRORS("alias.18.errors.txt");
}
+
+ // Binding on deep alias
+ {
+ QQmlComponent component(&engine, testFileUrl("alias.19.qml"));
+ VERIFY_ERRORS(0);
+
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+ QCOMPARE(object->property("height").toInt(), 960);
+ }
}
// QTBUG-13374 Test that alias properties and signals can coexist
@@ -2493,8 +2629,9 @@ void tst_qqmllanguage::scriptStringWithoutSourceCode()
QQmlEnginePrivate *eng = QQmlEnginePrivate::get(&engine);
QQmlRefPointer<QQmlTypeData> td = eng->typeLoader.getType(url);
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));
@@ -2632,7 +2769,7 @@ void tst_qqmllanguage::defaultPropertyListOrder()
QScopedPointer<MyContainer> container(qobject_cast<MyContainer *>(component.create()));
QVERIFY(container != nullptr);
- QCOMPARE(container->getChildren()->count(), 6);
+ QCOMPARE(container->getChildren()->size(), 6);
QCOMPARE(container->getChildren()->at(0)->property("index"), QVariant(0));
QCOMPARE(container->getChildren()->at(1)->property("index"), QVariant(1));
QCOMPARE(container->getChildren()->at(2)->property("index"), QVariant(2));
@@ -2641,6 +2778,32 @@ void tst_qqmllanguage::defaultPropertyListOrder()
QCOMPARE(container->getChildren()->at(5)->property("index"), QVariant(5));
}
+void tst_qqmllanguage::defaultPropertyWithInitializer_data()
+{
+ QTest::addColumn<QUrl>("file");
+ QTest::addColumn<QString>("objectName");
+
+ QTest::newRow("base") << testFileUrl("DefaultPropertyWithInitializer.qml") << u"default"_s;
+ QTest::newRow("user") << testFileUrl("DefaultPropertyWithInitializerUser.qml") << u"changed"_s;
+ QTest::newRow("list base") << testFileUrl("DefaultPropertyWithListInitializer.qml") << u"1"_s;
+ QTest::newRow("list user") << testFileUrl("DefaultPropertyWithListInitializerUser.qml") << u"2"_s;
+}
+
+void tst_qqmllanguage::defaultPropertyWithInitializer()
+{
+ QFETCH(QUrl, file);
+ QFETCH(QString, objectName);
+
+ QQmlComponent component(&engine, file);
+ VERIFY_ERRORS(0);
+
+ QScopedPointer<QObject> root(component.create());
+ QVERIFY(root);
+ auto entry = root->property("entry").value<QObject *>();
+ QVERIFY(entry);
+ QCOMPARE(entry->objectName(), objectName);
+}
+
void tst_qqmllanguage::declaredPropertyValues()
{
QQmlComponent component(&engine, testFileUrl("declaredPropertyValues.qml"));
@@ -2748,12 +2911,13 @@ 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());
}
- QCOMPARE(actualerror.left(partialMatch ? expectederror.length(): -1),expectederror);
+ QCOMPARE(actualerror.left(partialMatch ? expectederror.size(): -1),expectederror);
} else {
VERIFY_ERRORS(0);
QScopedPointer<QObject> object(component.create());
@@ -3203,7 +3367,7 @@ void tst_qqmllanguage::importsPath()
ThreadedTestHTTPServer server(dataDirectory());
- for (int i = 0; i < importPath.count(); ++i)
+ for (int i = 0; i < importPath.size(); ++i)
importPath[i].replace(QStringLiteral("{{ServerBaseUrl}}"), server.baseUrl().toString());
engine.setImportPathList(QStringList(defaultImportPathList) << importPath);
@@ -3331,7 +3495,7 @@ void tst_qqmllanguage::importIncorrectCase()
QQmlComponent component(&engine, testFileUrl("ImportIncorrectCase.qml"));
QList<QQmlError> errors = component.errors();
- QCOMPARE(errors.count(), 1);
+ QCOMPARE(errors.size(), 1);
const QString expectedError = isCaseSensitiveFileSystem(dataDirectory()) ?
QStringLiteral("No such file or directory") :
@@ -3415,11 +3579,11 @@ void tst_qqmllanguage::importJs()
{
DETERMINE_ERRORS(errorFile,expected,actual);
- QCOMPARE(expected.size(), actual.size());
+ QCOMPARE(actual.size(), expected.size());
for (int i = 0; i < expected.size(); ++i)
{
- const int compareLen = qMin(expected.at(i).length(), actual.at(i).length());
- QCOMPARE(expected.at(i).left(compareLen), actual.at(i).left(compareLen));
+ const int compareLen = qMin(expected.at(i).size(), actual.at(i).size());
+ QCOMPARE(actual.at(i).left(compareLen), expected.at(i).left(compareLen));
}
}
@@ -3467,7 +3631,7 @@ void tst_qqmllanguage::explicitSelfImport()
engine.setImportPathList(QStringList(defaultImportPathList) << testFile("lib"));
QQmlComponent component(&engine, testFileUrl("mixedModuleWithSelfImport.qml"));
- QVERIFY(component.errors().count() == 0);
+ QVERIFY(component.errors().size() == 0);
engine.setImportPathList(defaultImportPathList);
}
@@ -3638,7 +3802,7 @@ void tst_qqmllanguage::subclassedUncreateableRevision()
c.setData(qml.toUtf8(), QUrl::fromLocalFile(QDir::currentPath()));
QScopedPointer<QObject> obj(c.create());
QCOMPARE(obj.data(), static_cast<QObject*>(nullptr));
- QCOMPARE(c.errors().count(), 1);
+ QCOMPARE(c.errors().size(), 1);
QCOMPARE(c.errors().first().description(), QString("Cannot create MyUncreateableBaseClass"));
}
@@ -3693,7 +3857,7 @@ void tst_qqmllanguage::subclassedExtendedUncreateableRevision()
c.setData(qml.toUtf8(), QUrl::fromLocalFile(QDir::currentPath()));
QScopedPointer<QObject> obj(c.create());
QCOMPARE(obj.data(), static_cast<QObject*>(nullptr));
- QCOMPARE(c.errors().count(), 1);
+ QCOMPARE(c.errors().size(), 1);
QCOMPARE(c.errors().first().description(), QString("Cannot create MyExtendedUncreateableBaseClass"));
}
@@ -3723,6 +3887,11 @@ void tst_qqmllanguage::uncreatableTypesAsProperties()
QVERIFY(!object.isNull());
}
+tst_qqmllanguage::tst_qqmllanguage()
+ : QQmlDataTest(QT_QMLTEST_DATADIR)
+{
+}
+
void tst_qqmllanguage::initTestCase()
{
QQmlDataTest::initTestCase();
@@ -3734,8 +3903,6 @@ void tst_qqmllanguage::initTestCase()
defaultImportPathList = engine.importPathList();
- QQmlMetaType::registerCustomStringConverter(qMetaTypeId<MyCustomVariantType>(), myCustomVariantTypeConverter);
-
registerTypes();
// Registered here because it uses testFileUrl
qmlRegisterType(testFileUrl("CompositeType.qml"), "Test", 1, 0, "RegisteredCompositeType");
@@ -3743,6 +3910,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");
@@ -3771,6 +3939,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()
@@ -3918,6 +4087,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
@@ -4069,8 +4249,8 @@ void tst_qqmllanguage::globalEnums()
QVERIFY(enum2Class->getValueE() == MyEnum2Class::E_14);
QVERIFY(enum2Class->getValueE2() == MyEnum2Class::E_76);
- QVERIFY(signalA.count() == 1);
- QVERIFY(signalB.count() == 1);
+ QVERIFY(signalA.size() == 1);
+ QVERIFY(signalB.size() == 1);
QVERIFY(enum2Class->property("aValue") == MyEnum1Class::A_11);
QVERIFY(enum2Class->property("bValue") == 37);
@@ -4094,7 +4274,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()
@@ -4111,7 +4292,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()
@@ -4243,6 +4425,7 @@ void tst_qqmllanguage::qmlEnums()
QCOMPARE(o->property("otherEnumValue3").toInt(), 24);
QCOMPARE(o->property("otherEnumValue4").toInt(), 25);
QCOMPARE(o->property("otherEnumValue5").toInt(), 1);
+ QCOMPARE(o->property("otherEnumValue6").toInt(), -42);
}
{
@@ -4350,6 +4533,17 @@ void tst_qqmllanguage::deepProperty()
QCOMPARE(font.family(), QStringLiteral("test"));
}
+void tst_qqmllanguage::groupAssignmentFailure()
+{
+ auto ep = std::make_unique<QQmlEngine>();
+ QTest::failOnWarning("QQmlComponent: Component destroyed while completion pending");
+ QQmlComponent component(ep.get(), testFileUrl("groupFailure.qml"));
+ QScopedPointer<QObject> o(component.create());
+ QVERIFY(!o);
+ ep.reset();
+ // ~QQmlComponent should not crash here
+}
+
// Tests that the implicit import has lowest precedence, in the case where
// there are conflicting types and types only found in the local import.
// Tests that just check one (or the root) type are in ::importsOrder
@@ -4808,9 +5002,9 @@ void tst_qqmllanguage::preservePropertyCacheOnGroupObjects()
QQmlData *ddata = QQmlData::get(subObject);
QVERIFY(ddata);
- QQmlPropertyCache *subCache = ddata->propertyCache;
+ const QQmlPropertyCache *subCache = ddata->propertyCache.data();
QVERIFY(subCache);
- QQmlPropertyData *pd = subCache->property(QStringLiteral("newProperty"), /*object*/nullptr, /*context*/nullptr);
+ const QQmlPropertyData *pd = subCache->property(QStringLiteral("newProperty"), /*object*/nullptr, /*context*/nullptr);
QVERIFY(pd);
QCOMPARE(pd->propType(), QMetaType::fromType<int>());
}
@@ -4825,7 +5019,7 @@ void tst_qqmllanguage::propertyCacheInSync()
QVERIFY(anchors);
QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(anchors);
QVERIFY(vmemo);
- QQmlPropertyCache *vmemoCache = vmemo->propertyCache();
+ QQmlPropertyCache::ConstPtr vmemoCache = vmemo->propertyCache();
QVERIFY(vmemoCache);
QQmlData *ddata = QQmlData::get(anchors);
QVERIFY(ddata);
@@ -4898,13 +5092,13 @@ void tst_qqmllanguage::deferredProperties()
QQmlData *qmlData = QQmlData::get(object.data());
QVERIFY(qmlData);
- QCOMPARE(qmlData->deferredData.count(), 2); // MyDeferredListProperty.qml + deferredListProperty.qml
- QCOMPARE(qmlData->deferredData.first()->bindings.count(), 3); // "innerobj", "innerlist1", "innerlist2"
- QCOMPARE(qmlData->deferredData.last()->bindings.count(), 3); // "outerobj", "outerlist1", "outerlist2"
+ QCOMPARE(qmlData->deferredData.size(), 2); // MyDeferredListProperty.qml + deferredListProperty.qml
+ QCOMPARE(qmlData->deferredData.first()->bindings.size(), 3); // "innerobj", "innerlist1", "innerlist2"
+ QCOMPARE(qmlData->deferredData.last()->bindings.size(), 3); // "outerobj", "outerlist1", "outerlist2"
qmlExecuteDeferred(object.data());
- QCOMPARE(qmlData->deferredData.count(), 0);
+ QCOMPARE(qmlData->deferredData.size(), 0);
innerObj = object->findChild<QObject *>(QStringLiteral("innerobj")); // MyDeferredListProperty.qml
QVERIFY(innerObj);
@@ -4947,24 +5141,23 @@ static void beginDeferredOnce(QQmlEnginePrivate *enginePriv,
if (range.first == deferData->bindings.end())
continue;
- QQmlComponentPrivate::ConstructionState *state = new QQmlComponentPrivate::ConstructionState;
- state->completePending = true;
+ QQmlComponentPrivate::ConstructionState state;
+ state.setCompletePending(true);
- state->creator.reset(new QQmlObjectCreator(
- deferData->context->parent(), deferData->compilationUnit,
- QQmlRefPointer<QQmlContextData>()));
+ state.initCreator(deferData->context->parent(), deferData->compilationUnit,
+ QQmlRefPointer<QQmlContextData>());
enginePriv->inProgressCreations++;
std::deque<const QV4::CompiledData::Binding *> reversedBindings;
std::copy(range.first, range.second, std::front_inserter(reversedBindings));
- state->creator->beginPopulateDeferred(deferData->context);
+ state.creator()->beginPopulateDeferred(deferData->context);
for (const QV4::CompiledData::Binding *binding: reversedBindings)
- state->creator->populateDeferredBinding(property, deferData->deferredIdx, binding);
- state->creator->finalizePopulateDeferred();
- state->errors << state->creator->errors;
+ state.creator()->populateDeferredBinding(property, deferData->deferredIdx, binding);
+ state.creator()->finalizePopulateDeferred();
+ state.appendErrors(state.creator()->errors);
- deferredState->constructionStates += state;
+ deferredState->push_back(std::move(state));
// Cleanup any remaining deferred bindings for this property, also in inner contexts,
// to avoid executing them later and overriding the property that was just populated.
@@ -5015,22 +5208,22 @@ void tst_qqmllanguage::executeDeferredPropertiesOnce()
QQmlData *qmlData = QQmlData::get(object.data());
QVERIFY(qmlData);
- QCOMPARE(qmlData->deferredData.count(), 2); // MyDeferredListProperty.qml + deferredListProperty.qml
- QCOMPARE(qmlData->deferredData.first()->bindings.count(), 3); // "innerobj", "innerlist1", "innerlist2"
- QCOMPARE(qmlData->deferredData.last()->bindings.count(), 3); // "outerobj", "outerlist1", "outerlist2"
+ QCOMPARE(qmlData->deferredData.size(), 2); // MyDeferredListProperty.qml + deferredListProperty.qml
+ QCOMPARE(qmlData->deferredData.first()->bindings.size(), 3); // "innerobj", "innerlist1", "innerlist2"
+ QCOMPARE(qmlData->deferredData.last()->bindings.size(), 3); // "outerobj", "outerlist1", "outerlist2"
// first execution creates the outer object
testExecuteDeferredOnce(QQmlProperty(object.data(), "groupProperty"));
- QCOMPARE(qmlData->deferredData.count(), 2); // MyDeferredListProperty.qml + deferredListProperty.qml
- QCOMPARE(qmlData->deferredData.first()->bindings.count(), 2); // "innerlist1", "innerlist2"
- QCOMPARE(qmlData->deferredData.last()->bindings.count(), 2); // "outerlist1", "outerlist2"
+ QCOMPARE(qmlData->deferredData.size(), 2); // MyDeferredListProperty.qml + deferredListProperty.qml
+ QCOMPARE(qmlData->deferredData.first()->bindings.size(), 2); // "innerlist1", "innerlist2"
+ QCOMPARE(qmlData->deferredData.last()->bindings.size(), 2); // "outerlist1", "outerlist2"
QObjectList innerObjsAfterFirstExecute = object->findChildren<QObject *>(QStringLiteral("innerobj")); // MyDeferredListProperty.qml
QVERIFY(innerObjsAfterFirstExecute.isEmpty());
QObjectList outerObjsAfterFirstExecute = object->findChildren<QObject *>(QStringLiteral("outerobj")); // deferredListProperty.qml
- QCOMPARE(outerObjsAfterFirstExecute.count(), 1);
+ QCOMPARE(outerObjsAfterFirstExecute.size(), 1);
QCOMPARE(outerObjsAfterFirstExecute.first()->property("wasCompleted"), QVariant(true));
groupProperty = object->property("groupProperty").value<QObject *>();
@@ -5042,9 +5235,9 @@ void tst_qqmllanguage::executeDeferredPropertiesOnce()
// re-execution does nothing (to avoid overriding the property)
testExecuteDeferredOnce(QQmlProperty(object.data(), "groupProperty"));
- QCOMPARE(qmlData->deferredData.count(), 2); // MyDeferredListProperty.qml + deferredListProperty.qml
- QCOMPARE(qmlData->deferredData.first()->bindings.count(), 2); // "innerlist1", "innerlist2"
- QCOMPARE(qmlData->deferredData.last()->bindings.count(), 2); // "outerlist1", "outerlist2"
+ QCOMPARE(qmlData->deferredData.size(), 2); // MyDeferredListProperty.qml + deferredListProperty.qml
+ QCOMPARE(qmlData->deferredData.first()->bindings.size(), 2); // "innerlist1", "innerlist2"
+ QCOMPARE(qmlData->deferredData.last()->bindings.size(), 2); // "outerlist1", "outerlist2"
QObjectList innerObjsAfterSecondExecute = object->findChildren<QObject *>(QStringLiteral("innerobj")); // MyDeferredListProperty.qml
QVERIFY(innerObjsAfterSecondExecute.isEmpty());
@@ -5061,7 +5254,7 @@ void tst_qqmllanguage::executeDeferredPropertiesOnce()
// execution of a list property should execute all outer list bindings
testExecuteDeferredOnce(QQmlProperty(object.data(), "listProperty"));
- QCOMPARE(qmlData->deferredData.count(), 0);
+ QCOMPARE(qmlData->deferredData.size(), 0);
listProperty = object->property("listProperty").value<QQmlListProperty<QObject>>();
QCOMPARE(listProperty.count(&listProperty), 2);
@@ -5072,6 +5265,77 @@ void tst_qqmllanguage::executeDeferredPropertiesOnce()
QCOMPARE(listProperty.at(&listProperty, 1)->property("wasCompleted"), QVariant(true));
}
+class GroupType : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(int foo READ getFoo WRITE setFoo BINDABLE bindableFoo)
+ Q_PROPERTY(int bar READ getBar WRITE setBar BINDABLE bindableBar)
+ Q_CLASSINFO("DeferredPropertyNames", "bar")
+
+ QProperty<int> m_foo { 0 };
+ QProperty<int> m_bar { 0 };
+
+public:
+ int getFoo() const { return m_foo; }
+ void setFoo(int v) { m_foo = v; }
+ QBindable<int> bindableFoo() const { return QBindable<int>(&m_foo); }
+
+ int getBar() const { return m_bar; }
+ void setBar(int v) { m_bar = v; }
+ QBindable<int> bindableBar() const { return QBindable<int>(&m_bar); }
+};
+
+class ExtraDeferredProperties : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(GroupType *group READ getGroup)
+ Q_CLASSINFO("DeferredPropertyNames", "group,MyQmlObject")
+
+ GroupType m_group;
+
+public:
+ ExtraDeferredProperties(QObject *parent = nullptr) : QObject(parent) { }
+
+ GroupType *getGroup() { return &m_group; }
+};
+
+void tst_qqmllanguage::deferredProperties_extra()
+{
+ // Note: because ExtraDeferredProperties defers only a `group` property, the
+ // deferral does not actually work.
+ qmlRegisterType<GroupType>("deferred.properties.extra", 1, 0, "GroupType");
+ qmlRegisterType<ExtraDeferredProperties>("deferred.properties.extra", 1, 0,
+ "ExtraDeferredProperties");
+ QQmlComponent component(&engine);
+ component.setData(R"(
+ import QtQuick
+ import Test 1.0
+ import deferred.properties.extra 1.0
+
+ ExtraDeferredProperties {
+ group.foo: 4
+ group.bar: 4
+ MyQmlObject.value: 1
+ }
+ )", QUrl());
+
+ QVERIFY2(component.isReady(), qPrintable(component.errorString()));
+ QScopedPointer<ExtraDeferredProperties> object(
+ qobject_cast<ExtraDeferredProperties *>(component.create()));
+ QVERIFY(object);
+
+ QCOMPARE(object->getGroup()->getFoo(), 4); // not deferred (as group itself is not deferred)
+ QCOMPARE(object->getGroup()->getBar(), 0); // deferred, as per group's own deferred names
+ // but attached property is deferred:
+ QVERIFY(!qmlAttachedPropertiesObject<MyQmlObject>(object.get(), false));
+
+ qmlExecuteDeferred(object.get());
+
+ auto attached = qmlAttachedPropertiesObject<MyQmlObject>(object.get(), false);
+ QVERIFY(attached);
+ QCOMPARE(attached->property("value").toInt(), 1);
+}
+
void tst_qqmllanguage::noChildEvents()
{
QQmlComponent component(&engine);
@@ -5184,7 +5448,7 @@ void tst_qqmllanguage::instanceof_data()
// operands are indeed an instanceof each other, or a string for the
// expected error message.
- // assert that basic types don't convert to QObject
+ // assert that value types don't convert to QObject
QTest::newRow("1 instanceof QtObject")
<< testFileUrl("instanceof_qtqml.qml")
<< QVariant(false);
@@ -5497,13 +5761,31 @@ void tst_qqmllanguage::extendedForeignTypes()
QScopedPointer<QObject> o(component.create());
QVERIFY(!o.isNull());
+ QObject *extended = o->property("extended").value<QObject *>();
+ QVERIFY(extended);
+ QSignalSpy extensionChangedSpy(extended, SIGNAL(extensionChanged()));
+ QSignalSpy extensionChangedWithValueSpy(extended, SIGNAL(extensionChangedWithValue(int)));
+ QVERIFY(extensionChangedWithValueSpy.isValid());
+
QCOMPARE(o->property("extendedBase").toInt(), 43);
QCOMPARE(o->property("extendedExtension").toInt(), 42);
QCOMPARE(o->property("foreignExtendedExtension").toInt(), 42);
+
+ QCOMPARE(extensionChangedSpy.size(), 0);
+ extended->setProperty("extension", 44);
+ QCOMPARE(extensionChangedSpy.size(), 1);
+ QCOMPARE(extensionChangedWithValueSpy.size(), 1);
+ QCOMPARE(o->property("extendedChangeCount").toInt(), 1);
+ QCOMPARE(o->property("extendedExtension").toInt(), 44);
+
QCOMPARE(o->property("foreignObjectName").toString(), QLatin1String("foreign"));
QCOMPARE(o->property("foreignExtendedObjectName").toString(), QLatin1String("foreignExtended"));
QCOMPARE(o->property("extendedInvokable").toInt(), 123);
QCOMPARE(o->property("extendedSlot").toInt(), 456);
+
+ QObject *extension = qmlExtendedObject(extended);
+ QVERIFY(extension != nullptr);
+ QVERIFY(qobject_cast<Extension *>(extension) != nullptr);
}
void tst_qqmllanguage::foreignTypeSingletons() {
@@ -5530,7 +5812,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('*'));
@@ -5539,7 +5821,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;
@@ -5553,7 +5835,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()
@@ -5590,10 +5872,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");
}
@@ -5622,7 +5904,7 @@ void tst_qqmllanguage::overrideSingleton()
QTest::ignoreMessage(
QtWarningMsg,
- "singleton.qml:3: TypeError: Cannot read property 'objectName' of undefined");
+ "singleton.qml:3:12: TypeError: Cannot read property 'objectName' of undefined");
check("", "UncreatableSingleton");
qmlRegisterSingletonInstance("Test", 1, 0, "UncreatableSingleton",
@@ -5864,6 +6146,30 @@ void tst_qqmllanguage::inlineComponentDuplicateNameError()
QCOMPARE(component.errorString(), message);
}
+void tst_qqmllanguage::inlineComponentWithAliasInstantiatedWithNewProperties()
+{
+ // this tests that metaobjects are resolved in the correct order
+ // so that inline components are fully resolved before they are used
+ // in their parent component
+
+ QQmlEngine engine;
+ QQmlComponent component(&engine, testFileUrl("inlineComponentWithAliasInstantiated.qml"));
+ QScopedPointer<QObject> root {component.create()};
+ QVERIFY2(root, qPrintable(component.errorString()));
+ 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
@@ -5946,7 +6252,7 @@ void tst_qqmllanguage::qualifiedScopeInCustomParser()
"ListModel {\n"
" ListElement { text: \"a\"; type: BACKEND.EnumTester.FIRST }\n"
"}\n", QUrl());
- QVERIFY(component.isReady());
+ QVERIFY2(component.isReady(), qPrintable(component.errorString()));
QScopedPointer<QObject> obj(component.create());
QVERIFY(!obj.isNull());
}
@@ -5958,7 +6264,7 @@ void tst_qqmllanguage::checkUncreatableNoReason()
QString qml = QString("import QtQuick 2.0\nimport qt.uncreatable.noreason 1.0\nUncreatableElementNoReason {}");
QQmlComponent c(&engine);
c.setData(qml.toUtf8(), QUrl::fromLocalFile(QDir::currentPath()));
- QCOMPARE(c.errors().count(), 1);
+ QCOMPARE(c.errors().size(), 1);
QCOMPARE(c.errors().first().description(), QString("Type cannot be created in QML."));
}
@@ -5969,7 +6275,7 @@ void tst_qqmllanguage::checkURLtoURLObject()
"Component.onCompleted: { new URL(parent.source); } }");
QQmlComponent c(&engine);
c.setData(qml.toUtf8(), QUrl::fromLocalFile(QDir::currentPath()));
- QCOMPARE(c.errors().count(), 0);
+ QCOMPARE(c.errors().size(), 0);
}
struct TestValueType
@@ -6078,6 +6384,48 @@ void tst_qqmllanguage::extendedNamespace()
QCOMPARE(obj->property("fromExtension").toInt(), 9);
}
+void tst_qqmllanguage::extendedNamespaceByObject()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine);
+ c.setData("import StaticTest\n"
+ "import QtQml\n"
+ "ExtendedNamespaceByObject {\n"
+ " extension: 10\n"
+ "}", QUrl());
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> obj(c.create());
+ QVERIFY(!obj.isNull());
+
+ QCOMPARE(obj->property("extension").toInt(), 10);
+}
+
+void tst_qqmllanguage::extendedByAttachedType()
+{
+ QQmlEngine engine;
+ {
+ QQmlComponent c(&engine);
+ c.setData("import StaticTest\n"
+ "import QtQml\n"
+ "QtObject {\n"
+ " ExtendedNamespaceByObject.attachedName: \"name for extension\"\n"
+ "}",
+ QUrl());
+ QVERIFY2(!c.isReady(), qPrintable(c.errorString()));
+ }
+
+ {
+ QQmlComponent c(&engine);
+ c.setData("import StaticTest\n"
+ "import QtQml\n"
+ "QtObject {\n"
+ " Extended.attachedName: \"name for extension\"\n"
+ "}",
+ QUrl());
+ QVERIFY2(!c.isReady(), qPrintable(c.errorString()));
+ }
+}
+
void tst_qqmllanguage::factorySingleton()
{
QQmlEngine engine;
@@ -6118,23 +6466,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.count(), 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()
@@ -6153,7 +6493,194 @@ void tst_qqmllanguage::multiExtension()
QCOMPARE(o->property("c").toInt(), 12);
QCOMPARE(o->property("d").toInt(), 22);
QCOMPARE(o->property("f").toInt(), 31);
- QCOMPARE(o->property("g").toInt(), 44);
+ QCOMPARE(o->property("g").toInt(), 44); // NB: taken from the type, not from the extension!
+
+ QObject *extension = qmlExtendedObject(o.get());
+ QVERIFY(extension != nullptr);
+ QVERIFY(qobject_cast<ExtensionB *>(extension) != nullptr);
+
+ QCOMPARE(QQmlPrivate::qmlExtendedObject(o.get(), 0), extension);
+ QObject *baseTypeExtension = QQmlPrivate::qmlExtendedObject(o.get(), 1);
+ QVERIFY(baseTypeExtension);
+ QVERIFY(qobject_cast<ExtensionA *>(baseTypeExtension) != nullptr);
+}
+
+void tst_qqmllanguage::multiExtensionExtra()
+{
+ QQmlEngine engine;
+ {
+ QQmlComponent c(&engine);
+ c.setData("import StaticTest\nMultiExtensionThreeExtensions {}", QUrl());
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+
+ QObject *extension = qmlExtendedObject(o.get());
+ QVERIFY(extension != nullptr);
+ QVERIFY(qobject_cast<Extension *>(extension) != nullptr);
+
+ QCOMPARE(QQmlPrivate::qmlExtendedObject(o.get(), 0), extension);
+ QObject *baseTypeExtension = QQmlPrivate::qmlExtendedObject(o.get(), 1);
+ QVERIFY(baseTypeExtension);
+ QVERIFY(qobject_cast<ExtensionB *>(baseTypeExtension) != nullptr);
+ QObject *baseBaseTypeExtension = QQmlPrivate::qmlExtendedObject(o.get(), 2);
+ QVERIFY(baseBaseTypeExtension);
+ QVERIFY(qobject_cast<ExtensionA *>(baseBaseTypeExtension) != nullptr);
+ }
+
+ {
+ QQmlComponent c(&engine);
+ c.setData("import StaticTest\nMultiExtensionWithExtensionInBaseBase {}", QUrl());
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+
+ QObject *extension = qmlExtendedObject(o.get());
+ QVERIFY(extension != nullptr);
+ QVERIFY(qobject_cast<Extension *>(extension) != nullptr);
+
+ QCOMPARE(QQmlPrivate::qmlExtendedObject(o.get(), 0), extension);
+
+ QObject *baseBaseTypeExtension = QQmlPrivate::qmlExtendedObject(o.get(), 1);
+ QVERIFY(baseBaseTypeExtension);
+ QVERIFY(qobject_cast<ExtensionB *>(baseBaseTypeExtension) != nullptr);
+
+ QObject *baseBaseBaseTypeExtension = QQmlPrivate::qmlExtendedObject(o.get(), 2);
+ QVERIFY(baseBaseBaseTypeExtension);
+ QVERIFY(qobject_cast<ExtensionA *>(baseBaseBaseTypeExtension) != nullptr);
+ }
+}
+
+void tst_qqmllanguage::multiExtensionIndirect()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine);
+ c.setData("import StaticTest\nMultiExtensionIndirect {}", QUrl());
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QCOMPARE(o->property("a").toInt(), int('a'));
+ QCOMPARE(o->property("b").toInt(), 77); // indirect extension is not considered
+ QCOMPARE(o->property("p").toInt(), int('p'));
+ QCOMPARE(o->property("e").toInt(), int('e'));
+
+ QCOMPARE(o->property("c").toInt(), int('c')); // indirect extension is not considered
+ QCOMPARE(o->property("d").toInt(), 21); // indirect extension is not considered
+ QCOMPARE(o->property("f").toInt(), 31);
+ QCOMPARE(o->property("g").toInt(), 44); // NB: taken from the type, not from the extension!
+}
+
+void tst_qqmllanguage::multiExtensionQmlTypes()
+{
+ QQmlType extendedType = QQmlMetaType::qmlType(&MultiExtensionParent::staticMetaObject,
+ QStringLiteral("StaticTest"), QTypeRevision());
+ QVERIFY(extendedType.isValid());
+ QVERIFY(extendedType.extensionFunction());
+ QVERIFY(extendedType.extensionMetaObject() != nullptr);
+
+ QQmlType nonExtendedType = QQmlMetaType::qmlType(&ExtendedInParent::staticMetaObject,
+ QStringLiteral("StaticTest"), QTypeRevision());
+ QVERIFY(nonExtendedType.isValid());
+ QVERIFY(!nonExtendedType.extensionFunction());
+ QCOMPARE(nonExtendedType.extensionMetaObject(), nullptr);
+
+ QQmlType namespaceExtendedType = QQmlMetaType::qmlType(
+ &ExtendedByNamespace::staticMetaObject, QStringLiteral("StaticTest"), QTypeRevision());
+ QVERIFY(namespaceExtendedType.isValid());
+ QVERIFY(!namespaceExtendedType.extensionFunction()); // namespaces are non-creatable
+ QVERIFY(namespaceExtendedType.extensionMetaObject() != nullptr);
+
+ QQmlType namespaceNonExtendedType =
+ QQmlMetaType::qmlType(&ExtendedByNamespaceInParent::staticMetaObject,
+ QStringLiteral("StaticTest"), QTypeRevision());
+ QVERIFY(namespaceNonExtendedType.isValid());
+ QVERIFY(!namespaceNonExtendedType.extensionFunction());
+ QCOMPARE(namespaceNonExtendedType.extensionMetaObject(), nullptr);
+}
+
+void tst_qqmllanguage::extensionSpecial()
+{
+ QQmlEngine engine;
+
+ {
+ QQmlComponent c(&engine);
+ c.setData("import StaticTest\nExtendedInParent {}", QUrl());
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(o);
+
+ // property a exists only in the extension type
+ QCOMPARE(o->property("a").toInt(), int('a'));
+
+ // property c exists on the leaf type but since extension's properties
+ // are effectively FINAL, it is not used
+ QCOMPARE(o->property("c").toInt(), 11);
+ }
+
+ {
+ QQmlComponent c(&engine);
+ c.setData("import StaticTest\nExtendedByIndirect {}", QUrl());
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(o);
+
+ // there are (visibly) no properties in this case
+ QCOMPARE(o->property("b"), QVariant());
+ QCOMPARE(o->property("c"), QVariant());
+ QCOMPARE(o->property("d"), QVariant());
+ }
+
+ {
+ QQmlComponent c(&engine);
+ c.setData("import StaticTest\nExtendedInParentByIndirect {}", QUrl());
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(o);
+
+ // there are (visibly) no properties in this case (same as the previous)
+ QCOMPARE(o->property("b"), QVariant());
+ QCOMPARE(o->property("c"), QVariant());
+ QCOMPARE(o->property("d"), QVariant());
+ }
+}
+
+void tst_qqmllanguage::extensionRevision()
+{
+ QQmlEngine engine;
+ {
+ QQmlComponent c(&engine);
+ c.setData("import StaticTest 0.5\nExtendedWithRevisionOld { extension: 40\n}", QUrl());
+ QVERIFY(!c.isReady());
+ QRegularExpression error(
+ ".*\"ExtendedWithRevisionOld.extension\" is not available in StaticTest 0.5.*");
+ QVERIFY2(error.match(c.errorString()).hasMatch(),
+ qPrintable(u"Unmatched error: "_s + c.errorString()));
+ }
+
+ {
+ QQmlComponent c(&engine);
+ c.setData("import StaticTest 1.0\nExtendedWithRevisionNew { extension: 40\n}", QUrl());
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(o);
+ }
+}
+
+void tst_qqmllanguage::extendedGroupProperty()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine);
+ c.setData(R"(import StaticTest 1.0
+ ExtendedInGroup {
+ group.value: 42
+ group.value2: 42
+ }
+ )", QUrl());
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(o);
+
+ ExtendedInGroup *extendedInGroup = qobject_cast<ExtendedInGroup *>(o.data());
+ QVERIFY(extendedInGroup);
+ QCOMPARE(extendedInGroup->group()->value(), 42);
+ QCOMPARE(extendedInGroup->group()->value2(), 42);
}
void tst_qqmllanguage::invalidInlineComponent()
@@ -6174,21 +6701,51 @@ void tst_qqmllanguage::invalidInlineComponent()
void tst_qqmllanguage::warnOnInjectedParameters()
{
- QQmlEngine e;
- QQmlComponent c(&engine);
- QTest::ignoreMessage(QtWarningMsg, "qrc:/foo.qml:4:5 Parameter \"bar\" is not declared."
- " Injection of parameters into signal handlers is deprecated."
- " Use JavaScript functions with formal parameters instead.");
- c.setData("import QtQml\n"
- "QtObject {\n"
- " signal foo(bar: string)\n"
- " onFoo: print(bar)\n"
- " Component.onCompleted: foo('baz')\n"
- "}", QUrl("qrc:/foo.qml"));
- QVERIFY2(c.isReady(), qPrintable(c.errorString()));
- QTest::ignoreMessage(QtDebugMsg, "baz");
- QScopedPointer<QObject> o(c.create());
+ QQmlEngine e;
+ QQmlComponent c(&engine);
+ QTest::ignoreMessage(QtWarningMsg,
+ "qrc:/foo.qml:4:5 Parameter \"bar\" is not declared."
+ " Injection of parameters into signal handlers is deprecated."
+ " Use JavaScript functions with formal parameters instead.");
+ c.setData("import QtQml\n"
+ "QtObject {\n"
+ " signal foo(bar: string)\n"
+ " onFoo: print(bar)\n"
+ " Component.onCompleted: foo('baz')\n"
+ "}",
+ QUrl("qrc:/foo.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QTest::ignoreMessage(QtDebugMsg, "baz");
+ QScopedPointer<QObject> o(c.create());
+}
+
+#if QT_CONFIG(wheelevent)
+void tst_qqmllanguage::warnOnInjectedParametersFromCppSignal()
+{
+ QQmlEngine e;
+ QQmlComponent c(&engine);
+ QTest::ignoreMessage(QtWarningMsg,
+ "qrc:/qtbug93987.qml:6:21 Parameter \"wheel\" is not declared."
+ " Injection of parameters into signal handlers is deprecated."
+ " Use JavaScript functions with formal parameters instead.");
+ c.setData(R"(
+ import QtQuick
+ MouseArea {
+ width: 640
+ height: 480
+ onWheel: print(wheel.angleDelta)
+ })",
+ QUrl("qrc:/qtbug93987.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ constexpr auto wheelDelta = 120; // copied from tst_QAbstractSlider. probably a default unit
+ QPoint p(100, 100);
+ QWheelEvent event(p, p, QPoint(), QPoint(0, wheelDelta * 1), Qt::NoButton, Qt::NoModifier,
+ Qt::NoScrollPhase, false);
+ QTest::ignoreMessage(QtDebugMsg, "QPoint(0, 120)");
+ QCoreApplication::sendEvent(o.get(), &event);
}
+#endif
void tst_qqmllanguage::qtbug_86482()
{
@@ -6250,6 +6807,1858 @@ void tst_qqmllanguage::qtbug_85615()
QCOMPARE(o->property("resultNull").metaType(), QMetaType(QMetaType::Nullptr));
}
+void tst_qqmllanguage::bareInlineComponent()
+{
+ QQmlEngine engine;
+
+ QQmlComponent c(&engine, testFileUrl("bareInline.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+
+ QQmlMetaType::freeUnusedTypesAndCaches();
+
+ bool tab1Found = false;
+ const auto types = QQmlMetaType::qmlTypes();
+ for (const QQmlType &type : types) {
+ if (type.elementName() == QStringLiteral("Tab1")) {
+ QVERIFY(type.module().isEmpty());
+ tab1Found = true;
+
+ 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; }
+ void maybeBreakAtInstruction() final { }
+ void enteringFunction() final { }
+ void leavingFunction(const QV4::ReturnedValue &) final { }
+ void aboutToThrow() final { }
+};
+#else
+using DummyDebugger = QV4::Debugging::Debugger; // it's already dummy
+#endif
+
+void tst_qqmllanguage::hangOnWarning()
+{
+ QQmlEngine engine;
+
+ // A debugger prevents the disk cache.
+ // If we load the file from disk cache we don't parse it and we don't see the warning.
+ engine.handle()->setDebugger(new DummyDebugger);
+
+ QTest::ignoreMessage(QtWarningMsg,
+ qPrintable(QStringLiteral("%1:3 : Ignored annotation")
+ .arg(testFileUrl("hangOnWarning.qml").toString())));
+ QQmlComponent component(&engine, testFileUrl("hangOnWarning.qml"));
+ QScopedPointer<QObject> object(component.create());
+ 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;
+ QQmlComponent c(&engine);
+ c.setData(R"(
+import QtQml 2.0
+import StaticTest 1.0
+QtObject {
+ property EnumList enumList: EnumList {}
+ property var list: enumList.list()
+ property bool resultAlpha: EnumList.Alpha === list[0]
+ property bool resultBeta: EnumList.Beta === list[1]
+ property bool resultGamma: EnumList.Gamma === list[2]
+ property var resultEnumType: EnumList.Alpha
+ property var resultEnumListType: list[0]
+})",
+ QUrl());
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+
+ QScopedPointer<QObject> o(c.create());
+ QCOMPARE(o->property("resultAlpha").toBool(), true);
+ QCOMPARE(o->property("resultBeta").toBool(), true);
+ QCOMPARE(o->property("resultGamma").toBool(), true);
+ QCOMPARE(o->property("resultEnumType").metaType(), QMetaType(QMetaType::Int));
+ QCOMPARE(o->property("resultEnumListType").metaType(), QMetaType(QMetaType::Int));
+}
+
+void tst_qqmllanguage::deepInlineComponentScriptBinding()
+{
+ QQmlEngine e;
+ QQmlComponent c(&engine);
+ c.loadUrl(testFileUrl("deepInlineComponentScriptBinding.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+}
+
+void tst_qqmllanguage::propertyObserverOnReadonly()
+{
+ QQmlEngine e;
+ QQmlComponent c(&engine, testFileUrl("SelectionRange.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+
+ QCOMPARE(o->property("zoomer").toDouble(), o->property("height").toDouble());
+ o->setProperty("height", QVariant::fromValue<double>(54.2));
+ QCOMPARE(o->property("zoomer").toDouble(), 54.2);
+ QCOMPARE(o->property("height").toDouble(), 54.2);
+}
+
+void tst_qqmllanguage::valueTypeWithEnum()
+{
+ {
+ QQmlEngine e;
+ QQmlComponent c(&engine);
+ c.setData("import Test\n"
+ "ObjectTypeHoldingValueType1 {\n"
+ " vv.quality: ValueTypeWithEnum1.NormalQuality\n"
+ "}", QUrl());
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ ObjectTypeHoldingValueType1 *holder = qobject_cast<ObjectTypeHoldingValueType1 *>(o.data());
+
+ QCOMPARE(holder->vv().quality(), ValueTypeWithEnum1::NormalQuality);
+ }
+
+ {
+ QQmlEngine e;
+ QQmlComponent c(&engine);
+ c.setData("import Test\n"
+ "ObjectTypeHoldingValueType2 {\n"
+ " vv.quality: ValueTypeWithEnum2.LowQuality\n"
+ "}", QUrl());
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ ObjectTypeHoldingValueType2 *holder = qobject_cast<ObjectTypeHoldingValueType2 *>(o.data());
+
+ QCOMPARE(holder->vv().quality(), ValueTypeWithEnum2::LowQuality);
+ }
+}
+
+void tst_qqmllanguage::propertyAndAliasMustHaveDistinctNames_data()
+{
+ QTest::addColumn<QString>("fileName");
+ QTest::addColumn<QString>("error");
+
+ QTest::addRow("sameNamePropertyAlias") << "sameNamePropertyAlias.qml" << "Property duplicates alias name";
+ QTest::addRow("sameNameAliasProperty") << "sameNameAliasProperty.qml" << "Alias has same name as existing property";
+}
+
+void tst_qqmllanguage::propertyAndAliasMustHaveDistinctNames()
+{
+ QFETCH(QString, fileName);
+ QFETCH(QString, error);
+ QQmlEngine engine;
+ QQmlComponent c(&engine, testFileUrl(fileName));
+ QVERIFY(!c.isReady());
+ auto actualError = c.errorString();
+ QVERIFY2(actualError.contains(error), qPrintable(actualError));
+}
+
+void tst_qqmllanguage::enumsFromRelatedTypes()
+{
+ QQmlEngine e;
+ {
+ QQmlComponent c(&engine);
+ c.setData("import Test\n"
+ "ObjectTypeHoldingValueType1 {\n"
+ " vv.quality: ObjectTypeHoldingValueType1.NormalQuality\n"
+ "}", QUrl("Test1.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ ObjectTypeHoldingValueType1 *holder = qobject_cast<ObjectTypeHoldingValueType1 *>(o.data());
+ QCOMPARE(holder->q(), ValueTypeWithEnum1::NormalQuality);
+ }
+
+ {
+ QQmlComponent c(&engine);
+ c.setData("import Test\n"
+ "ObjectTypeHoldingValueType2 {\n"
+ " vv.quality: ObjectTypeHoldingValueType2.LowQuality\n"
+ "}", QUrl("Test2.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+
+ QTest::ignoreMessage(
+ QtWarningMsg,
+ "Test2.qml:3:5: Unable to assign [undefined] to ValueTypeWithEnum2::Quality");
+ QScopedPointer<QObject> o(c.create());
+ ObjectTypeHoldingValueType2 *holder = qobject_cast<ObjectTypeHoldingValueType2 *>(o.data());
+ QCOMPARE(holder->q(), ValueTypeWithEnum2::HighQuality);
+ }
+}
+
+void tst_qqmllanguage::variantListConversion()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine, testFileUrl("variantListConversion.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+
+ Foo *foo = qobject_cast<Foo *>(o.data());
+ QVERIFY(foo);
+ const QVariantList list = foo->getList();
+ QCOMPARE(list.size(), 3);
+ const Large l0 = qvariant_cast<Large>(list.at(0));
+ QCOMPARE(l0.a, 12ull);
+ const Large l1 = qvariant_cast<Large>(list.at(1));
+ QCOMPARE(l1.a, 13ull);
+ const QObject *attached = qvariant_cast<QObject *>(list.at(2));
+ QVERIFY(attached);
+ QCOMPARE(attached->metaObject(), &QQmlComponentAttached::staticMetaObject);
+}
+
+void tst_qqmllanguage::thisInArrowFunction()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine, testFileUrl("thisInArrow.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+
+ QTest::ignoreMessage(QtDebugMsg, "43");
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+
+ QCOMPARE(qvariant_cast<QObject *>(o->property("arrowResult")), o.data());
+ QCOMPARE(qvariant_cast<QObject *>(o->property("funcResult")), o.data());
+ QCOMPARE(qvariant_cast<QObject *>(o->property("aResult")), o.data());
+ QCOMPARE(qvariant_cast<QObject *>(o->property("aaResult")), o.data());
+
+ QCOMPARE(qvariant_cast<QObject *>(o->property("fResult")), nullptr);
+ QCOMPARE(o->property("fResult").metaType(), QMetaType::fromType<QJSValue>());
+ QVERIFY(qvariant_cast<QJSValue>(o->property("fResult")).isObject());
+
+ QObject *child = qvariant_cast<QObject *>(o->property("child"));
+ QVERIFY(child != nullptr);
+ QCOMPARE(qvariant_cast<QObject *>(o->property("ffResult")), child);
+}
+
+void tst_qqmllanguage::jittedAsCast()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine, testFileUrl("jittedAsCast.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QCOMPARE(o->property("running").toBool(), true);
+ QTRY_COMPARE(o->property("running").toBool(), false);
+ QCOMPARE(o->property("interval").toInt(), 10);
+}
+
+void tst_qqmllanguage::propertyNecromancy()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine, testFileUrl("propertyNecromancy.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(qvariant_cast<QObject *>(o->property("notified")) != nullptr);
+
+ // It becomes null, not undefined.
+ QTRY_VERIFY(o->property("notified").isNull());
+ QVERIFY(o->property("notified").isValid());
+}
+
+void tst_qqmllanguage::generalizedGroupedProperty()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine, testFileUrl("generalizedGroupedProperty.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+
+ QCOMPARE(o->objectName(), QStringLiteral("foo"));
+ MyAttachedObject *rootAttached = static_cast<MyAttachedObject *>(
+ qmlAttachedPropertiesObject<MyQmlObject>(o.data()));
+ QVERIFY(rootAttached);
+ QCOMPARE(rootAttached->value(), 0);
+ QCOMPARE(rootAttached->value2(), 0);
+
+ ImmediateProperties *child = qvariant_cast<ImmediateProperties *>(o->property("child"));
+ QVERIFY(child);
+ QCOMPARE(child->objectName(), QStringLiteral("barrrrr"));
+
+ MyAttachedObject *childAttached = static_cast<MyAttachedObject *>(
+ qmlAttachedPropertiesObject<MyQmlObject>(child));
+ QVERIFY(childAttached);
+ QCOMPARE(childAttached->value(), 0);
+
+ qmlExecuteDeferred(child);
+ QCOMPARE(childAttached->value(), 4);
+
+ QCOMPARE(o->objectName(), QStringLiteral("barrrrr ..."));
+ QCOMPARE(rootAttached->value(), 10);
+ QCOMPARE(rootAttached->value2(), 0);
+ QCOMPARE(childAttached->value(), 4);
+
+ o->metaObject()->invokeMethod(o.data(), "something");
+ QCOMPARE(o->objectName(), QStringLiteral("rabrab ..."));
+
+ ImmediateProperties *meanChild = qvariant_cast<ImmediateProperties *>(o->property("meanChild"));
+ QVERIFY(meanChild);
+ qmlExecuteDeferred(meanChild);
+ QCOMPARE(child->objectName(), QStringLiteral("bar"));
+ QCOMPARE(o->objectName(), QStringLiteral("bar ..."));
+ QCOMPARE(childAttached->value(), 11);
+
+ ImmediateProperties *deferred = qvariant_cast<ImmediateProperties *>(o->property("deferred"));
+ QVERIFY(deferred);
+ QCOMPARE(deferred->objectName(), QStringLiteral("holz"));
+ qmlExecuteDeferred(deferred);
+ QCOMPARE(o->objectName(), QStringLiteral("holz ..."));
+ QCOMPARE(rootAttached->value(), 10);
+ QCOMPARE(rootAttached->value2(), 12);
+
+ ImmediateProperties *meanDeferred
+ = qvariant_cast<ImmediateProperties *>(o->property("meanDeferred"));
+ QVERIFY(meanDeferred);
+ qmlExecuteDeferred(meanDeferred);
+ QCOMPARE(deferred->objectName(), QStringLiteral("stein"));
+ QCOMPARE(o->objectName(), QStringLiteral("stein ..."));
+
+ {
+ QQmlComponent bad(&engine, testFileUrl("badGeneralizedGroupedProperty.qml"));
+ QVERIFY(bad.isError());
+ QVERIFY(bad.errorString().contains(
+ QStringLiteral("Cannot assign to non-existent property \"root\"")));
+ }
+
+ {
+ QQmlComponent bad(&engine, testFileUrl("badGeneralizedGroupedProperty2.qml"));
+ QVERIFY(bad.isError());
+ QVERIFY(bad.errorString().contains(QStringLiteral("Invalid grouped property access")));
+ }
+}
+
+void tst_qqmllanguage::groupedAttachedProperty_data()
+{
+ QTest::addColumn<QString>("file");
+ QTest::addRow("12") << QStringLiteral("validAttachedProperty.12.qml");
+ QTest::addRow("13") << QStringLiteral("validAttachedProperty.13.qml");
+}
+
+void tst_qqmllanguage::groupedAttachedProperty()
+{
+ QFETCH(QString, file);
+
+ QQmlEngine engine;
+ QQmlComponent c(&engine, testFileUrl(file));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+ MyTypeObject *typed = qobject_cast<MyTypeObject *>(o.data());
+ QVERIFY(typed != nullptr);
+ MyGroupedObject *grouped = typed->grouped();
+ QVERIFY(grouped != nullptr);
+ MyAttachedObject *attached = qobject_cast<MyAttachedObject *>(
+ qmlAttachedPropertiesObject<MyQmlObject>(grouped));
+ QVERIFY(attached != nullptr);
+ QCOMPARE(attached->value(), 10);
+}
+
+void tst_qqmllanguage::ambiguousContainingType()
+{
+ // Need to do it twice, so that we load from disk cache the second time.
+ for (int i = 0; i < 2; ++i) {
+ QQmlEngine engine;
+
+ // Should not crash when loading the type
+ QQmlComponent c(&engine, testFileUrl("ambiguousBinding/ambiguousContainingType.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+ }
+}
+
+void tst_qqmllanguage::objectAsBroken()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine, testFileUrl("asBroken.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+ QVariant selfAsBroken = o->property("selfAsBroken");
+ QVERIFY(selfAsBroken.isValid());
+ QCOMPARE(selfAsBroken.metaType(), QMetaType::fromType<std::nullptr_t>());
+
+ QQmlComponent b(&engine, testFileUrl("Broken.qml"));
+ QVERIFY(b.isError());
+}
+
+void tst_qqmllanguage::customValueTypes()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine, testFileUrl("customValueTypes.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+
+ QCOMPARE(qvariant_cast<BaseValueType>(o->property("base")).content(), 27);
+ QCOMPARE(qvariant_cast<DerivedValueType>(o->property("derived")).content(), 28);
+
+ o->setObjectName(QStringLiteral("a"));
+
+ QCOMPARE(qvariant_cast<DerivedValueType>(o->property("derived")).content(), 14);
+ QCOMPARE(qvariant_cast<BaseValueType>(o->property("base")).content(), 13);
+}
+
+void tst_qqmllanguage::valueTypeList()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine, testFileUrl("valueTypeList.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+
+ {
+ QCOMPARE(o->property("c").toInt(), 17);
+ QCOMPARE(qvariant_cast<QPointF>(o->property("d")), QPointF(3, 4));
+ QCOMPARE(qvariant_cast<DerivedValueType>(o->property("y")).content(), 29);
+ const QList<DerivedValueType> x = qvariant_cast<QList<DerivedValueType>>(o->property("x"));
+ QCOMPARE(x.size(), 3);
+ for (const DerivedValueType &d : x)
+ QCOMPARE(d.content(), 29);
+
+ const QList<BaseValueType> baseList
+ = qvariant_cast<QList<BaseValueType>>(o->property("baseList"));
+ QCOMPARE(baseList.size(), 3);
+ for (const BaseValueType &b : baseList)
+ QCOMPARE(b.content(), 29);
+
+ const QRectF f = qvariant_cast<QRectF>(o->property("f"));
+ QCOMPARE(f, QRectF(0, 2, 17, 1));
+ }
+
+ o->setObjectName(QStringLiteral("foo"));
+ {
+ QCOMPARE(qvariant_cast<QPointF>(o->property("d")), QPointF(12, 4));
+
+ // x is an actual value type list. We don't store references to y in there, but actual copies.
+ QCOMPARE(qvariant_cast<DerivedValueType>(o->property("y")).content(), 29);
+
+ const QList<DerivedValueType> x = qvariant_cast<QList<DerivedValueType>>(o->property("x"));
+ QCOMPARE(x.size(), 3);
+ QCOMPARE(x[0].content(), 29);
+ QCOMPARE(x[1].content(), 30);
+ QCOMPARE(x[2].content(), 29);
+
+ const QList<BaseValueType> baseList
+ = qvariant_cast<QList<BaseValueType>>(o->property("baseList"));
+ QCOMPARE(baseList.size(), 3);
+ for (const BaseValueType &b : baseList)
+ QCOMPARE(b.content(), 29);
+ }
+}
+
+void tst_qqmllanguage::componentMix()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine, testFileUrl("componentMix.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+ QObject *things = qvariant_cast<QObject *>(o->property("things"));
+ QVERIFY(things);
+ QObject *delegated = qvariant_cast<QObject *>(o->property("delegated"));
+ QVERIFY(delegated);
+ QObject *view = qvariant_cast<QObject *>(things->property("view"));
+ QVERIFY(view);
+ QObject *delegate = qvariant_cast<QObject *>(view->property("delegate"));
+ QVERIFY(delegate);
+ QCOMPARE(delegate->metaObject(), &QQmlComponent::staticMetaObject);
+ QObject *delegate2 = qvariant_cast<QObject *>(delegated->property("delegate"));
+ QVERIFY(delegate2);
+ QCOMPARE(delegate2->metaObject(), &QQmlComponent::staticMetaObject);
+}
+
+void tst_qqmllanguage::uncreatableAttached()
+{
+ qmlRegisterTypesAndRevisions<ItemAttached>("ABC", 1);
+ QQmlEngine engine;
+ const QUrl url = testFileUrl("uncreatableAttached.qml");
+ QQmlComponent c(&engine, url);
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QTest::ignoreMessage(QtWarningMsg, "Only foo can have ItemAttached!");
+ QScopedPointer o(c.create());
+ QVERIFY(o.isNull());
+ QVERIFY(c.errorString().contains(
+ QLatin1String("Could not create attached properties object 'ItemAttached'")));
+}
+
+class MyGadget
+{
+ Q_GADGET
+ Q_PROPERTY(int value READ value WRITE setValue RESET resetValue)
+
+public:
+ int value() const { return m_value; }
+ void setValue(int value) { m_value = value; }
+ void resetValue() { setValue(25); }
+
+ bool operator==(const MyGadget &other) const { return m_value == other.m_value; }
+ bool operator!=(const MyGadget &other) const { return m_value != other.m_value; }
+
+private:
+ int m_value = -1;
+};
+
+class MyObject : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(MyGadget gadget MEMBER m_gadget RESET resetGadget NOTIFY gadgetChanged)
+ void resetGadget() { qDebug("FAIL"); }
+
+public:
+ MyObject(QObject *parent = nullptr) : QObject(parent) { }
+ MyGadget m_gadget;
+
+signals:
+ void gadgetChanged();
+};
+
+void tst_qqmllanguage::resetGadgetProperty()
+{
+ qmlRegisterType<MyObject>("MyObject", 1, 0, "MyObject");
+
+ QQmlEngine engine;
+ QQmlComponent component(&engine);
+ component.setData(
+ "import QtQml 2.0; import MyObject 1.0; MyObject { gadget.value: undefined }",
+ QUrl());
+ QVERIFY2(component.isReady(), qPrintable(component.errorString()));
+ QScopedPointer<QObject> o(component.create());
+ QVERIFY2(!o.isNull(), qPrintable(component.errorString()));
+
+ MyObject *m = qobject_cast<MyObject *>(o.data());
+ QVERIFY(m);
+ QCOMPARE(m->m_gadget.value(), 25);
+}
+
+void tst_qqmllanguage::leakingAttributesQmlAttached()
+{
+ // Check for leakage in the QML_ATTACHED macro
+ {
+ QQmlComponent c(&engine);
+ c.setData("import StaticTest\n"
+ "import QtQuick\n"
+ "Item {"
+ " OriginalQmlAttached.abc: true"
+ "}",
+ QUrl());
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(o);
+ QObject *attached = qmlAttachedPropertiesObject<OriginalQmlAttached>(o.data());
+ QVERIFY(attached);
+ QCOMPARE(attached->property("abc"), QVariant(true));
+ }
+ {
+ QQmlComponent c(&engine);
+ c.setData("import StaticTest\n"
+ "import QtQuick\n"
+ "Item {"
+ " LeakingQmlAttached.abc: true"
+ "}",
+ QUrl());
+ QVERIFY(!c.isReady());
+ }
+
+ {
+ QQmlComponent c(&engine);
+ c.setData("import StaticTest\n"
+ "import QtQuick\n"
+ "Item {"
+ " DerivedQmlAttached.anotherAbc: \"I am not a bool.\"\n"
+ " OriginalQmlAttached.abc: true"
+ "}",
+ QUrl());
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(o);
+
+ QObject *attached = qmlAttachedPropertiesObject<OriginalQmlAttached>(o.data());
+ QVERIFY(attached);
+ QCOMPARE(attached->property("abc"), QVariant(true));
+
+ attached = qmlAttachedPropertiesObject<DerivedQmlAttached>(o.data());
+ QVERIFY(attached);
+ QCOMPARE(attached->property("anotherAbc"), QVariant("I am not a bool."));
+ }
+}
+
+void tst_qqmllanguage::leakingAttributesQmlSingleton()
+{
+ {
+ QQmlComponent c(&engine);
+ c.setData(R"(
+import StaticTest
+import QtQuick
+Item {
+ Text { text: OriginalSingleton.abc }
+ Text { text: OriginalSingleton.abc }
+ Text { id: check }
+ Component.onCompleted: {
+ OriginalSingleton.abc = "Updated string content!"
+ check.text = "onCompletedExecuted!"
+ }
+})",
+ QUrl());
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(o);
+ QCOMPARE(o->children().size(), 4);
+ QCOMPARE(o->children()[2]->property("text"), QVariant("onCompletedExecuted!"));
+ QCOMPARE(o->children()[0]->property("text"), QVariant("Updated string content!"));
+ QCOMPARE(o->children()[1]->property("text"), QVariant("Updated string content!"));
+ }
+ {
+ QQmlComponent c(&engine);
+
+ c.setData(R"(
+import StaticTest
+import QtQuick
+Item {
+ property var text: LeakingSingleton.abc
+ property var text2: LeakingSingleton.abc
+ Text { id: check }
+ Component.onCompleted: {
+ LeakingSingleton.abc = "Updated string content!"
+ check.text = "onCompletedExecuted!"
+ }
+})",
+ QUrl());
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(o);
+ QCOMPARE(o->children().size(), 2);
+ QCOMPARE(o->children().front()->property("text"), QVariant("onCompletedExecuted!"));
+ // empty because not a singleton -> LeakingSingleton.abc is [undefined]
+ QVERIFY(!o->property("text").isValid());
+ QVERIFY(!o->property("text2").isValid());
+ }
+ {
+ QQmlComponent c(&engine);
+ c.setData(R"(
+import StaticTest
+import QtQuick
+Item {
+ Text { text: DerivedSingleton.anotherAbc }
+ Text { text: DerivedSingleton.anotherAbc }
+ Text { id: check }
+ Component.onCompleted: {
+ DerivedSingleton.anotherAbc = "Updated string content!";
+ check.text = "onCompletedExecuted!"
+ }
+})",
+ QUrl());
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(o);
+ QCOMPARE(o->children().size(), 4);
+ QCOMPARE(o->children()[2]->property("text"), QVariant("onCompletedExecuted!"));
+ QCOMPARE(o->children()[0]->property("text"), QVariant("Updated string content!"));
+ QCOMPARE(o->children()[1]->property("text"), QVariant("Updated string content!"));
+ }
+}
+
+void tst_qqmllanguage::leakingAttributesQmlForeign()
+{
+ {
+ QQmlComponent c(&engine);
+ c.setData(R"(
+import StaticTest
+ForeignerForeign {
+ abc: "Hello World"
+})",
+ QUrl());
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(o);
+ QVERIFY(o->property("abc").isValid());
+ }
+ {
+ QQmlComponent c(&engine);
+ c.setData(R"(
+import StaticTest
+LeakingForeignerForeign {
+})",
+ QUrl());
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(o);
+ 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()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine);
+ c.setData(R"(
+ import QML
+ QtObject {
+ id: root
+ property list<string> props: Object.getOwnPropertyNames(root.Component)
+ }
+ )", QUrl(QStringLiteral("attachedOwn.qml")));
+
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+ const QStringList expected {"objectName", "objectNameChanged", "completed", "destruction"};
+ QCOMPARE(o->property("props").value<QStringList>(), expected);
+}
+
+void tst_qqmllanguage::bindableOnly()
+{
+ qmlRegisterTypesAndRevisions<BindableOnly>("ABC", 1);
+ QQmlEngine engine;
+
+ QQmlComponent c(&engine);
+ c.setData("import ABC\nBindableOnly {\n"
+ " property int a: score\n"
+ " data: \"sc\" + \"ore\"\n"
+ " objectName: data\n"
+ "}", QUrl(u"bindableOnly.qml"_s));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+ BindableOnly *bindableOnly = qobject_cast<BindableOnly *>(o.data());
+ QCOMPARE(bindableOnly->scoreBindable().value(), 4);
+ QCOMPARE(o->property("a").toInt(), 4);
+ bindableOnly->scoreBindable().setValue(5);
+ QCOMPARE(bindableOnly->scoreBindable().value(), 5);
+ QCOMPARE(o->property("a").toInt(), 5);
+ QCOMPARE(o->property("data").value<QByteArray>(), QByteArray("score"));
+ QCOMPARE(o->objectName(), QStringLiteral("score"));
+}
+
+static void listsEqual(QObject *object, const char *method)
+{
+ const QByteArray v4SequencePropertyName = QByteArray("v4Sequence") + method;
+ const QByteArray jsArrayPropertyName = QByteArray("jsArray") + method;
+
+ const QList<QRectF> v4SequenceProperty
+ = object->property(v4SequencePropertyName.constData()).value<QList<QRectF>>();
+ const QList<QRectF> jsArrayProperty
+ = object->property(jsArrayPropertyName.constData()).value<QList<QRectF>>();
+
+ const qsizetype v4SequenceCount = v4SequenceProperty.size();
+ QCOMPARE(v4SequenceCount, jsArrayProperty.size());
+
+ for (qsizetype i = 0; i < v4SequenceCount; ++i)
+ QCOMPARE(v4SequenceProperty.at(i), jsArrayProperty.at(i));
+}
+
+void tst_qqmllanguage::v4SequenceMethods()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, testFileUrl("v4SequenceMethods.qml"));
+ QVERIFY2(component.isReady(), qPrintable(component.errorString()));
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+
+ QCOMPARE(object->property("v4SequenceToString"), object->property("jsArrayToString"));
+ QCOMPARE(object->property("v4SequenceToLocaleString"), object->property("jsArrayToLocaleString"));
+
+ QVERIFY(object->property("entriesMatch").toBool());
+ QVERIFY(object->property("keysMatch").toBool());
+ QVERIFY(object->property("valuesMatch").toBool());
+
+ listsEqual(object.data(), "Concat");
+ listsEqual(object.data(), "Pop");
+ listsEqual(object.data(), "Push");
+ listsEqual(object.data(), "Reverse");
+ listsEqual(object.data(), "Shift");
+ listsEqual(object.data(), "Unshift");
+ listsEqual(object.data(), "Filter");
+ listsEqual(object.data(), "Sort1");
+ listsEqual(object.data(), "Sort2");
+
+ QCOMPARE(object->property("v4SequenceFind"), object->property("jsArrayFind"));
+ QCOMPARE(object->property("v4SequenceFind").value<QRectF>().x(), 1.0);
+
+ QCOMPARE(object->property("v4SequenceFindIndex"), object->property("jsArrayFindIndex"));
+ QCOMPARE(object->property("v4SequenceFindIndex").toInt(), 1);
+
+ QCOMPARE(object->property("v4SequenceIncludes"), object->property("jsArrayIncludes"));
+ QVERIFY(object->property("v4SequenceIncludes").toBool());
+
+ QCOMPARE(object->property("v4SequenceJoin"), object->property("jsArrayJoin"));
+ QVERIFY(object->property("v4SequenceJoin").toString().contains(QStringLiteral("1")));
+
+ QCOMPARE(object->property("v4SequencePopped"), object->property("jsArrayPopped"));
+ QVERIFY(object->property("v4SequencePopped").value<QRectF>().x() != 1.0);
+
+ QCOMPARE(object->property("v4SequencePushed"), object->property("jsArrayPushed"));
+ QCOMPARE(object->property("v4SequencePushed").toInt(), 4);
+
+ QCOMPARE(object->property("v4SequenceShifted"), object->property("jsArrayShifted"));
+ QCOMPARE(object->property("v4SequenceShifted").value<QRectF>().x(), 1.0);
+
+ QCOMPARE(object->property("v4SequenceUnshifted"), object->property("jsArrayUnshifted"));
+ QCOMPARE(object->property("v4SequenceUnshifted").toInt(), 4);
+
+ QCOMPARE(object->property("v4SequenceIndexOf"), object->property("jsArrayIndexOf"));
+ QCOMPARE(object->property("v4SequenceIndexOf").toInt(), 1);
+
+ QCOMPARE(object->property("v4SequenceLastIndexOf"), object->property("jsArrayLastIndexOf"));
+ QCOMPARE(object->property("v4SequenceLastIndexOf").toInt(), 2);
+
+ QCOMPARE(object->property("v4SequenceEvery"), object->property("jsArrayEvery"));
+ QVERIFY(object->property("v4SequenceEvery").toBool());
+
+ QCOMPARE(object->property("v4SequenceSome"), object->property("jsArrayEvery"));
+ QVERIFY(object->property("v4SequenceSome").toBool());
+
+ QCOMPARE(object->property("v4SequenceForEach"), object->property("jsArrayForEach"));
+ QCOMPARE(object->property("v4SequenceForEach").toString(), QStringLiteral("-1--5--9-"));
+
+ QCOMPARE(object->property("v4SequenceMap").toStringList(), object->property("jsArrayMap").toStringList());
+ QCOMPARE(object->property("v4SequenceReduce").toString(), object->property("jsArrayReduce").toString());
+
+ QCOMPARE(object->property("v4SequenceOwnPropertyNames").toStringList(),
+ object->property("jsArrayOwnPropertyNames").toStringList());
+}
+
+void tst_qqmllanguage::v4SequenceMethodsWithParams_data()
+{
+ QTest::addColumn<double>("i");
+ QTest::addColumn<double>("j");
+ QTest::addColumn<double>("k");
+
+ const double indices[] = {
+ double(std::numeric_limits<qsizetype>::min()),
+ double(std::numeric_limits<qsizetype>::min()) + 1,
+ double(std::numeric_limits<uint>::min()) - 1,
+ double(std::numeric_limits<uint>::min()),
+ double(std::numeric_limits<uint>::min()) + 1,
+ double(std::numeric_limits<int>::min()),
+ -10, -3, -2, -1, 0, 1, 2, 3, 10,
+ double(std::numeric_limits<int>::max()),
+ double(std::numeric_limits<uint>::max()) - 1,
+ double(std::numeric_limits<uint>::max()),
+ double(std::numeric_limits<uint>::max()) + 1,
+ double(std::numeric_limits<qsizetype>::max() - 1),
+ double(std::numeric_limits<qsizetype>::max()),
+ };
+
+ // We cannot test the full cross product. So, take a random sample instead.
+ const qsizetype numIndices = sizeof(indices) / sizeof(double);
+ qsizetype seed = QRandomGenerator::global()->generate();
+ const int numSamples = 4;
+ for (int i = 0; i < numSamples; ++i) {
+ seed = qHash(i, seed);
+ const double vi = indices[qAbs(seed) % numIndices];
+ for (int j = 0; j < numSamples; ++j) {
+ seed = qHash(j, seed);
+ const double vj = indices[qAbs(seed) % numIndices];
+ for (int k = 0; k < numSamples; ++k) {
+ seed = qHash(k, seed);
+ const double vk = indices[qAbs(seed) % numIndices];
+ const QString tag = QLatin1String("%1/%2/%3")
+ .arg(QString::number(vi), QString::number(vj), QString::number(vk));
+ QTest::newRow(qPrintable(tag)) << vi << vj << vk;
+
+ // output all the tags so that we can find out what combination caused a test to hang.
+ qDebug().noquote() << "scheduling" << tag;
+ }
+ }
+ }
+}
+
+void tst_qqmllanguage::v4SequenceMethodsWithParams()
+{
+ QFETCH(double, i);
+ QFETCH(double, j);
+ QFETCH(double, k);
+ QQmlEngine engine;
+ QQmlComponent component(&engine, testFileUrl("v4SequenceMethodsWithParams.qml"));
+ QVERIFY2(component.isReady(), qPrintable(component.errorString()));
+ QScopedPointer<QObject> object(component.createWithInitialProperties({
+ {QStringLiteral("i"), i},
+ {QStringLiteral("j"), j},
+ {QStringLiteral("k"), k}
+ }));
+ QVERIFY(!object.isNull());
+
+ listsEqual(object.data(), "CopyWithin");
+ listsEqual(object.data(), "Fill");
+ listsEqual(object.data(), "Slice");
+ listsEqual(object.data(), "Splice");
+ listsEqual(object.data(), "Spliced");
+
+ QCOMPARE(object->property("v4SequenceIndexOf"), object->property("jsArrayIndexOf"));
+ QCOMPARE(object->property("v4SequenceLastIndexOf"), object->property("jsArrayLastIndexOf"));
+}
+
+void tst_qqmllanguage::jsFunctionOverridesImport()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, testFileUrl("jsFunctionOverridesImport.qml"));
+ QVERIFY2(component.isReady(), qPrintable(component.errorString()));
+ QScopedPointer<QObject> object(component.create());
+ QCOMPARE(object->objectName(), u"foo"_s);
+}
+
+void tst_qqmllanguage::bindingAliasToComponentUrl()
+{
+ QQmlEngine engine;
+ {
+ QQmlComponent component(&engine, testFileUrl("bindingAliasToComponentUrl.qml"));
+ QVERIFY2(component.isReady(), qPrintable(component.errorString()));
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(object);
+ QCOMPARE(object->property("accessibleNormalUrl"), object->property("urlClone"));
+ }
+ {
+ QQmlComponent component(&engine, testFileUrl("bindingAliasToComponentUrl2.qml"));
+ QVERIFY2(component.isReady(), qPrintable(component.errorString()));
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(object);
+ QCOMPARE(object->property("accessibleNormalProgress"), QVariant(1.0));
+ }
+}
+
+void tst_qqmllanguage::badGroupedProperty()
+{
+ QQmlEngine engine;
+ const QUrl url = testFileUrl("badGroupedProperty.qml");
+ QQmlComponent c(&engine, url);
+ QVERIFY(c.isError());
+ QCOMPARE(c.errorString(),
+ QStringLiteral("%1:6 Cannot assign to non-existent property \"onComplete\"\n")
+ .arg(url.toString()));
+}
+
+void tst_qqmllanguage::functionInGroupedProperty()
+{
+ QQmlEngine engine;
+ const QUrl url = testFileUrl("functionInGroupedProperty.qml");
+ QQmlComponent c(&engine, url);
+ QVERIFY(c.isError());
+ QCOMPARE(c.errorString(),
+ QStringLiteral("%1:6 Function declaration inside grouped property\n")
+ .arg(url.toString()));
+}
+
+void tst_qqmllanguage::signalInlineComponentArg()
+{
+ QQmlEngine engine;
+ {
+ QQmlComponent component(&engine, testFileUrl("SignalInlineComponentArg.qml"));
+ QVERIFY2(component.isReady(), qPrintable(component.errorString()));
+ QScopedPointer<QObject> object(component.create());
+
+ QCOMPARE(object->property("success"), u"Signal was called"_s);
+ }
+ {
+ QQmlComponent component(&engine, testFileUrl("signalInlineComponentArg1.qml"));
+ QVERIFY2(component.isReady(), qPrintable(component.errorString()));
+ QScopedPointer<QObject> object(component.create());
+
+ QCOMPARE(object->property("successFromOwnSignal"),
+ u"Own signal was called with component from another file"_s);
+ QCOMPARE(object->property("successFromSignalFromFile"),
+ u"Signal was called from another file"_s);
+ }
+}
+
+void tst_qqmllanguage::functionSignatureEnforcement()
+{
+ QQmlEngine engine;
+
+ QQmlComponent c1(&engine, testFileUrl("signatureIgnored.qml"));
+ QVERIFY2(c1.isReady(), qPrintable(c1.errorString()));
+
+ QScopedPointer<QObject> ignored(c1.create());
+ QCOMPARE(ignored->property("l").toInt(), 5);
+ QCOMPARE(ignored->property("m").toInt(), 77);
+ QCOMPARE(ignored->property("n").toInt(), 67);
+
+ 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(), 77);
+ QCOMPARE(enforced->property("n").toInt(), 99);
+ QCOMPARE(enforced->property("o").toInt(), 77);
+}
+
+void tst_qqmllanguage::importPrecedence()
+{
+ QQmlEngine engine;
+
+ QQmlComponent c1(&engine, testFileUrl("importPrecedenceGood.qml"));
+ QVERIFY2(c1.isReady(), qPrintable(c1.errorString()));
+ QScopedPointer<QObject> o1(c1.create());
+ QVERIFY(!o1.isNull());
+ QVERIFY(o1->property("theAgent").value<QObject *>() != nullptr);
+
+ QUrl c2Url = testFileUrl("importPrecedenceBad.qml");
+ QQmlComponent c2(&engine, c2Url);
+ QVERIFY2(c2.isReady(), qPrintable(c2.errorString()));
+ QTest::ignoreMessage(
+ QtWarningMsg,
+ qPrintable(c2Url.toString() + u":11: ReferenceError: agent is not defined"_s));
+
+ QScopedPointer<QObject> o2(c2.create());
+ QVERIFY(!o2.isNull());
+ QCOMPARE(o2->property("theAgent").value<QObject *>(), nullptr);
+}
+
+void tst_qqmllanguage::nullIsNull()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine, testFileUrl("nullIsNull.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+ QVERIFY(o->property("someProperty").value<QObject*>() != nullptr);
+ QTRY_COMPARE(o->property("someProperty").value<QObject*>(), nullptr);
+}
+
+void tst_qqmllanguage::multiRequired()
+{
+ QQmlEngine engine;
+ const QUrl url = testFileUrl("multiRequired.qml");
+ QQmlComponent c(&engine, url);
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(o.isNull());
+ QCOMPARE(c.errorString(),
+ 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;
+ QQmlComponent c(&engine, testFileUrl("objectAndGadgetMethodCallsRejectThisObject.qml"));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+
+ const QList<int> lines = { 15, 19, 21, 22, 24, 25, 28, 31 };
+ for (int line : lines) {
+ const QString message
+ = ".*:%1: 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'"_L1.arg(QString::number(line));
+ QTest::ignoreMessage(QtWarningMsg, QRegularExpression(message));
+ }
+
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+
+ QCOMPARE(o->property("badRect"), QRectF(1, 2, 3, 4));
+ QCOMPARE(o->property("goodRect1"), QRectF(1, 2, 3, 4));
+ QCOMPARE(o->property("goodRect2"), QRectF(1, 2, 3, 4));
+
+ QCOMPARE(o->property("badString"), QStringLiteral("27"));
+ QCOMPARE(o->property("goodString1"), QStringLiteral("27"));
+ QCOMPARE(o->property("goodString2"), QStringLiteral("27"));
+ QCOMPARE(o->property("goodString3"), QStringLiteral("27"));
+
+ QVERIFY(o->property("goodString4").value<QString>().startsWith("QObject_QML_"_L1));
+ QVERIFY(o->property("badString2").value<QString>().startsWith("QObject_QML_"_L1));
+
+ QCOMPARE(o->property("badInt"), 5);
+ QCOMPARE(o->property("goodInt1"), 5);
+ QCOMPARE(o->property("goodInt2"), 5);
+ QCOMPARE(o->property("goodInt3"), 5);
+}
+
+void tst_qqmllanguage::objectAndGadgetMethodCallsAcceptThisObject()
+{
+ QQmlEngine engine;
+ 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: "
+ "Cannot call method QtObject::rect on QObject_QML_"));
+ QTest::ignoreMessage(
+ QtWarningMsg, QRegularExpression(
+ "objectAndGadgetMethodCallsAcceptThisObject.qml:20: Error: "
+ "Cannot call method BaseValueType::report on QObject_QML_"));
+ QTest::ignoreMessage(
+ QtWarningMsg, QRegularExpression(
+ "objectAndGadgetMethodCallsAcceptThisObject.qml:26: Error: "
+ "Cannot call method toString on QRectF"));
+ QTest::ignoreMessage(
+ QtWarningMsg, QRegularExpression(
+ "objectAndGadgetMethodCallsAcceptThisObject.qml:29: Error: "
+ "Cannot call method OriginalSingleton::mm on QObject_QML_"));
+
+
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(!o.isNull());
+
+ QCOMPARE(o->property("badRect"), QRectF());
+ QCOMPARE(o->property("goodRect1"), QRectF(1, 2, 3, 4));
+ QCOMPARE(o->property("goodRect2"), QRectF(1, 2, 3, 4));
+
+ QCOMPARE(o->property("badString"), QString());
+ QCOMPARE(o->property("goodString1"), QStringLiteral("27"));
+ QCOMPARE(o->property("goodString2"), QStringLiteral("27"));
+ QCOMPARE(o->property("goodString3"), QStringLiteral("28"));
+
+ QVERIFY(o->property("goodString4").value<QString>().startsWith("Qt("_L1));
+ QCOMPARE(o->property("badString2"), QString());
+
+ QCOMPARE(o->property("badInt"), 0);
+ QCOMPARE(o->property("goodInt1"), 5);
+ QCOMPARE(o->property("goodInt2"), 5);
+ 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,
+ 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());
+ 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::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);
+}
+
QTEST_MAIN(tst_qqmllanguage)
#include "tst_qqmllanguage.moc"