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