diff options
Diffstat (limited to 'tests/auto/qml/qqmllanguage')
30 files changed, 676 insertions, 16 deletions
diff --git a/tests/auto/qml/qqmllanguage/CMakeLists.txt b/tests/auto/qml/qqmllanguage/CMakeLists.txt index e7fa05d23e..08d747b818 100644 --- a/tests/auto/qml/qqmllanguage/CMakeLists.txt +++ b/tests/auto/qml/qqmllanguage/CMakeLists.txt @@ -12,18 +12,15 @@ list(APPEND test_data ${test_data_glob}) qt_internal_add_test(tst_qqmllanguage SOURCES - ../../shared/testhttpserver.cpp ../../shared/testhttpserver.h - ../../shared/util.cpp ../../shared/util.h testtypes.cpp testtypes.h tst_qqmllanguage.cpp - INCLUDE_DIRECTORIES - ../../shared PUBLIC_LIBRARIES Qt::CorePrivate Qt::Gui Qt::GuiPrivate Qt::Network Qt::QmlPrivate + Qt::QuickTestUtilsPrivate TESTDATA ${test_data} ) @@ -67,4 +64,4 @@ set_target_properties(tst_qqmllanguage PROPERTIES QT_QML_MODULE_URI StaticTest ) -qt6_qml_type_registration(tst_qqmllanguage) +_qt_internal_qml_type_registration(tst_qqmllanguage) diff --git a/tests/auto/qml/qqmllanguage/data/Broken.qml b/tests/auto/qml/qqmllanguage/data/Broken.qml new file mode 100644 index 0000000000..e1b61f31f4 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/Broken.qml @@ -0,0 +1,5 @@ +import QtQml + +QtObject { + notThere: 5 +} diff --git a/tests/auto/qml/qqmllanguage/data/ComponentType.qml b/tests/auto/qml/qqmllanguage/data/ComponentType.qml new file mode 100644 index 0000000000..e8addde1c4 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/ComponentType.qml @@ -0,0 +1,8 @@ +import QtQml + +Component { + id: componentRoot + QtObject { + objectName: "enclosed" + } +} diff --git a/tests/auto/qml/qqmllanguage/data/GroupFailureInner.qml b/tests/auto/qml/qqmllanguage/data/GroupFailureInner.qml new file mode 100644 index 0000000000..7972cc9683 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/GroupFailureInner.qml @@ -0,0 +1,2 @@ +import QtQml +QtObject { property url u } diff --git a/tests/auto/qml/qqmllanguage/data/GroupFailureOuter.qml b/tests/auto/qml/qqmllanguage/data/GroupFailureOuter.qml new file mode 100644 index 0000000000..2a34a29789 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/GroupFailureOuter.qml @@ -0,0 +1,5 @@ +import QtQml + +QtObject { + property GroupFailureInner b +} diff --git a/tests/auto/qml/qqmllanguage/data/MyRectangle.qml b/tests/auto/qml/qqmllanguage/data/MyRectangle.qml new file mode 100644 index 0000000000..4d5e7c6c8d --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/MyRectangle.qml @@ -0,0 +1,10 @@ +import QtQuick + +Item { + property alias rectangle1AnchorsleftMargin: rectangle1.anchors.leftMargin + + Rectangle { + id: rectangle1 + anchors.leftMargin: 250 + } +} diff --git a/tests/auto/qml/qqmllanguage/data/SignalInlineComponentArg.qml b/tests/auto/qml/qqmllanguage/data/SignalInlineComponentArg.qml new file mode 100644 index 0000000000..0424ac1534 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/SignalInlineComponentArg.qml @@ -0,0 +1,21 @@ +import QtQuick + +Item { + component Abc: Item { + property string success + } + + signal canYouFeelIt(arg1: Abc) + property Abc someAbc: Abc { + success: "Signal was called" + } + property string success: "Signal not called yet" + + Component.onCompleted: { + canYouFeelIt(someAbc); + } + + onCanYouFeelIt: (arg) => { + success = arg.success + } +} diff --git a/tests/auto/qml/qqmllanguage/data/alias.15.qml b/tests/auto/qml/qqmllanguage/data/alias.15.qml index 5f3de9c83e..7e362d8823 100644 --- a/tests/auto/qml/qqmllanguage/data/alias.15.qml +++ b/tests/auto/qml/qqmllanguage/data/alias.15.qml @@ -9,4 +9,25 @@ Item { Item { id: symbol } + + Rectangle { + id: txtElevationValue + + property Rectangle background: Rectangle { } + + state: "ValidatorInvalid" + + states: [ + State { + name: "ValidatorInvalid" + PropertyChanges { + target: txtElevationValue + background.border.color: "red" // this line caused the segfault in qtbug107795 + } + }, + State { + name: "ValidatorAcceptable" + } + ] + } } diff --git a/tests/auto/qml/qqmllanguage/data/alias.15a.qml b/tests/auto/qml/qqmllanguage/data/alias.15a.qml new file mode 100644 index 0000000000..ba8097c997 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/alias.15a.qml @@ -0,0 +1,12 @@ +import QtQuick 2.15 + +Item { + id: root + + property alias symbol: symbol + symbol.layer.enabled: true + + Item { + id: symbol + } +} diff --git a/tests/auto/qml/qqmllanguage/data/alias.19.qml b/tests/auto/qml/qqmllanguage/data/alias.19.qml new file mode 100644 index 0000000000..a96c0c694d --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/alias.19.qml @@ -0,0 +1,11 @@ +import QtQuick + +Item { + id: myThing + width: 1920 + + MyRectangle { + rectangle1AnchorsleftMargin: myThing.width / 2 + Component.onCompleted: myThing.height = rectangle1AnchorsleftMargin + } +} diff --git a/tests/auto/qml/qqmllanguage/data/ambiguousBinding/TestCase.qml b/tests/auto/qml/qqmllanguage/data/ambiguousBinding/TestCase.qml new file mode 100644 index 0000000000..c76d2b679e --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/ambiguousBinding/TestCase.qml @@ -0,0 +1,6 @@ +import QtQml 2.15 +import QtTest 1.0 + +QtObject { + component Comp: QtObject {} +} diff --git a/tests/auto/qml/qqmllanguage/data/ambiguousBinding/ambiguousContainingType.qml b/tests/auto/qml/qqmllanguage/data/ambiguousBinding/ambiguousContainingType.qml new file mode 100644 index 0000000000..765dc91fe1 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/ambiguousBinding/ambiguousContainingType.qml @@ -0,0 +1,3 @@ +import QtQml 2.15 + +TestCase {} diff --git a/tests/auto/qml/qqmllanguage/data/asBroken.qml b/tests/auto/qml/qqmllanguage/data/asBroken.qml new file mode 100644 index 0000000000..381a012df6 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/asBroken.qml @@ -0,0 +1,6 @@ +import QtQml + +QtObject { + id: self + property var selfAsBroken: self as Broken +} diff --git a/tests/auto/qml/qqmllanguage/data/badGroupedProperty.qml b/tests/auto/qml/qqmllanguage/data/badGroupedProperty.qml new file mode 100644 index 0000000000..1b8ba61725 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/badGroupedProperty.qml @@ -0,0 +1,10 @@ +import QtQml + +QtObject { + id: testItem + property rect rect + onComplete { + rect.x: 2 + rect.width: 22 + } +} diff --git a/tests/auto/qml/qqmllanguage/data/bindingAliasToComponentUrl.qml b/tests/auto/qml/qqmllanguage/data/bindingAliasToComponentUrl.qml new file mode 100644 index 0000000000..44fbd03354 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/bindingAliasToComponentUrl.qml @@ -0,0 +1,10 @@ +import QtQuick +Item { + id: root + Component { + id: accessibleNormal + Item {} + } + property alias accessibleNormalUrl: accessibleNormal.url + property url urlClone: root.accessibleNormalUrl // crashes qml utility +} diff --git a/tests/auto/qml/qqmllanguage/data/bindingAliasToComponentUrl2.qml b/tests/auto/qml/qqmllanguage/data/bindingAliasToComponentUrl2.qml new file mode 100644 index 0000000000..cfdec5e39b --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/bindingAliasToComponentUrl2.qml @@ -0,0 +1,11 @@ +import QtQuick +Item { + id: root + Component { + id: accessibleNormal + ComponentType { + id: inaccessibleNormal + } + } + property alias accessibleNormalProgress: accessibleNormal.progress +} diff --git a/tests/auto/qml/qqmllanguage/data/componentMix.qml b/tests/auto/qml/qqmllanguage/data/componentMix.qml new file mode 100644 index 0000000000..0edc997484 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/componentMix.qml @@ -0,0 +1,18 @@ +import QtQml + +QtObject { + component View: QtObject { + default property Component delegate + } + + component Things : QtObject { + property QtObject view: View { delegate: QtObject {} } + } + + component Delegated : View { + delegate: QtObject {} + } + + property Things things: Things {} + property Delegated delegated: Delegated {} +} diff --git a/tests/auto/qml/qqmllanguage/data/foreignExtended.qml b/tests/auto/qml/qqmllanguage/data/foreignExtended.qml index 4863e0d567..b01af6d229 100644 --- a/tests/auto/qml/qqmllanguage/data/foreignExtended.qml +++ b/tests/auto/qml/qqmllanguage/data/foreignExtended.qml @@ -5,12 +5,17 @@ QtObject { property Foreign foreign: Foreign { objectName: "foreign" } - property Extended extended: Extended {} + property Extended extended: Extended { + objectName: "extended" + property int changeCount: 0 + onExtensionChanged: ++changeCount + } property ForeignExtended foreignExtended: ForeignExtended { objectName: "foreignExtended" } property int extendedBase: extended.base + property int extendedChangeCount: extended.changeCount property int extendedInvokable: extended.invokable() property int extendedSlot: extended.slot() diff --git a/tests/auto/qml/qqmllanguage/data/fuzzed.1.errors.txt b/tests/auto/qml/qqmllanguage/data/fuzzed.1.errors.txt index e399799fe9..758be7feae 100644 --- a/tests/auto/qml/qqmllanguage/data/fuzzed.1.errors.txt +++ b/tests/auto/qml/qqmllanguage/data/fuzzed.1.errors.txt @@ -1,2 +1 @@ -2:8:Cannot assign to non-existent property "_G" - +2:11:Non-existent attached object diff --git a/tests/auto/qml/qqmllanguage/data/groupFailure.qml b/tests/auto/qml/qqmllanguage/data/groupFailure.qml new file mode 100644 index 0000000000..e8f8999482 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/groupFailure.qml @@ -0,0 +1,5 @@ +import QtQml + +GroupFailureOuter { + b.u: null +} diff --git a/tests/auto/qml/qqmllanguage/data/jittedAsCast.qml b/tests/auto/qml/qqmllanguage/data/jittedAsCast.qml new file mode 100644 index 0000000000..e1798a5d35 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/jittedAsCast.qml @@ -0,0 +1,15 @@ +import QtQml + +QtObject { + property QtObject obj: Timer { + interval: 1 + running: true + repeat: true + onTriggered: { + if (++interval === 10) + running = false + } + } + property bool running: (obj as Timer).running + property int interval: (obj as Timer).interval +} diff --git a/tests/auto/qml/qqmllanguage/data/sameNameAliasProperty.qml b/tests/auto/qml/qqmllanguage/data/sameNameAliasProperty.qml new file mode 100644 index 0000000000..288fd618a6 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/sameNameAliasProperty.qml @@ -0,0 +1,7 @@ +import QtQml 2.15 + +QtObject { + id: root + property int a + property alias a: root.a +} diff --git a/tests/auto/qml/qqmllanguage/data/sameNamePropertyAlias.qml b/tests/auto/qml/qqmllanguage/data/sameNamePropertyAlias.qml new file mode 100644 index 0000000000..bb26ba4396 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/sameNamePropertyAlias.qml @@ -0,0 +1,7 @@ +import QtQml 2.15 + +QtObject { + id: root + property alias a: root.a + property int a +} diff --git a/tests/auto/qml/qqmllanguage/data/signalInlineComponentArg1.qml b/tests/auto/qml/qqmllanguage/data/signalInlineComponentArg1.qml new file mode 100644 index 0000000000..e20710edd9 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/signalInlineComponentArg1.qml @@ -0,0 +1,30 @@ +import QtQuick + +// this file performs two tests: first, using a signal with a inline component from another file +// and second, calling the signal from another file using an inline component from another file + +Item { + signal canYouFeelIt(arg1:SignalInlineComponentArg.Abc) + + property SignalInlineComponentArg.Abc someAbc: SignalInlineComponentArg.Abc { + success: "Own signal was called with component from another file" + } + + property SignalInlineComponentArg fromAnotherFile: SignalInlineComponentArg {} + + // success of own signal call with parameter from another file + property string successFromOwnSignal: "Signal not called yet" + // makes it easier to test + property string successFromSignalFromFile: fromAnotherFile.success + + Component.onCompleted: { + canYouFeelIt(someAbc); + fromAnotherFile.someAbc.success = "Signal was called from another file" + fromAnotherFile.canYouFeelIt(fromAnotherFile.someAbc) + } + + onCanYouFeelIt: (arg) => { + successFromOwnSignal = arg.success + } +} + diff --git a/tests/auto/qml/qqmllanguage/data/thisInArrow.qml b/tests/auto/qml/qqmllanguage/data/thisInArrow.qml new file mode 100644 index 0000000000..7dd19782e6 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/thisInArrow.qml @@ -0,0 +1,39 @@ +import QtQml + +QtObject { + id: root + + property int width: 43 + Component.onCompleted: () => { console.log(this.width); } + + property var arrow: () => { return this; } + property var func: function() { return this; } + + property QtObject child: QtObject { + property var aa; + property var ff; + + Component.onCompleted: { + root.arrowResult = root.arrow(); + root.funcResult = root.func(); + + var a = root.arrow; + root.aResult = a(); + var f = root.func; + root.fResult = f(); + + aa = a; + root.aaResult = aa(); + + ff = f; + root.ffResult = ff(); + } + } + + property var arrowResult + property var funcResult + property var aResult + property var fResult + property var aaResult + property var ffResult +} diff --git a/tests/auto/qml/qqmllanguage/data/uncreatableAttached.qml b/tests/auto/qml/qqmllanguage/data/uncreatableAttached.qml new file mode 100644 index 0000000000..4a861ea075 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/uncreatableAttached.qml @@ -0,0 +1,8 @@ +import QtQml +import ABC + +QtObject { + id: testItem + objectName: "00000000000000000000" + ItemAttached.attachedName: "111111111" +} diff --git a/tests/auto/qml/qqmllanguage/data/variantListConversion.qml b/tests/auto/qml/qqmllanguage/data/variantListConversion.qml index 334bf17393..19760a64ee 100644 --- a/tests/auto/qml/qqmllanguage/data/variantListConversion.qml +++ b/tests/auto/qml/qqmllanguage/data/variantListConversion.qml @@ -1,7 +1,8 @@ import Test +import QtQml Foo { a.a: 12 b.a: 13 - fooProperty: [a, b] + fooProperty: [a, b, Component] } diff --git a/tests/auto/qml/qqmllanguage/testtypes.cpp b/tests/auto/qml/qqmllanguage/testtypes.cpp index fd541fd36a..92e24bc332 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.cpp +++ b/tests/auto/qml/qqmllanguage/testtypes.cpp @@ -177,7 +177,7 @@ void EnumSupportingCustomParser::verifyBindings(const QQmlRefPointer<QV4::Execut return; } - if (binding->type != QV4::CompiledData::Binding::Type_Script) { + if (binding->type() != QV4::CompiledData::Binding::Type_Script) { error(binding, QStringLiteral("Custom parser invoked with the wrong property value. Expected script that evaluates to enum")); return; } diff --git a/tests/auto/qml/qqmllanguage/testtypes.h b/tests/auto/qml/qqmllanguage/testtypes.h index 2ffc324cae..e43424c9ca 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.h +++ b/tests/auto/qml/qqmllanguage/testtypes.h @@ -1464,13 +1464,23 @@ public: class Extension : public QObject { Q_OBJECT - Q_PROPERTY(int extension READ extension CONSTANT) + Q_PROPERTY(int extension READ extension WRITE setExtension NOTIFY extensionChanged FINAL) public: Extension(QObject *parent = nullptr) : QObject(parent) {} - int extension() const { return 42; } + int extension() const { return ext; } + void setExtension(int e) { + if (e != ext) { + ext = e; + emit extensionChanged(); + } + } Q_INVOKABLE int invokable() { return 123; } +Q_SIGNALS: + void extensionChanged(); public slots: int slot() { return 456; } +private: + int ext = 42; }; class Extended : public QObject @@ -1566,6 +1576,33 @@ public: int own() const { return 93; } }; +class ExtendedNamespaceByObject : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_EXTENDED_NAMESPACE(Extension) + + Q_PROPERTY(QString dummy READ dummy CONSTANT) + Q_PROPERTY(int extension READ extension WRITE setExtension NOTIFY extensionChanged) + + int m_ext = 0; + +public: + ExtendedNamespaceByObject(QObject *parent = nullptr) : QObject(parent) {} + QString dummy() const { return QStringLiteral("dummy"); } + int extension() const { return m_ext; } + void setExtension(int e) + { + if (e != m_ext) { + m_ext = e; + Q_EMIT extensionChanged(); + } + } + +Q_SIGNALS: + void extensionChanged(); +}; + class FactorySingleton : public QObject { Q_OBJECT @@ -1773,6 +1810,60 @@ private: QVariantList mFooProperty; }; +class ItemAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString attachedName READ attachedName WRITE setAttachedName NOTIFY attachedNameChanged) + QML_ELEMENT + QML_ATTACHED(ItemAttached) +public: + ItemAttached(QObject *parent = nullptr) : QObject(parent) {} + + QString attachedName() const { return m_name; } + void setAttachedName(const QString &name) + { + if (name != m_name) { + m_name = name; + emit attachedNameChanged(); + } + } + + static ItemAttached *qmlAttachedProperties(QObject *object) + { + if (object->objectName() != QLatin1String("foo")) { + qWarning("Only foo can have ItemAttached!"); + return nullptr; + } + + return new ItemAttached(object); + } + +signals: + void attachedNameChanged(); + +private: + QString m_name; +}; + +class BindableOnly : public QObject +{ + Q_OBJECT + Q_PROPERTY(QByteArray data READ data WRITE setData BINDABLE dataBindable FINAL) + QML_ELEMENT +public: + BindableOnly(QObject *parent = nullptr) + : QObject(parent) + {} + + QBindable<QByteArray> dataBindable() { return QBindable<QByteArray>(&m_data); } + + QByteArray data() const { return m_data.value(); } + void setData(const QByteArray &newData) { m_data.setValue(newData); } + +private: + QProperty<QByteArray> m_data; +}; + void registerTypes(); #endif // TESTTYPES_H diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index d09f903ccb..047df2f6d0 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -50,11 +50,12 @@ #include <private/qqmlvmemetaobject_p.h> #include <private/qqmlcomponent_p.h> #include <private/qqmltype_p_p.h> +#include <private/qqmlcomponentattached_p.h> +#include <private/qv4debugging_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> @@ -85,6 +86,9 @@ class tst_qqmllanguage : public QQmlDataTest { Q_OBJECT +public: + tst_qqmllanguage(); + private slots: void initTestCase() override; void cleanupTestCase(); @@ -242,6 +246,8 @@ private slots: void deepProperty(); + void groupAssignmentFailure(); + void compositeSingletonProperties(); void compositeSingletonSameEngine(); void compositeSingletonDifferentEngine(); @@ -345,6 +351,7 @@ private slots: void checkURLtoURLObject(); void registerValueTypes(); void extendedNamespace(); + void extendedNamespaceByObject(); void factorySingleton(); void extendedSingleton(); void qtbug_85932(); @@ -363,7 +370,23 @@ private slots: void propertyObserverOnReadonly(); + void propertyAndAliasMustHaveDistinctNames_data(); + void propertyAndAliasMustHaveDistinctNames(); + void variantListConversion(); + void thisInArrowFunction(); + + void jittedAsCast(); + + void ambiguousContainingType(); + void objectAsBroken(); + void componentMix(); + void uncreatableAttached(); + + void bindableOnly(); + void badGroupedProperty(); + void bindingAliasToComponentUrl(); + void signalInlineComponentArg(); private: QQmlEngine engine; @@ -2141,6 +2164,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. @@ -2218,6 +2257,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 @@ -3730,6 +3779,11 @@ void tst_qqmllanguage::uncreatableTypesAsProperties() QVERIFY(!object.isNull()); } +tst_qqmllanguage::tst_qqmllanguage() + : QQmlDataTest(QT_QMLTEST_DATADIR) +{ +} + void tst_qqmllanguage::initTestCase() { QQmlDataTest::initTestCase(); @@ -4355,6 +4409,17 @@ void tst_qqmllanguage::deepProperty() QCOMPARE(font.family(), QStringLiteral("test")); } +void tst_qqmllanguage::groupAssignmentFailure() +{ + auto ep = std::make_unique<QQmlEngine>(); + 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); + 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 @@ -5502,9 +5567,20 @@ 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())); + QCOMPARE(o->property("extendedBase").toInt(), 43); QCOMPARE(o->property("extendedExtension").toInt(), 42); QCOMPARE(o->property("foreignExtendedExtension").toInt(), 42); + + QCOMPARE(extensionChangedSpy.count(), 0); + extended->setProperty("extension", 44); + QCOMPARE(extensionChangedSpy.count(), 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); @@ -6096,6 +6172,22 @@ 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::factorySingleton() { QQmlEngine engine; @@ -6294,8 +6386,23 @@ void tst_qqmllanguage::bareInlineComponent() QVERIFY(tab1Found); } +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 { } +}; + 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()))); @@ -6355,6 +6462,26 @@ void tst_qqmllanguage::propertyObserverOnReadonly() QCOMPARE(o->property("height").toDouble(), 54.2); } +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::variantListConversion() { QQmlEngine engine; @@ -6365,11 +6492,181 @@ void tst_qqmllanguage::variantListConversion() Foo *foo = qobject_cast<Foo *>(o.data()); QVERIFY(foo); const QVariantList list = foo->getList(); - QCOMPARE(list.length(), 2); + QCOMPARE(list.length(), 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::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::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'"))); +} + +void tst_qqmllanguage::bindableOnly() +{ + qmlRegisterTypesAndRevisions<BindableOnly>("ABC", 1); + QQmlEngine engine; + + QQmlComponent c(&engine); + c.setData("import ABC\nBindableOnly {\n" + " data: \"sc\" + \"ore\"\n" + " objectName: data\n" + "}", QUrl(u"bindableOnly.qml"_qs)); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QCOMPARE(o->property("data").value<QByteArray>(), QByteArray("score")); + QCOMPARE(o->objectName(), QStringLiteral("score")); +} + +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::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::signalInlineComponentArg() +{ + QQmlEngine engine; + { + QQmlComponent component(&engine, testFileUrl("SignalInlineComponentArg.qml")); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> object(component.create()); + + QCOMPARE(object->property("success"), QStringLiteral("Signal was called")); + } + { + QQmlComponent component(&engine, testFileUrl("signalInlineComponentArg1.qml")); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> object(component.create()); + + QCOMPARE(object->property("successFromOwnSignal"), + QStringLiteral("Own signal was called with component from another file")); + QCOMPARE(object->property("successFromSignalFromFile"), + QStringLiteral("Signal was called from another file")); + } } QTEST_MAIN(tst_qqmllanguage) |