diff options
Diffstat (limited to 'tests/auto/qml/qqmlecmascript')
28 files changed, 888 insertions, 118 deletions
diff --git a/tests/auto/qml/qqmlecmascript/CMakeLists.txt b/tests/auto/qml/qqmlecmascript/CMakeLists.txt index f4b4169c82..12cab47a36 100644 --- a/tests/auto/qml/qqmlecmascript/CMakeLists.txt +++ b/tests/auto/qml/qqmlecmascript/CMakeLists.txt @@ -7,6 +7,12 @@ ## tst_qqmlecmascript Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qqmlecmascript LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + # Collect test data file(GLOB_RECURSE test_data_glob RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} @@ -24,6 +30,7 @@ qt_internal_add_test(tst_qqmlecmascript Qt::Network Qt::QmlPrivate Qt::QuickTestUtilsPrivate + Qt::QuickPrivate TESTDATA ${test_data} ) diff --git a/tests/auto/qml/qqmlecmascript/data/AssignListPropertyByIndexOnGadget.qml b/tests/auto/qml/qqmlecmascript/data/AssignListPropertyByIndexOnGadget.qml new file mode 100644 index 0000000000..f8ee283f1e --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/AssignListPropertyByIndexOnGadget.qml @@ -0,0 +1,20 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQml +import Qt.test + +QtObject { + property listPropertyAssignment_Gadget gadget + property ListPropertyAssignment_Object object: ListPropertyAssignment_Object { } + + Component.onCompleted: { + gadget.gadgetStringList = ["Element1", "Element2", "Element3"] + gadget.gadgetVariantList = [1, "foo", null, true] + object.qobjectStringList = ["Element1", "Element2", "Element3"] + + gadget.gadgetStringList[0] = "Completely new Element" + gadget.gadgetVariantList[0] = "Completely new Element" + object.qobjectStringList[0] = "Completely new Element" + } +} diff --git a/tests/auto/qml/qqmlecmascript/data/PropertyVarCircularComponent4.qml b/tests/auto/qml/qqmlecmascript/data/PropertyVarCircularComponent4.qml index 9273a52f54..ff7fe4434c 100644 --- a/tests/auto/qml/qqmlecmascript/data/PropertyVarCircularComponent4.qml +++ b/tests/auto/qml/qqmlecmascript/data/PropertyVarCircularComponent4.qml @@ -22,7 +22,6 @@ Rectangle { if (component.status == Component.Ready) { text.vp = component.createObject(null); // has JavaScript ownership } - gc(); } } } diff --git a/tests/auto/qml/qqmlecmascript/data/PropertyVarOwnershipComponent.qml b/tests/auto/qml/qqmlecmascript/data/PropertyVarOwnershipComponent.qml index f4307081c5..4e8da872f5 100644 --- a/tests/auto/qml/qqmlecmascript/data/PropertyVarOwnershipComponent.qml +++ b/tests/auto/qml/qqmlecmascript/data/PropertyVarOwnershipComponent.qml @@ -25,7 +25,6 @@ Rectangle { if (component.status == Component.Ready) { textTwo.vp = component.createObject(null); // has JavaScript ownership } - gc(); } function deassignVp() { diff --git a/tests/auto/qml/qqmlecmascript/data/changeslots/propertyChangeSlotErrors.3.qml b/tests/auto/qml/qqmlecmascript/data/changeslots/propertyChangeSlotErrors.3.qml deleted file mode 100644 index d611e0fe30..0000000000 --- a/tests/auto/qml/qqmlecmascript/data/changeslots/propertyChangeSlotErrors.3.qml +++ /dev/null @@ -1,12 +0,0 @@ -import QtQuick 2.0 - -Item { - property int changeCount: 0 - - // invalid property name - we don't allow $ - property bool $nameWithDollarsign: false - - on$NameWithDollarsignChanged: { - changeCount = changeCount + 4; - } -} diff --git a/tests/auto/qml/qqmlecmascript/data/changeslots/propertyChangeSlotErrors.4.qml b/tests/auto/qml/qqmlecmascript/data/changeslots/propertyChangeSlotErrors.4.qml deleted file mode 100644 index a6862517c6..0000000000 --- a/tests/auto/qml/qqmlecmascript/data/changeslots/propertyChangeSlotErrors.4.qml +++ /dev/null @@ -1,12 +0,0 @@ -import QtQuick 2.0 - -Item { - property int changeCount: 0 - - property bool _6nameWithUnderscoreNumber: false - - // invalid property name - the first character after an underscore must be a letter - on_6NameWithUnderscoreNumberChanged: { - changeCount = changeCount + 3; - } -} diff --git a/tests/auto/qml/qqmlecmascript/data/changeslots/propertyChangeSlots.qml b/tests/auto/qml/qqmlecmascript/data/changeslots/propertyChangeSlots.qml index f91fb71f1f..9a3141e15a 100644 --- a/tests/auto/qml/qqmlecmascript/data/changeslots/propertyChangeSlots.qml +++ b/tests/auto/qml/qqmlecmascript/data/changeslots/propertyChangeSlots.qml @@ -6,6 +6,8 @@ Item { property bool normalName: false property bool _nameWithUnderscore: false property bool ____nameWithUnderscores: false + property bool _6nameWithUnderscoreNumber: false + property bool $nameWithDollarsign: false onNormalNameChanged: { changeCount = changeCount + 1; @@ -19,9 +21,20 @@ Item { changeCount = changeCount + 3; } + on$NameWithDollarsignChanged: { + changeCount = changeCount + 4; + } + + on_6NameWithUnderscoreNumberChanged: { + changeCount = changeCount + 5; + } + Component.onCompleted: { normalName = true; _nameWithUnderscore = true; ____nameWithUnderscores = true; + $nameWithDollarsign = true; + _6nameWithUnderscoreNumber = true; } + } diff --git a/tests/auto/qml/qqmlecmascript/data/date.qml b/tests/auto/qml/qqmlecmascript/data/date.qml index 8e190b1f8f..33644b604d 100644 --- a/tests/auto/qml/qqmlecmascript/data/date.qml +++ b/tests/auto/qml/qqmlecmascript/data/date.qml @@ -26,8 +26,12 @@ Item { function check_value(date, tag, qdt) { var result = true; + if (isNaN(date)) { + console.warn("Invalid Date"); + return false; + } if (date.getFullYear() != 2014) { - console.warn("Wrong year (" + tag + "):", date.getFullYear(), "!= 2014") + console.warn("Wrong year (" + tag + "):", date.getFullYear(), "!= 2014"); result = false; } // July; JS's months are Jan 0 to 11 Dec, vs. Qt's 1 to 12. diff --git a/tests/auto/qml/qqmlecmascript/data/frozenQObject3.qml b/tests/auto/qml/qqmlecmascript/data/frozenQObject3.qml new file mode 100644 index 0000000000..51c321684e --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/frozenQObject3.qml @@ -0,0 +1,30 @@ +import QtQml +import test + +QtObject { + id: root + + property FrozenObjects a: FrozenObjects { objectName: "a" } + property FrozenObjects b: FrozenObjects { objectName: "b" } + + // Create wrappers and immediately discard them + objectName: a.getConst().objectName + "/" + b.getNonConst().objectName + + // Create a non-const wrapper and retain it + property var objNonConst: a.getNonConst() + + // Create a const wrapper and retain it + property var objConst: b.getConst() + + property int gcs: 0 + + property Timer t: Timer { + interval: 1 + running: true + repeat: true + onTriggered: { + gc(); + ++root.gcs; + } + } +} diff --git a/tests/auto/qml/qqmlecmascript/data/getThis.qml b/tests/auto/qml/qqmlecmascript/data/getThis.qml index db9854e872..90c51caf4d 100644 --- a/tests/auto/qml/qqmlecmascript/data/getThis.qml +++ b/tests/auto/qml/qqmlecmascript/data/getThis.qml @@ -1,5 +1,5 @@ // Copyright (C) 2019 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 import QtQml 2.12 diff --git a/tests/auto/qml/qqmlecmascript/data/lookupsDoNotBypassProxy.qml b/tests/auto/qml/qqmlecmascript/data/lookupsDoNotBypassProxy.qml new file mode 100644 index 0000000000..321bd21ad8 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/lookupsDoNotBypassProxy.qml @@ -0,0 +1,29 @@ +import QtQml + +QtObject { + function test_proxy() { + let base = { + id: 'baseid', + name: 'basename', + length: 42 + }; + + let handler = { + get: function (ao, prop) { + return Reflect.get(ao, prop); + } + }; + + let r = new Proxy(base, handler); + let validCount = 0; + if (r.id === base.id) + ++validCount; + if (r.length === base.length) + ++validCount; + if (r.name === base.name) + ++validCount; + return validCount; + } + + property int result: test_proxy() +} diff --git a/tests/auto/qml/qqmlecmascript/data/methodCallOnDerivedSingleton.qml b/tests/auto/qml/qqmlecmascript/data/methodCallOnDerivedSingleton.qml new file mode 100644 index 0000000000..9d2ee433fd --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/methodCallOnDerivedSingleton.qml @@ -0,0 +1,6 @@ +import Qt.test +import QtQml + +QtObject { + Component.onCompleted: SingletonInheritanceTest.trackPage("test", {x: 42}) +} diff --git a/tests/auto/qml/qqmlecmascript/data/propertyVar.reparent.qml b/tests/auto/qml/qqmlecmascript/data/propertyVar.reparent.qml index 6f5094de27..5f017c4880 100644 --- a/tests/auto/qml/qqmlecmascript/data/propertyVar.reparent.qml +++ b/tests/auto/qml/qqmlecmascript/data/propertyVar.reparent.qml @@ -16,7 +16,6 @@ Item { function assignVarProp() { vp = constructGarbage(); - gc(); } function deassignVarProp() { diff --git a/tests/auto/qml/qqmlecmascript/data/propertyVarImplicitOwnership.qml b/tests/auto/qml/qqmlecmascript/data/propertyVarImplicitOwnership.qml index ecc4892334..144fcc8a93 100644 --- a/tests/auto/qml/qqmlecmascript/data/propertyVarImplicitOwnership.qml +++ b/tests/auto/qml/qqmlecmascript/data/propertyVarImplicitOwnership.qml @@ -16,7 +16,6 @@ Item { function assignCircular() { vp = constructGarbage(); - gc(); } function deassignCircular() { diff --git a/tests/auto/qml/qqmlecmascript/data/qmlTypeWrapperArgs3.qml b/tests/auto/qml/qqmlecmascript/data/qmlTypeWrapperArgs3.qml new file mode 100644 index 0000000000..a50c8e0f46 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/qmlTypeWrapperArgs3.qml @@ -0,0 +1,12 @@ +import QtQml +import Qt.test.singletonWithEnum + +QtObject { + id: root + required property QtObject invokableObject + + Component.onCompleted: { + root.invokableObject.method_typeWrapper(Component) + root.invokableObject.method_typeWrapper(SingletonWithEnum) + } +} diff --git a/tests/auto/qml/qqmlecmascript/data/qpropertyResetCorrectlyLinked.qml b/tests/auto/qml/qqmlecmascript/data/qpropertyResetCorrectlyLinked.qml new file mode 100644 index 0000000000..490fec2dc8 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/qpropertyResetCorrectlyLinked.qml @@ -0,0 +1,8 @@ +import QtQuick + +Item { + property var val: undefined + property var observes: width + width: val + implicitWidth: 200 +} diff --git a/tests/auto/qml/qqmlecmascript/data/resetGadget.qml b/tests/auto/qml/qqmlecmascript/data/resetGadget.qml new file mode 100644 index 0000000000..2bc196da34 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/resetGadget.qml @@ -0,0 +1,9 @@ +import Qt.test + +ResettableGadgetHolder { + id: root + property bool trigger: false + onTriggerChanged: { + root.g.value = undefined + } +} diff --git a/tests/auto/qml/qqmlecmascript/data/restoreObserverAfterReset.qml b/tests/auto/qml/qqmlecmascript/data/restoreObserverAfterReset.qml new file mode 100644 index 0000000000..2933d9b4d5 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/restoreObserverAfterReset.qml @@ -0,0 +1,20 @@ +import QtQuick + +Item { + height: undefined + implicitHeight: 30 + property int steps: 0 + + Behavior on height { + NumberAnimation { + duration: 500 + } + } + + onHeightChanged: ++steps + + Component.onCompleted: { + height = Qt.binding(() => implicitHeight); + implicitHeight = 60; + } +} diff --git a/tests/auto/qml/qqmlecmascript/data/scriptConnect.8.qml b/tests/auto/qml/qqmlecmascript/data/scriptConnect.8.qml new file mode 100644 index 0000000000..7d43aa6c05 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/scriptConnect.8.qml @@ -0,0 +1,21 @@ +import Qt.test +import QtQuick + +Item { + id: root + property int count: 0 + signal someSignal + + property Item item: Item { + id: contextItem + function test() { + count++; + } + } + + function itemDestroy() { + contextItem.destroy() + } + + Component.onCompleted: root.someSignal.connect(contextItem, contextItem.test); +} diff --git a/tests/auto/qml/qqmlecmascript/data/scriptConnect.9.qml b/tests/auto/qml/qqmlecmascript/data/scriptConnect.9.qml new file mode 100644 index 0000000000..1123edf3f7 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/scriptConnect.9.qml @@ -0,0 +1,30 @@ +import Qt.test +import QtQuick + +MyQmlObject { + id: root + property int a: 0 + + signal someSignal + + function disconnectSignal() { + root.someSignal.disconnect(other.MyQmlObject, root.test) + } + + function destroyObj() { + other.destroy() + } + + function test() { + other.MyQmlObject.value2++ + root.a = other.MyQmlObject.value2 + } + + property MyQmlObject obj + obj: MyQmlObject { + id: other + MyQmlObject.value2: 0 + } + + Component.onCompleted: root.someSignal.connect(other.MyQmlObject, root.test) +} diff --git a/tests/auto/qml/qqmlecmascript/data/scriptConnect.deletion.qml b/tests/auto/qml/qqmlecmascript/data/scriptConnect.deletion.qml new file mode 100644 index 0000000000..efbbc9fedc --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/scriptConnect.deletion.qml @@ -0,0 +1,36 @@ +import Qt.test +import QtQml + +QtObject { + id: root + + property int a: 0 + property int b: 0 + + signal someSignal + + function destroyObj() { + obj.destroy() + } + + function test() { + ++a + } + + component DestructionReceiver: QtObject { + // Has its own context and therefore can receive Component.onDestruction + } + + property QtObject obj: QtObject { + property QtObject inner: DestructionReceiver { + Component.onDestruction: { + // The outer obj is already queued for deletion. + // We don't want to see this signal delivered. + root.someSignal(); + ++root.b + } + } + } + + Component.onCompleted: someSignal.connect(obj, test) +} diff --git a/tests/auto/qml/qqmlecmascript/data/scriptConnectSingleton.qml b/tests/auto/qml/qqmlecmascript/data/scriptConnectSingleton.qml new file mode 100644 index 0000000000..f666945b33 --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/scriptConnectSingleton.qml @@ -0,0 +1,21 @@ +import QtQuick +import Test + +Item { + id: root + + property int a: 0 + signal mySignal + + function test() { + MyInheritedQmlObjectSingleton.value++ + root.a = MyInheritedQmlObjectSingleton.value + } + + function disconnectSingleton() { + root.mySignal.disconnect(MyInheritedQmlObjectSingleton, root.test) + } + + Component.onCompleted: root.mySignal.connect(MyInheritedQmlObjectSingleton, + root.test) +} diff --git a/tests/auto/qml/qqmlecmascript/data/scriptDisconnect.5.qml b/tests/auto/qml/qqmlecmascript/data/scriptDisconnect.5.qml new file mode 100644 index 0000000000..9d24fa85ae --- /dev/null +++ b/tests/auto/qml/qqmlecmascript/data/scriptDisconnect.5.qml @@ -0,0 +1,19 @@ +import Qt.test +import QtQuick + +Item { + id: root + property int count: 0 + signal someSignal + signal disconnectSignal + + property Item item: Item { + id: contextItem + function test() { + count++; + } + } + + Component.onCompleted: root.someSignal.connect(contextItem, contextItem.test); + onDisconnectSignal: { root.someSignal.disconnect(contextItem, contextItem.test); } +} diff --git a/tests/auto/qml/qqmlecmascript/data/singletonTest.qml b/tests/auto/qml/qqmlecmascript/data/singletonTest.qml index b0e951b89a..f4bdcdbed3 100644 --- a/tests/auto/qml/qqmlecmascript/data/singletonTest.qml +++ b/tests/auto/qml/qqmlecmascript/data/singletonTest.qml @@ -1,5 +1,5 @@ // Copyright (C) 2013 Canonical Limited and/or its subsidiary(-ies). -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only import QtQuick 2.0 import Test 1.0 diff --git a/tests/auto/qml/qqmlecmascript/data/singletonTest2.qml b/tests/auto/qml/qqmlecmascript/data/singletonTest2.qml index 5a5bad5b8c..5d6af4d67c 100644 --- a/tests/auto/qml/qqmlecmascript/data/singletonTest2.qml +++ b/tests/auto/qml/qqmlecmascript/data/singletonTest2.qml @@ -1,5 +1,5 @@ // Copyright (C) 2013 Canonical Limited and/or its subsidiary(-ies). -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only import QtQuick 2.0 import Test 1.0 diff --git a/tests/auto/qml/qqmlecmascript/testtypes.cpp b/tests/auto/qml/qqmlecmascript/testtypes.cpp index 40f5e5cf5c..5f7713392b 100644 --- a/tests/auto/qml/qqmlecmascript/testtypes.cpp +++ b/tests/auto/qml/qqmlecmascript/testtypes.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 "testtypes.h" #ifndef QT_NO_WIDGETS # include <QWidget> @@ -8,6 +9,7 @@ #include <QQmlEngine> #include <QJSEngine> #include <QThread> +#include <QtQuickTestUtils/private/qmlutils_p.h> class BaseExtensionObject : public QObject { @@ -104,7 +106,7 @@ public: void setWidth(int) { } }; -void MyQmlObject::v8function(QQmlV4Function *function) +void MyQmlObject::v8function(QQmlV4FunctionPtr function) { function->v4engine()->throwError(QStringLiteral("Exception thrown from within QObject slot")); } @@ -393,9 +395,7 @@ void QObjectContainer::children_append(QQmlListProperty<QObject> *prop, QObject if (that->gcOnAppend) { QQmlEngine *engine = qmlEngine(that); - engine->collectGarbage(); - QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - QCoreApplication::processEvents(); + gc(*engine); } } @@ -412,7 +412,7 @@ QObject *QObjectContainer::children_at(QQmlListProperty<QObject> *prop, qsizetyp void QObjectContainer::children_clear(QQmlListProperty<QObject> *prop) { QObjectContainer *that = static_cast<QObjectContainer*>(prop->object); - foreach (QObject *c, that->dataChildren) + for (QObject *c : std::as_const(that->dataChildren)) QObject::disconnect(c, SIGNAL(destroyed(QObject*)), that, SLOT(childDestroyed(QObject*))); that->dataChildren.clear(); } @@ -436,6 +436,44 @@ void ClassWithQProperty2::callback() // Q_UNUSED(this->value.value()); // force evaluation } +ListPropertyAssignment_Gadget::ListPropertyAssignment_Gadget() { } + +QStringList ListPropertyAssignment_Gadget::gadgetStringList() const +{ + return m_gadgetStringList; +} + +void ListPropertyAssignment_Gadget::setGadgetStringList(const QStringList &list) +{ + if (m_gadgetStringList == list) + return; + m_gadgetStringList = list; +} + +QVariantList ListPropertyAssignment_Gadget::gadgetVariantList() const +{ + return m_gadgetVariantList; +} + +void ListPropertyAssignment_Gadget::setGadgetVariantList(const QVariantList &list) +{ + if (m_gadgetVariantList == list) + return; + m_gadgetVariantList = list; +} + +ListPropertyAssignment_Object::ListPropertyAssignment_Object(QObject *parent) + : QObject{ parent } { } + +void ListPropertyAssignment_Object::setQobjectStringList(const QStringList &newList) +{ + if (m_qobjectStringList == newList) + return; + m_qobjectStringList = newList; +} + +bool MetaCallInterceptor::didGetObjectDestroyedCallback = false; + void registerTypes() { qmlRegisterType<MyQmlObject>("Qt.test", 1,0, "MyQmlObjectAlias"); @@ -542,6 +580,15 @@ void registerTypes() qmlRegisterType<Receiver>("Qt.test", 1,0, "Receiver"); qmlRegisterType<Sender>("Qt.test", 1,0, "Sender"); qmlRegisterTypesAndRevisions<ReadOnlyBindable>("Qt.test", 1); + qmlRegisterTypesAndRevisions<ResettableGadgetHolder>("Qt.test", 1); + + qmlRegisterTypesAndRevisions<ListPropertyAssignment_Gadget>("Qt.test", 1); + qmlRegisterTypesAndRevisions<ListPropertyAssignment_Object>("Qt.test", 1); + + qmlRegisterTypesAndRevisions<SingletonRegistrationWrapper>("Qt.test", 1); + + qmlRegisterExtendedType<TypeWithCustomMetaObject, TypeToTriggerProxyMetaObject>( + "Qt.test", 1,0, "TypeWithCustomMetaObject"); } #include "testtypes.moc" diff --git a/tests/auto/qml/qqmlecmascript/testtypes.h b/tests/auto/qml/qqmlecmascript/testtypes.h index ff9dda36d1..cc20437fff 100644 --- a/tests/auto/qml/qqmlecmascript/testtypes.h +++ b/tests/auto/qml/qqmlecmascript/testtypes.h @@ -1,5 +1,5 @@ // Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef TESTTYPES_H #define TESTTYPES_H @@ -31,6 +31,7 @@ #include <private/qqmlengine_p.h> #include <private/qv4qobjectwrapper_p.h> +#include <private/qqmlcomponentattached_p.h> class MyQmlAttachedObject : public QObject { @@ -243,7 +244,7 @@ public slots: void myinvokable(MyQmlObject *o) { myinvokableObject = o; } void variantMethod(const QVariant &v) { m_variant = v; } void qjsvalueMethod(const QJSValue &v) { m_qjsvalue = v; } - void v8function(QQmlV4Function*); + void v8function(QQmlV4FunctionPtr); void registeredFlagMethod(Qt::MouseButtons v) { m_buttons = v; } QString slotWithReturnValue(const QString &arg) { return arg; } int resetCount() { return m_resetCount; } @@ -862,6 +863,17 @@ struct NonRegisteredType struct CompletelyUnknown; +class SingletonWithEnum : public QObject +{ + Q_OBJECT + Q_ENUMS(TestEnum) +public: + enum TestEnum { + TestValue = 42, + TestValue_MinusOne = -1 + }; +}; + class MyInvokableObject : public MyInvokableBaseObject { Q_OBJECT @@ -919,7 +931,7 @@ public: Q_INVOKABLE void method_unknown(NonRegisteredType) { invoke(28); } - Q_INVOKABLE void method_overload2(QQmlV4Function *v) + Q_INVOKABLE void method_overload2(QQmlV4FunctionPtr v) { invoke(31); QV4::Scope scope(v->v4engine()); @@ -955,12 +967,43 @@ public: invoke(40); m_actuals << f; } + Q_INVOKABLE void method_qobject(QObject *o) { invoke(41); m_actuals << QVariant::fromValue(o); } + Q_INVOKABLE QQmlComponent *someComponent() { return &m_someComponent; } + Q_INVOKABLE void method_component(QQmlComponent *c) + { + invoke(42); + m_actuals << QVariant::fromValue(c); + } + + Q_INVOKABLE MyTypeObject *someTypeObject() { return &m_someTypeObject; } + Q_INVOKABLE void method_component(MyTypeObject *c) + { + invoke(43); + m_actuals << QVariant::fromValue(c); + } + + Q_INVOKABLE void method_component(const QUrl &c) + { + invoke(44); + m_actuals << QVariant::fromValue(c); + } + + Q_INVOKABLE void method_typeWrapper(QQmlComponentAttached *attached) + { + m_actuals << QVariant::fromValue(attached); + } + + Q_INVOKABLE void method_typeWrapper(SingletonWithEnum *singleton) + { + m_actuals << QVariant::fromValue(singleton); + } + private: friend class MyInvokableBaseObject; void invoke(int idx) { if (m_invoked != -1) m_invokedError = true; m_invoked = idx;} @@ -969,6 +1012,8 @@ private: QVariantList m_actuals; QFont m_someFont; + QQmlComponent m_someComponent; + MyTypeObject m_someTypeObject; public: Q_SIGNALS: @@ -1807,17 +1852,6 @@ public: QML_DECLARE_TYPEINFO(FallbackBindingsTypeObject, QML_HAS_ATTACHED_PROPERTIES) QML_DECLARE_TYPEINFO(FallbackBindingsTypeDerived, QML_HAS_ATTACHED_PROPERTIES) -class SingletonWithEnum : public QObject -{ - Q_OBJECT - Q_ENUMS(TestEnum) -public: - enum TestEnum { - TestValue = 42, - TestValue_MinusOne = -1 - }; -}; - // Like QtObject, but with default property class QObjectContainer : public QObject { @@ -2000,6 +2034,161 @@ public: QBindable<int> bindableX() const { return &_xProp; } }; +class ResettableGadget +{ + Q_GADGET + Q_PROPERTY(qreal value READ value WRITE setValue RESET resetValue) + + qreal m_value = 0; + +public: + qreal value() const { return m_value; } + void setValue(qreal val) { m_value = val; } + void resetValue() { m_value = 42; } +}; + +class ResettableGadgetHolder : public QObject { + Q_OBJECT + QML_ELEMENT + + Q_PROPERTY(ResettableGadget g READ g WRITE setG NOTIFY gChanged) + ResettableGadget m_g; + +signals: + void gChanged(); + +public: + ResettableGadget g() const { return m_g; } + void setG(ResettableGadget newG) + { + if (m_g.value() == newG.value()) + return; + m_g = newG; + Q_EMIT gChanged(); + } +}; + +class ListPropertyAssignment_Gadget +{ + Q_GADGET + Q_PROPERTY(QStringList gadgetStringList READ gadgetStringList WRITE setGadgetStringList) + Q_PROPERTY(QVariantList gadgetVariantList READ gadgetVariantList WRITE setGadgetVariantList) + QML_VALUE_TYPE(listPropertyAssignment_Gadget) +public: + ListPropertyAssignment_Gadget(); + QStringList gadgetStringList() const; + void setGadgetStringList(const QStringList &list); + + QVariantList gadgetVariantList() const; + void setGadgetVariantList(const QVariantList &list); + +private: + QStringList m_gadgetStringList; + QVariantList m_gadgetVariantList; +}; + +class ListPropertyAssignment_Object : public QObject +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(QStringList qobjectStringList READ qobjectStringList WRITE setQobjectStringList) +public: + explicit ListPropertyAssignment_Object(QObject *parent = nullptr); + + QStringList qobjectStringList() const { return m_qobjectStringList; } + + void setQobjectStringList(const QStringList &newList); + +private: + QStringList m_qobjectStringList; +}; + +class SingletonBase : public QObject { + Q_OBJECT + +public: + Q_INVOKABLE virtual void trackPage(const QString&) {} + Q_INVOKABLE virtual void trackPage(const QString&, const QVariantMap&) {} + + bool m_okay = false; +}; + +class SingletonImpl : public SingletonBase { + Q_OBJECT + +public: + Q_INVOKABLE virtual void trackPage(const QString&) override {} + Q_INVOKABLE virtual void trackPage(const QString&, const QVariantMap&) override + { + m_okay = true; + } +}; + +class SingletonRegistrationWrapper { + Q_GADGET + QML_FOREIGN(SingletonBase) + QML_NAMED_ELEMENT(SingletonInheritanceTest) + QML_SINGLETON + +public: + static SingletonBase* create(QQmlEngine*, QJSEngine*) { + return new SingletonImpl(); + } + +private: + SingletonRegistrationWrapper() = default; +}; + +class MetaCallInterceptor : public QObject, public QDynamicMetaObjectData +{ + Q_OBJECT +public: + MetaCallInterceptor() + { + didGetObjectDestroyedCallback = false; + } + + void objectDestroyed(QObject *object) override + { + didGetObjectDestroyedCallback = true; + + // Deletes this meta object + QDynamicMetaObjectData::objectDestroyed(object); + } + + QMetaObject *toDynamicMetaObject(QObject *) override + { + return const_cast<QMetaObject *>(&MetaCallInterceptor::staticMetaObject); + } + + int metaCall(QObject *o, QMetaObject::Call call, int idx, void **argv) override + { + return o->qt_metacall(call, idx, argv); + } + + static bool didGetObjectDestroyedCallback; +}; + +struct TypeToTriggerProxyMetaObject +{ + Q_GADGET +}; + +class TypeWithCustomMetaObject : public QObject +{ + Q_OBJECT + QML_NAMED_ELEMENT(TypeWithCustomMetaObject) + QML_EXTENDED_NAMESPACE(TypeToTriggerProxyMetaObject) + +public: + TypeWithCustomMetaObject() + { + auto *p = QObjectPrivate::get(this); + Q_ASSERT(!p->metaObject); + p->metaObject = new MetaCallInterceptor; + } +}; + void registerTypes(); #endif // TESTTYPES_H diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp index edb1e9ba80..2d124da279 100644 --- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp +++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp @@ -1,6 +1,7 @@ // Copyright (C) 2017 Crimson AS <info@crimson.no> // Copyright (C) 2021 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 <QtTest/QtTest> #include <QtQml/qqmlcomponent.h> #include <QtQml/qqmlengine.h> @@ -27,6 +28,8 @@ #include <private/qqmlabstractbinding_p.h> #include <private/qqmlvaluetypeproxybinding_p.h> #include <QtCore/private/qproperty_p.h> +#include <QtQuick/qquickwindow.h> +#include <QtQuick/private/qquickitem_p.h> #include <QtQuickTestUtils/private/qmlutils_p.h> #include <QtQuickTestUtils/private/testhttpserver_p.h> @@ -376,6 +379,8 @@ private slots: void qpropertyBindingHandlesUndefinedCorrectly(); void qpropertyBindingHandlesUndefinedWithoutResetCorrectly_data(); void qpropertyBindingHandlesUndefinedWithoutResetCorrectly(); + void qpropertyBindingRestoresObserverAfterReset(); + void qpropertyBindingObserverCorrectlyLinkedAfterReset(); void hugeRegexpQuantifiers(); void singletonTypeWrapperLookup(); void getThisObject(); @@ -396,6 +401,7 @@ private slots: void sequenceConversionMethod(); void proxyIteration(); void proxyHandlerTraps(); + void lookupsDoNotBypassProxy(); void gcCrashRegressionTest(); void cmpInThrows(); void frozenQObject(); @@ -416,6 +422,13 @@ private slots: void doNotCrashOnReadOnlyBindable(); + void resetGadget(); + void assignListPropertyByIndexOnGadget(); + + void methodCallOnDerivedSingleton(); + + void proxyMetaObject(); + private: // static void propertyVarWeakRefCallback(v8::Persistent<v8::Value> object, void* parameter); static void verifyContextLifetime(const QQmlRefPointer<QQmlContextData> &ctxt); @@ -432,14 +445,6 @@ private: } }; -static void gc(QQmlEngine &engine) -{ - engine.collectGarbage(); - QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - QCoreApplication::processEvents(); -} - - tst_qqmlecmascript::tst_qqmlecmascript() : QQmlDataTest(QT_QMLTEST_DATADIR) { @@ -3127,11 +3132,14 @@ void tst_qqmlecmascript::callQtInvokables() QCOMPARE(o->invoked(), -1); QCOMPARE(o->actuals().size(), 0); - o->reset(); - QVERIFY(EVALUATE_ERROR("object.method_QPointF(object)")); - QCOMPARE(o->error(), false); - QCOMPARE(o->invoked(), -1); - QCOMPARE(o->actuals().size(), 0); + // This fails if the QtQml module is loaded but works if it's not. + // If QtQml is loaded, QPointF is a structured value type that can be created from any object. + // + // o->reset(); + // QVERIFY(EVALUATE_ERROR("object.method_QPointF(object)")); + // QCOMPARE(o->error(), false); + // QCOMPARE(o->invoked(), -1); + // QCOMPARE(o->actuals().size(), 0); o->reset(); QVERIFY(EVALUATE_VALUE("object.method_QPointF(object.method_get_QPointF())", QV4::Primitive::undefinedValue())); @@ -3186,6 +3194,17 @@ void tst_qqmlecmascript::callQtInvokables() QCOMPARE(o->invoked(), -1); // no function got called due to incompatible arguments } + { + o->reset(); + QQmlComponent comp(&qmlengine, testFileUrl("qmlTypeWrapperArgs3.qml")); + QScopedPointer<QObject> root {comp.createWithInitialProperties({{"invokableObject", QVariant::fromValue(o)}}) }; + QVERIFY(root); + QCOMPARE(o->error(), false); + QCOMPARE(o->actuals().size(), 2); + QCOMPARE(o->actuals().at(0).metaType(), QMetaType::fromType<QQmlComponentAttached *>()); + QCOMPARE(o->actuals().at(1).metaType(), QMetaType::fromType<SingletonWithEnum *>()); + } + o->reset(); QVERIFY(EVALUATE_VALUE("object.method_QObject(undefined)", QV4::Primitive::undefinedValue())); QCOMPARE(o->error(), false); @@ -3517,6 +3536,27 @@ void tst_qqmlecmascript::callQtInvokables() QCOMPARE(o->error(), false); QCOMPARE(o->invoked(), -1); QCOMPARE(o->actuals(), QVariantList()); + + o->reset(); + QVERIFY(EVALUATE_VALUE("object.method_component(object.someComponent())", + QV4::Primitive::undefinedValue())); + QCOMPARE(o->error(), false); + QCOMPARE(o->invoked(), 42); + QCOMPARE(o->actuals(), QVariantList() << QVariant::fromValue(o->someComponent())); + + o->reset(); + QVERIFY(EVALUATE_VALUE("object.method_component(object.someTypeObject())", + QV4::Primitive::undefinedValue())); + QCOMPARE(o->error(), false); + QCOMPARE(o->invoked(), 43); + QCOMPARE(o->actuals(), QVariantList() << QVariant::fromValue(o->someTypeObject())); + + o->reset(); + QVERIFY(EVALUATE_VALUE("object.method_component('qrc:/somewhere/else')", + QV4::Primitive::undefinedValue())); + QCOMPARE(o->error(), false); + QCOMPARE(o->invoked(), 44); + QCOMPARE(o->actuals(), QVariantList() << QVariant::fromValue(QUrl("qrc:/somewhere/else"))); } void tst_qqmlecmascript::resolveClashingProperties() @@ -3815,6 +3855,82 @@ void tst_qqmlecmascript::scriptConnect() QScopedPointer<QObject> root { component.create() }; QVERIFY2(root, qPrintable(component.errorString())); } + + { + QQmlComponent component(&engine, testFileUrl("scriptConnect.8.qml")); + + QScopedPointer<QObject> obj(component.create()); + QVERIFY2(obj, qPrintable(component.errorString())); + QVERIFY(obj.data() != nullptr); + + QCOMPARE(obj.data()->property("count"), 0); + + QMetaObject::invokeMethod(obj.data(), "someSignal"); + QCOMPARE(obj.data()->property("count"), 1); + + QMetaObject::invokeMethod(obj.data(), "itemDestroy"); + QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); + QCoreApplication::processEvents(); + + QMetaObject::invokeMethod(obj.data(), "someSignal"); + QCOMPARE(obj.data()->property("count"), 1); + } + + { + QQmlComponent component(&engine, testFileUrl("scriptConnect.9.qml")); + + QScopedPointer<QObject> obj(component.create()); + QVERIFY2(obj, qPrintable(component.errorString())); + QVERIFY(obj.data() != nullptr); + + MyQmlObject *object = qobject_cast<MyQmlObject *>(obj.data()); + + QCOMPARE(object->property("a"), 0); + + QMetaObject::invokeMethod(object, "someSignal"); + QCOMPARE(object->property("a"), 1); + + QMetaObject::invokeMethod(object, "destroyObj", Qt::DirectConnection); + QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); + QApplication::processEvents(); + + QMetaObject::invokeMethod(object, "someSignal"); + + QCOMPARE(object->property("a"), 1); + } + + { + QQmlComponent component(&engine, testFileUrl("scriptConnectSingleton.qml")); + + QScopedPointer<QObject> obj(component.create()); + QVERIFY2(obj, qPrintable(component.errorString())); + QVERIFY(obj.data() != nullptr); + + QMetaObject::invokeMethod(obj.data(), "mySignal", Qt::DirectConnection); + QCOMPARE(obj.data()->property("a").toInt(), 1); + engine.clearSingletons(); + QMetaObject::invokeMethod(obj.data(), "mySignal", Qt::DirectConnection); + QCOMPARE(obj.data()->property("a").toInt(), 1); + } + + { + QQmlComponent component(&engine, testFileUrl("scriptConnect.deletion.qml")); + + QScopedPointer<QObject> obj(component.create()); + QVERIFY2(obj, qPrintable(component.errorString())); + QVERIFY(!obj.isNull()); + + QCOMPARE(obj->property("a"), 0); + + QMetaObject::invokeMethod(obj.data(), "someSignal"); + QCOMPARE(obj->property("a"), 1); + + QCOMPARE(obj->property("b"), 0); + QMetaObject::invokeMethod(obj.data(), "destroyObj", Qt::DirectConnection); + + QTRY_COMPARE(obj->property("b"), 1); + QCOMPARE(obj->property("a"), 1); + } } void tst_qqmlecmascript::scriptDisconnect() @@ -3895,6 +4011,60 @@ void tst_qqmlecmascript::scriptDisconnect() emit object->argumentSignal(19, "Hello world!", 10.25, MyQmlObject::EnumValue4, Qt::RightButton); QCOMPARE(object->property("test").toInt(), 3); } + + { + QQmlComponent component(&engine, testFileUrl("scriptDisconnect.5.qml")); + + QScopedPointer<QObject> obj(component.create()); + QVERIFY2(obj, qPrintable(component.errorString())); + QVERIFY(obj.data() != nullptr); + + QCOMPARE(obj.data()->property("count"), 0); + + QMetaObject::invokeMethod(obj.data(), "someSignal"); + QCOMPARE(obj.data()->property("count"), 1); + + QMetaObject::invokeMethod(obj.data(), "disconnectSignal"); + + QMetaObject::invokeMethod(obj.data(), "someSignal"); + QCOMPARE(obj.data()->property("count"), 1); + } + + { + QQmlComponent component(&engine, testFileUrl("scriptConnect.9.qml")); + + QScopedPointer<QObject> obj(component.create()); + QVERIFY2(obj, qPrintable(component.errorString())); + QVERIFY(obj.data() != nullptr); + + MyQmlObject *object = qobject_cast<MyQmlObject *>(obj.data()); + + QCOMPARE(object->property("a"), 0); + + QMetaObject::invokeMethod(object, "someSignal"); + QCOMPARE(object->property("a"), 1); + + QMetaObject::invokeMethod(object, "disconnectSignal", Qt::DirectConnection); + + QMetaObject::invokeMethod(object, "someSignal"); + + QCOMPARE(object->property("a"), 1); + } + + { + QQmlComponent component(&engine, testFileUrl("scriptConnectSingleton.qml")); + + QScopedPointer<QObject> obj(component.create()); + QVERIFY2(obj, qPrintable(component.errorString())); + QVERIFY(obj.data() != nullptr); + + QMetaObject::invokeMethod(obj.data(), "mySignal", Qt::DirectConnection); + QCOMPARE(obj.data()->property("a").toInt(), 1); + + QMetaObject::invokeMethod(obj.data(), "disconnectSingleton", Qt::DirectConnection); + QMetaObject::invokeMethod(obj.data(), "mySignal", Qt::DirectConnection); + QCOMPARE(obj.data()->property("a").toInt(), 1); + } } class OwnershipObject : public QObject @@ -3923,10 +4093,7 @@ void tst_qqmlecmascript::ownership() QScopedPointer<QObject> object(component.create(context.data())); - engine.collectGarbage(); - - QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - QCoreApplication::processEvents(); + gc(engine); QVERIFY(own.object.isNull()); } @@ -3940,10 +4107,7 @@ void tst_qqmlecmascript::ownership() QScopedPointer<QObject> object(component.create(context.data())); - engine.collectGarbage(); - - QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - QCoreApplication::processEvents(); + gc(engine); QVERIFY(own.object != nullptr); } @@ -4019,9 +4183,7 @@ void tst_qqmlecmascript::ownershipCustomReturnValue() QVERIFY(source.value != nullptr); } - engine.collectGarbage(); - QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - QCoreApplication::processEvents(); + gc(engine); QVERIFY(source.value.isNull()); } @@ -4052,10 +4214,7 @@ void tst_qqmlecmascript::ownershipRootObject() QScopedPointer<QObject> object(component.create(context.data())); QVERIFY2(object, qPrintable(component.errorString())); - engine.collectGarbage(); - - QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - QCoreApplication::processEvents(); + gc(engine); QVERIFY(own.object != nullptr); } @@ -4080,10 +4239,7 @@ void tst_qqmlecmascript::ownershipConsistency() QScopedPointer<QObject> object(component.create(context.data())); QVERIFY2(object, qPrintable(component.errorString())); - engine.collectGarbage(); - - QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - QCoreApplication::processEvents(); + gc(engine); QVERIFY(own.object != nullptr); } @@ -4613,6 +4769,7 @@ void tst_qqmlecmascript::verifyContextLifetime(const QQmlRefPointer<QQmlContextD } ctxt->engine()->collectGarbage(); + QTRY_VERIFY(gcDone(ctxt->engine())); qml = scripts->get(i); newContext = qml ? qml->getContext() : nullptr; QCOMPARE(scriptContext.data(), newContext.data()); @@ -5257,6 +5414,7 @@ void tst_qqmlecmascript::propertyChangeSlots() QQmlComponent component(&engine, testFileUrl("changeslots/propertyChangeSlots.qml")); QScopedPointer<QObject> object(component.create()); QVERIFY2(object, qPrintable(component.errorString())); + QCOMPARE(object->property("changeCount"), 15); // ensure that invalid property names fail properly. QTest::ignoreMessage(QtWarningMsg, "QQmlComponent: Component is not ready"); @@ -5272,20 +5430,6 @@ void tst_qqmlecmascript::propertyChangeSlots() QCOMPARE(e2.errors().at(0).toString(), expectedErrorString); object.reset(e2.create()); QVERIFY(!object); - - QTest::ignoreMessage(QtWarningMsg, "QQmlComponent: Component is not ready"); - QQmlComponent e3(&engine, testFileUrl("changeslots/propertyChangeSlotErrors.3.qml")); - expectedErrorString = e3.url().toString() + QLatin1String(":9:5: Cannot assign to non-existent property \"on$NameWithDollarsignChanged\""); - QCOMPARE(e3.errors().at(0).toString(), expectedErrorString); - object.reset(e3.create()); - QVERIFY(!object); - - QTest::ignoreMessage(QtWarningMsg, "QQmlComponent: Component is not ready"); - QQmlComponent e4(&engine, testFileUrl("changeslots/propertyChangeSlotErrors.4.qml")); - expectedErrorString = e4.url().toString() + QLatin1String(":9:5: Cannot assign to non-existent property \"on_6NameWithUnderscoreNumberChanged\""); - QCOMPARE(e4.errors().at(0).toString(), expectedErrorString); - object.reset(e4.create()); - QVERIFY(!object); } void tst_qqmlecmascript::propertyVar_data() @@ -5453,7 +5597,9 @@ void tst_qqmlecmascript::propertyVarOwnership() QScopedPointer<QObject> object(component.create()); QVERIFY2(object, qPrintable(component.errorString())); QMetaObject::invokeMethod(object.data(), "createComponent"); - engine.collectGarbage(); + // This test only works if we don't deliver the pending delete later event + // that collectGarbage will post before calling runTest + gc(engine, GCFlags::DontSendPostedEvents); QMetaObject::invokeMethod(object.data(), "runTest"); QCOMPARE(object->property("test").toBool(), true); } @@ -5469,8 +5615,7 @@ void tst_qqmlecmascript::propertyVarImplicitOwnership() QScopedPointer<QObject> object(component.create()); QVERIFY2(object, qPrintable(component.errorString())); QMetaObject::invokeMethod(object.data(), "assignCircular"); - QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper. - QCoreApplication::processEvents(); + gc(engine); QObject *rootObject = object->property("vp").value<QObject*>(); QVERIFY(rootObject != nullptr); QObject *childObject = rootObject->findChild<QObject*>("text"); @@ -5479,6 +5624,8 @@ void tst_qqmlecmascript::propertyVarImplicitOwnership() QCOMPARE(childObject->property("textCanary").toInt(), 10); // Creates a reference to a constructed QObject: QMetaObject::invokeMethod(childObject, "constructQObject"); + // Don't send delete later events yet, we do it manually later + gc(engine, GCFlags::DontSendPostedEvents); QPointer<QObject> qobjectGuard(childObject->property("vp").value<QObject*>()); // get the pointer prior to processing deleteLater events. QVERIFY(!qobjectGuard.isNull()); QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper. @@ -5497,8 +5644,7 @@ void tst_qqmlecmascript::propertyVarReparent() QScopedPointer<QObject> object(component.create()); QVERIFY2(object, qPrintable(component.errorString())); QMetaObject::invokeMethod(object.data(), "assignVarProp"); - QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper. - QCoreApplication::processEvents(); + gc(engine); QObject *rect = object->property("vp").value<QObject*>(); QObject *text = rect->findChild<QObject*>("textOne"); QObject *text2 = rect->findChild<QObject*>("textTwo"); @@ -5512,6 +5658,7 @@ void tst_qqmlecmascript::propertyVarReparent() QCOMPARE(text2->property("textCanary").toInt(), 12); // now construct an image which we will reparent. QMetaObject::invokeMethod(text2, "constructQObject"); + gc(engine, GCFlags::DontSendPostedEvents); QObject *image = text2->property("vp").value<QObject*>(); QPointer<QObject> imageGuard(image); QVERIFY(!imageGuard.isNull()); @@ -5539,8 +5686,7 @@ void tst_qqmlecmascript::propertyVarReparentNullContext() QScopedPointer<QObject> object(component.create()); QVERIFY2(object, qPrintable(component.errorString())); QMetaObject::invokeMethod(object.data(), "assignVarProp"); - QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); // process deleteLater() events from QV8QObjectWrapper. - QCoreApplication::processEvents(); + gc(engine); QObject *rect = object->property("vp").value<QObject*>(); QObject *text = rect->findChild<QObject*>("textOne"); QObject *text2 = rect->findChild<QObject*>("textTwo"); @@ -5554,6 +5700,7 @@ void tst_qqmlecmascript::propertyVarReparentNullContext() QCOMPARE(text2->property("textCanary").toInt(), 12); // now construct an image which we will reparent. QMetaObject::invokeMethod(text2, "constructQObject"); + gc(engine); QObject *image = text2->property("vp").value<QObject*>(); QPointer<QObject> imageGuard(image); QVERIFY(!imageGuard.isNull()); @@ -5780,9 +5927,7 @@ void tst_qqmlecmascript::handleReferenceManagement() gc(hrmEngine); QCOMPARE(dtorCount, 0); // second has JS ownership, kept alive by first's reference object.reset(); - hrmEngine.collectGarbage(); - QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - QCoreApplication::processEvents(); + gc(hrmEngine); QCOMPARE(dtorCount, 3); } @@ -5799,9 +5944,7 @@ void tst_qqmlecmascript::handleReferenceManagement() gc(hrmEngine); QCOMPARE(dtorCount, 2); // both should be cleaned up, since circular references shouldn't keep alive. object.reset(); - hrmEngine.collectGarbage(); - QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - QCoreApplication::processEvents(); + gc(hrmEngine); QCOMPARE(dtorCount, 3); } @@ -7892,7 +8035,7 @@ public: void init(QV4::ExecutionEngine *v4, QV4::WeakValue *weakRef, bool *resultPtr) { - QV4::QObjectWrapper::wrap(v4, this); + (void) QV4::QObjectWrapper::wrap(v4, this); // Intentionally drop the wrapper QQmlEngine::setObjectOwnership(this, QQmlEngine::JavaScriptOwnership); this->resultPtr = resultPtr; @@ -7980,12 +8123,9 @@ void tst_qqmlecmascript::onDestructionViaGC() v4->memoryManager->allocate<QV4::WeakReferenceSentinel>(weakRef.data(), &sentinelResult); } gc(engine); - + QVERIFY2(weakRef->isNullOrUndefined(), "The weak value was not cleared"); QVERIFY2(mutatorResult, "We failed to re-assign the weak reference a new value during GC"); - QVERIFY2(!sentinelResult, "The weak value was cleared on first GC run"); - QVERIFY2(!weakRef->isNullOrUndefined(), "The weak value was cleared on first GC run"); - gc(engine); - QVERIFY2(weakRef->isNullOrUndefined(), "The weak value was not cleared on second gc run"); + QVERIFY2(sentinelResult, "The weak reference was not cleared properly"); } struct EventProcessor : public QObject @@ -8078,7 +8218,9 @@ void tst_qqmlecmascript::qqmldataDestroyed() QVERIFY2(object, qPrintable(c.errorString())); // now gc causing the collection of the dynamically constructed object. engine.collectGarbage(); + QTRY_VERIFY(gcDone(&engine)); engine.collectGarbage(); + QTRY_VERIFY(gcDone(&engine)); // now process events to allow deletion (calling qqmldata::destroyed()) QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); QCoreApplication::processEvents(); @@ -9389,6 +9531,32 @@ void tst_qqmlecmascript::qpropertyBindingHandlesUndefinedWithoutResetCorrectly() QCOMPARE(root->property("value2").toInt(), 2); } +void tst_qqmlecmascript::qpropertyBindingRestoresObserverAfterReset() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("restoreObserverAfterReset.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + QTRY_COMPARE(o->property("height").toDouble(), 60.0); + QVERIFY(o->property("steps").toInt() > 3); +} + +void tst_qqmlecmascript::qpropertyBindingObserverCorrectlyLinkedAfterReset() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("qpropertyResetCorrectlyLinked.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + std::unique_ptr<QObject> o(c.create()); + QVERIFY(o); + QCOMPARE(o->property("width"), 200); + auto item = qobject_cast<QQuickItem *>(o.get()); + auto itemPriv = QQuickItemPrivate::get(item); + QBindingStorage *storage = qGetBindingStorage(itemPriv); + QPropertyBindingDataPointer ptr { storage->bindingData(&itemPriv->width) }; + QCOMPARE(ptr.observerCount(), 1); +} + void tst_qqmlecmascript::hugeRegexpQuantifiers() { QJSEngine engine; @@ -9987,6 +10155,17 @@ void tst_qqmlecmascript::proxyHandlerTraps() QVERIFY(value.isString() && value.toString() == QStringLiteral("SUCCESS")); } +void tst_qqmlecmascript::lookupsDoNotBypassProxy() +{ + QQmlEngine engine; + // we need a component to have a proper compilation to byte code; + // otherwise, we don't actually end up with lookups + QQmlComponent comp(&engine, testFileUrl("lookupsDoNotBypassProxy.qml")); + QVERIFY(comp.isReady()); + std::unique_ptr<QObject> obj { comp.create() }; + QCOMPARE(obj->property("result").toInt(), 3); +} + void tst_qqmlecmascript::cmpInThrows() { QJSEngine engine; @@ -10027,6 +10206,9 @@ public: Q_INVOKABLE void triggerSignal() { emit fooMember2Emitted(&m_fooMember2); } + Q_INVOKABLE const FrozenFoo *getConst() { return createFloating(); } + Q_INVOKABLE FrozenFoo *getNonConst() { return createFloating(); } + FrozenFoo *fooMember() { return &m_fooMember; } FrozenFoo *fooMember2() { return &m_fooMember2; } @@ -10036,6 +10218,16 @@ signals: private: const FrozenFoo *fooMemberConst() const { return &m_fooMember; } + FrozenFoo *createFloating() + { + if (!m_floating) { + m_floating = new FrozenFoo; + m_floating->setObjectName(objectName()); + } + return m_floating; + } + + FrozenFoo *m_floating = nullptr; FrozenFoo m_fooMember; FrozenFoo m_fooMember2; }; @@ -10058,6 +10250,17 @@ void tst_qqmlecmascript::frozenQObject() QVERIFY(frozenObjects->property("caughtSignal").toBool()); QCOMPARE(frozenObjects->fooMember()->name(), QStringLiteral("Jane")); QCOMPARE(frozenObjects->fooMember2()->name(), QStringLiteral("Jane")); + + QQmlComponent component3(&engine, testFileUrl("frozenQObject3.qml")); + QScopedPointer<QObject> root3(component3.create()); + QCOMPARE(root3->objectName(), QLatin1String("a/b")); + QVERIFY(root3->property("objConst").value<QObject *>()); + QVERIFY(root3->property("objNonConst").value<QObject *>()); + + QTRY_VERIFY(root3->property("gcs").toInt() > 8); + + QVERIFY(root3->property("objConst").value<QObject *>()); + QVERIFY(root3->property("objNonConst").value<QObject *>()); } struct ConstPointer : QObject @@ -10373,6 +10576,80 @@ void tst_qqmlecmascript::doNotCrashOnReadOnlyBindable() QCOMPARE(o->property("x").toInt(), 7); } +void tst_qqmlecmascript::resetGadget() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("resetGadget.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); + auto resettableGadgetHolder = qobject_cast<ResettableGadgetHolder *>(o.get()); + QVERIFY(resettableGadgetHolder); + QCOMPARE(resettableGadgetHolder->g().value(), 0); + resettableGadgetHolder->setProperty("trigger", QVariant::fromValue(true)); + QCOMPARE(resettableGadgetHolder->g().value(), 42); +} + +void tst_qqmlecmascript::assignListPropertyByIndexOnGadget() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFile("AssignListPropertyByIndexOnGadget.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); + + const auto &gadget = o->property("gadget").value<ListPropertyAssignment_Gadget>(); + const auto *object = o->property("object").value<ListPropertyAssignment_Object *>(); + QVERIFY(object); + + QStringList expected{ "Completely new Element", "Element2", "Element3" }; + QVariantList variants { + u"Completely new Element"_s, + u"foo"_s, + QVariant::fromValue<std::nullptr_t>(nullptr), + QVariant::fromValue<bool>(true) + }; + + QCOMPARE(gadget.gadgetStringList(), expected); + QCOMPARE(gadget.gadgetVariantList(), variants); + QCOMPARE(object->qobjectStringList(), expected); +} + +void tst_qqmlecmascript::methodCallOnDerivedSingleton() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFile("methodCallOnDerivedSingleton.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(o); + auto singleton = engine.singletonInstance<SingletonBase *>("Qt.test", "SingletonInheritanceTest"); + QVERIFY(singleton); + QVERIFY(singleton->m_okay); +} + +void tst_qqmlecmascript::proxyMetaObject() +{ + // Verify that TypeWithCustomMetaObject, that extends another type, + // thereby triggering a QQmlProxyMetaObject, is still proxied the + // QDynamicMetaObjectData::objectDestroyed callback. + + QQmlEngine engine; + QQmlComponent component(&engine); + component.setData(R"( + import QtQuick + import QtQml + import Qt.test + Rectangle { + TypeWithCustomMetaObject {} + } + )", QUrl("testData")); + QScopedPointer<QObject> o(component.create()); + QVERIFY(o); + QVERIFY(!MetaCallInterceptor::didGetObjectDestroyedCallback); + o.reset(nullptr); + QVERIFY(MetaCallInterceptor::didGetObjectDestroyedCallback); +} + QTEST_MAIN(tst_qqmlecmascript) #include "tst_qqmlecmascript.moc" |