diff options
Diffstat (limited to 'tests/auto/qml/qqmllanguage')
64 files changed, 2714 insertions, 66 deletions
diff --git a/tests/auto/qml/qqmllanguage/CMakeLists.txt b/tests/auto/qml/qqmllanguage/CMakeLists.txt index e07f741bf6..9fff8f2118 100644 --- a/tests/auto/qml/qqmllanguage/CMakeLists.txt +++ b/tests/auto/qml/qqmllanguage/CMakeLists.txt @@ -7,6 +7,12 @@ ## tst_qqmllanguage Test: ##################################################################### +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qqmllanguage LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + # Collect test data file(GLOB_RECURSE test_data_glob RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} @@ -27,6 +33,8 @@ qt_internal_add_test(tst_qqmllanguage TESTDATA ${test_data} ) +add_subdirectory(testhelper) + #### Keys ignored in scope 1:.:.:qqmllanguage.pro:<TRUE>: # OTHER_FILES = "data/readonlyObjectProperty.qml" # QML_IMPORT_NAME = "StaticTest" diff --git a/tests/auto/qml/qqmllanguage/data/AliasHolder.qml b/tests/auto/qml/qqmllanguage/data/AliasHolder.qml new file mode 100644 index 0000000000..42aed6ed26 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/AliasHolder.qml @@ -0,0 +1,6 @@ +import QtQml + +QtObject { + property alias strokeStyle: path.restoreMode + property Binding p: Binding { id: path } +} diff --git a/tests/auto/qml/qqmllanguage/data/CompositeTypeWithEnumSelfReference.qml b/tests/auto/qml/qqmllanguage/data/CompositeTypeWithEnumSelfReference.qml new file mode 100644 index 0000000000..0ec43bf7aa --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/CompositeTypeWithEnumSelfReference.qml @@ -0,0 +1,8 @@ +import QtQml +import Test + +QtObject { + enum A { B, C, D } + property int e: CompositeTypeWithEnumSelfReference.C + property int f: CompositeTypeWithEnumSelfReference.A.D +} diff --git a/tests/auto/qml/qqmllanguage/data/Comps/IconPropertiesGroup.qml b/tests/auto/qml/qqmllanguage/data/Comps/IconPropertiesGroup.qml new file mode 100644 index 0000000000..232c755bfb --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/Comps/IconPropertiesGroup.qml @@ -0,0 +1,5 @@ +import QtQuick + +QtObject { + function dothing() { console.log("do") } +} diff --git a/tests/auto/qml/qqmllanguage/data/Comps/OverlayDrawer.qml b/tests/auto/qml/qqmllanguage/data/Comps/OverlayDrawer.qml new file mode 100644 index 0000000000..713c760a04 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/Comps/OverlayDrawer.qml @@ -0,0 +1,5 @@ +import QtQuick + +Item { + property IconPropertiesGroup handleOpenIcon: IconPropertiesGroup {} +} diff --git a/tests/auto/qml/qqmllanguage/data/Comps/qmldir b/tests/auto/qml/qqmllanguage/data/Comps/qmldir new file mode 100644 index 0000000000..0a68a376f6 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/Comps/qmldir @@ -0,0 +1,4 @@ +module Comps +OverlayDrawer 254.0 OverlayDrawer.qml +IconPropertiesGroup 254.0 IconPropertiesGroup.qml + diff --git a/tests/auto/qml/qqmllanguage/data/DeepAliasOnIC.qml b/tests/auto/qml/qqmllanguage/data/DeepAliasOnIC.qml new file mode 100644 index 0000000000..134eacf913 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/DeepAliasOnIC.qml @@ -0,0 +1,27 @@ +import QtQml + +QtObject { + id: root + objectName: "theRoot" + + component ObjectWithColor: QtObject { + property string color + property var varvar + } + + property ObjectWithColor border: ObjectWithColor { + objectName: root.objectName + color: root.trueBorderColor + varvar: root.trueBorderVarvar + } + + readonly property rect readonlyRect: ({x: 12, y: 13, width: 14, height: 15}) + + property alias borderObjectName: root.border.objectName + property alias borderColor: root.border.color + property alias borderVarvar: root.border.varvar + property alias readonlyRectX: root.readonlyRect.x + + property string trueBorderColor: "green" + property var trueBorderVarvar: 1234 +} diff --git a/tests/auto/qml/qqmllanguage/data/SingletonWithRequiredProperties/SingletonWithRequired1.qml b/tests/auto/qml/qqmllanguage/data/SingletonWithRequiredProperties/SingletonWithRequired1.qml new file mode 100644 index 0000000000..f549e851a3 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/SingletonWithRequiredProperties/SingletonWithRequired1.qml @@ -0,0 +1,6 @@ +pragma Singleton +import QtQml + +QtObject { + required property int i +} diff --git a/tests/auto/qml/qqmllanguage/data/SingletonWithRequiredProperties/SingletonWithRequired2.qml b/tests/auto/qml/qqmllanguage/data/SingletonWithRequiredProperties/SingletonWithRequired2.qml new file mode 100644 index 0000000000..1f9e7e3a42 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/SingletonWithRequiredProperties/SingletonWithRequired2.qml @@ -0,0 +1,8 @@ +pragma Singleton +import QtQml + +QtObject { + property QtObject o: QtObject { + required property int i + } +} diff --git a/tests/auto/qml/qqmllanguage/data/SingletonWithRequiredProperties/qmldir b/tests/auto/qml/qqmllanguage/data/SingletonWithRequiredProperties/qmldir new file mode 100644 index 0000000000..46e397ca76 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/SingletonWithRequiredProperties/qmldir @@ -0,0 +1,4 @@ +module SingletonWithRequiredProperties + +singleton SingletonWithRequired1 1.0 SingletonWithRequired1.qml +singleton SingletonWithRequired2 1.0 SingletonWithRequired2.qml diff --git a/tests/auto/qml/qqmllanguage/data/TypeAnnotationCycle1.qml b/tests/auto/qml/qqmllanguage/data/TypeAnnotationCycle1.qml new file mode 100644 index 0000000000..6186faa00b --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/TypeAnnotationCycle1.qml @@ -0,0 +1,15 @@ +import QtQml + +QtObject { + id: self + property QtObject b + property Component c + function a() : TypeAnnotationCycle2 { return c.createObject() as TypeAnnotationCycle2 } + + Component.onCompleted: { + c = Qt.createComponent("TypeAnnotationCycle2.qml"); + let v = a(); + v.addTypeAnnotationCycle1(self as TypeAnnotationCycle1); + b = v.b; + } +} diff --git a/tests/auto/qml/qqmllanguage/data/TypeAnnotationCycle2.qml b/tests/auto/qml/qqmllanguage/data/TypeAnnotationCycle2.qml new file mode 100644 index 0000000000..9e3ffa86d2 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/TypeAnnotationCycle2.qml @@ -0,0 +1,6 @@ +import QtQml + +QtObject { + property QtObject b + function addTypeAnnotationCycle1(c: TypeAnnotationCycle1) { b = c; } +} diff --git a/tests/auto/qml/qqmllanguage/data/UIToolBar.qml b/tests/auto/qml/qqmllanguage/data/UIToolBar.qml new file mode 100644 index 0000000000..08a22d2492 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/UIToolBar.qml @@ -0,0 +1,10 @@ +import QtQml + +QtObject { + id: root + signal doneClicked() + signal foo() + + onObjectNameChanged: foo() + Component.onCompleted: root.foo.connect(root.doneClicked) +} diff --git a/tests/auto/qml/qqmllanguage/data/alias.16.qml b/tests/auto/qml/qqmllanguage/data/alias.16.qml index 4637aec58f..335d240003 100644 --- a/tests/auto/qml/qqmllanguage/data/alias.16.qml +++ b/tests/auto/qml/qqmllanguage/data/alias.16.qml @@ -2,7 +2,7 @@ import QtQuick 2.0 import QtQuick.Window 2.0 Window { - visible: true + visible: false property alias list: repeater.model diff --git a/tests/auto/qml/qqmllanguage/data/aliasWriter.qml b/tests/auto/qml/qqmllanguage/data/aliasWriter.qml new file mode 100644 index 0000000000..4001c2af34 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/aliasWriter.qml @@ -0,0 +1,5 @@ +import QtQml + +AliasHolder { + strokeStyle: 1 +} diff --git a/tests/auto/qml/qqmllanguage/data/ambiguousComponents.qml b/tests/auto/qml/qqmllanguage/data/ambiguousComponents.qml new file mode 100644 index 0000000000..64c31b46d6 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/ambiguousComponents.qml @@ -0,0 +1,16 @@ +import QtQuick +import Comps as Comps + +Comps.OverlayDrawer { + id: self + + property var dothing + + Component.onCompleted: dothing = handleOpenIcon.dothing + + function dodo() { dothing() } + + function testInstanceOf() : bool { + return self instanceof Comps.OverlayDrawer + } +} diff --git a/tests/auto/qml/qqmllanguage/data/asCastToInlineComponent.qml b/tests/auto/qml/qqmllanguage/data/asCastToInlineComponent.qml new file mode 100644 index 0000000000..428ccd5eef --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/asCastToInlineComponent.qml @@ -0,0 +1,16 @@ +import QtQml + +QtObject { + id: root + + component MyItem: QtObject { + property int value: 10 + onValueChanged: root.objectName = "value: " + value + } + + property Instantiator i: Instantiator { + id: loader + delegate: MyItem {} + onObjectChanged: (loader.object as MyItem).value = 20 + } +} diff --git a/tests/auto/qml/qqmllanguage/data/asValueType.qml b/tests/auto/qml/qqmllanguage/data/asValueType.qml new file mode 100644 index 0000000000..b51dc9c6ec --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/asValueType.qml @@ -0,0 +1,20 @@ +pragma ValueTypeBehavior: Addressable +import QtQml +import StaticTest + +QtObject { + property var a + property rect b: a as rect + property bool c: a instanceof rect + property bool d: ({x: 10, y: 20}) instanceof point + property var e: ({x: 10, y: 20}) as point + property var f: "red" as withString + property var g: "green" as string + property rect bb + property var p: bb as size; + property var q: this as size; + property var r: ({}) as size; + property var s: 11 as size; + property var t: Component as size; + property var u: Qt as size; +} diff --git a/tests/auto/qml/qqmllanguage/data/asValueTypeGood.qml b/tests/auto/qml/qqmllanguage/data/asValueTypeGood.qml new file mode 100644 index 0000000000..777ada3848 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/asValueTypeGood.qml @@ -0,0 +1,35 @@ +pragma ValueTypeBehavior: Assertable +import QtQml as Q +import StaticTest as S + +Q.QtObject { + property var a + property rect b: a as Q.rect + property bool c: a instanceof Q.rect + property bool d: ({x: 10, y: 20}) instanceof Q.point + property var e: ({x: 10, y: 20}) as Q.point + property var f: "red" as S.withString + property var g: "green" as Q.string + + property var h: new S.withString("red") + property var i: { + let p = new Q.point; + p.x = 10 + p.y = 20 + return p + } + + property var j: 4.0 as Q.int + property var k: (4.5 / 1.5) as Q.int + property var l: 5 as Q.double + property var m: "something" as Q.var + property var n: 1 as Q.bool + property var o: Infinity as Q.int + + property var p: b as Q.size; + property var q: this as Q.size; + property var r: ({}) as Q.size; + property var s: 11 as Q.size; + property var t: Q.Component as Q.size; + property var u: Q.Qt as Q.size; +} diff --git a/tests/auto/qml/qqmllanguage/data/badICAnnotation.qml b/tests/auto/qml/qqmllanguage/data/badICAnnotation.qml new file mode 100644 index 0000000000..6f0db53f2a --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/badICAnnotation.qml @@ -0,0 +1,25 @@ +import QtQml + +QtObject { + id: self + + function doStuff(status: Binding.NotAnInlineComponent) : int { + return status + } + + function doStuff2(status: InlineComponentBase.IC) : QtObject { + return status + } + + function doStuff3(status: InlineComponentBase.NotIC) : QtObject { + return status + } + + property InlineComponentBase.IC ic: InlineComponentBase.IC {} + + property int a: doStuff(5) + property QtObject b: doStuff2(ic) + property QtObject c: doStuff3(ic) + property QtObject d: doStuff2(self) +} + diff --git a/tests/auto/qml/qqmllanguage/data/SingletonTest.qml b/tests/auto/qml/qqmllanguage/data/badSingleton/SingletonTest.qml index 70e1671754..70e1671754 100644 --- a/tests/auto/qml/qqmllanguage/data/SingletonTest.qml +++ b/tests/auto/qml/qqmllanguage/data/badSingleton/SingletonTest.qml diff --git a/tests/auto/qml/qqmllanguage/data/qmldir b/tests/auto/qml/qqmllanguage/data/badSingleton/qmldir index c946de657c..c946de657c 100644 --- a/tests/auto/qml/qqmllanguage/data/qmldir +++ b/tests/auto/qml/qqmllanguage/data/badSingleton/qmldir diff --git a/tests/auto/qml/qqmllanguage/data/qtbug_85932.qml b/tests/auto/qml/qqmllanguage/data/badSingleton/qtbug_85932.qml index aa21558220..aa21558220 100644 --- a/tests/auto/qml/qqmllanguage/data/qtbug_85932.qml +++ b/tests/auto/qml/qqmllanguage/data/badSingleton/qtbug_85932.qml diff --git a/tests/auto/qml/qqmllanguage/data/corpseInQmlList.qml b/tests/auto/qml/qqmllanguage/data/corpseInQmlList.qml new file mode 100644 index 0000000000..dc0e145064 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/corpseInQmlList.qml @@ -0,0 +1,13 @@ +import QtQml + +QtObject { + property var b; + + function returnList(a: QtObject) : list<QtObject> { + return [a] + } + + function setB(a: QtObject) { + b = { b: returnList(a) } + } +} diff --git a/tests/auto/qml/qqmllanguage/data/deepAliasOnICUser.qml b/tests/auto/qml/qqmllanguage/data/deepAliasOnICUser.qml new file mode 100644 index 0000000000..50eaa7c3e2 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/deepAliasOnICUser.qml @@ -0,0 +1,9 @@ +import QtQml + +DeepAliasOnIC { + borderObjectName: "theLeaf" + borderColor: "black" + borderVarvar: "mauve" +} + + diff --git a/tests/auto/qml/qqmllanguage/data/deepAliasOnReadonly.qml b/tests/auto/qml/qqmllanguage/data/deepAliasOnReadonly.qml new file mode 100644 index 0000000000..f5ae62406b --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/deepAliasOnReadonly.qml @@ -0,0 +1,5 @@ +import QtQml + +DeepAliasOnIC { + readonlyRectX: 55 +} diff --git a/tests/auto/qml/qqmllanguage/data/derivedFromUnexposedBase.qml b/tests/auto/qml/qqmllanguage/data/derivedFromUnexposedBase.qml new file mode 100644 index 0000000000..b508474a36 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/derivedFromUnexposedBase.qml @@ -0,0 +1,6 @@ +import Test + +DerivedFromUnexposedBase { + group.value: 42 + groupGadget.value: 42 +} diff --git a/tests/auto/qml/qqmllanguage/data/dynamicGroupPropertyRejected.qml b/tests/auto/qml/qqmllanguage/data/dynamicGroupPropertyRejected.qml new file mode 100644 index 0000000000..2fffea25c6 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/dynamicGroupPropertyRejected.qml @@ -0,0 +1,5 @@ +import Test + +DerivedFromUnexposedBase { + dynamic.value: "This should fail" +} diff --git a/tests/auto/qml/qqmllanguage/data/enumPropsManyUnderlyingTypes.qml b/tests/auto/qml/qqmllanguage/data/enumPropsManyUnderlyingTypes.qml new file mode 100644 index 0000000000..b713d2aa24 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/enumPropsManyUnderlyingTypes.qml @@ -0,0 +1,10 @@ +import Test + +EnumPropsManyUnderlyingTypes { + si8prop: EnumPropsManyUnderlyingTypes.ResolvedValue + ui8prop: EnumPropsManyUnderlyingTypes.ResolvedValue + si16prop: EnumPropsManyUnderlyingTypes.ResolvedValue + ui16prop: EnumPropsManyUnderlyingTypes.ResolvedValue + si64prop: EnumPropsManyUnderlyingTypes.ResolvedValue + ui64prop: EnumPropsManyUnderlyingTypes.ResolvedValue +} diff --git a/tests/auto/qml/qqmllanguage/data/enumScopes.qml b/tests/auto/qml/qqmllanguage/data/enumScopes.qml new file mode 100644 index 0000000000..c71872387f --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/enumScopes.qml @@ -0,0 +1,16 @@ +import QtQml 2.15 +import EnumScopeTest 1.0 + +QtObject { + property NonSingleton n: NonSingleton { + id: nonSingleton + } + + property bool singletonUnscoped: Singleton.enumProperty === Singleton.EnumValue2 + property bool singletonScoped: Singleton.enumProperty === Singleton.EnumType.EnumValue2 + property bool nonSingletonUnscoped: nonSingleton.enumProperty === NonSingleton.EnumValue2 + property bool nonSingletonScoped: nonSingleton.enumProperty === NonSingleton.EnumType.EnumValue2 + + property int singletonScopedValue: EnumProviderSingleton.Expected.Value + property int singletonUnscopedValue: EnumProviderSingleton.Value +} diff --git a/tests/auto/qml/qqmllanguage/data/inlineComponentWithImplicitComponent.qml b/tests/auto/qml/qqmllanguage/data/inlineComponentWithImplicitComponent.qml new file mode 100644 index 0000000000..902dfb501d --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/inlineComponentWithImplicitComponent.qml @@ -0,0 +1,27 @@ +import QtQml + +QtObject { + component C1: QtObject { + property Component comp: null + } + + component C2: C1 { + comp: QtObject { + objectName: "green" + } + } + + component C3: C1 { + comp: Component { + QtObject { + objectName: "blue" + } + } + } + + property QtObject c1: C1 {} + property QtObject c2: C2 {} + property QtObject c3: C3 {} + + objectName: c2.comp.createObject().objectName + " " + c3.comp.createObject().objectName +} diff --git a/tests/auto/qml/qqmllanguage/data/invalidGroupedProperty.1.errors.txt b/tests/auto/qml/qqmllanguage/data/invalidGroupedProperty.1.errors.txt index d76f18ba89..5ddb8a2e6d 100644 --- a/tests/auto/qml/qqmllanguage/data/invalidGroupedProperty.1.errors.txt +++ b/tests/auto/qml/qqmllanguage/data/invalidGroupedProperty.1.errors.txt @@ -1 +1 @@ -5:5:Invalid grouped property access: Property "o" with type "QVariant", which is not a value type +5:7:Cannot assign to non-existent property "blah" diff --git a/tests/auto/qml/qqmllanguage/data/invalidGroupedProperty.3.errors.txt b/tests/auto/qml/qqmllanguage/data/invalidGroupedProperty.3.errors.txt index 9a0422753f..ced96fba83 100644 --- a/tests/auto/qml/qqmllanguage/data/invalidGroupedProperty.3.errors.txt +++ b/tests/auto/qml/qqmllanguage/data/invalidGroupedProperty.3.errors.txt @@ -1 +1 @@ -4:5:Invalid grouped property access: Property "customType" with type "MyCustomVariantType", which is not a value type +4:5:Invalid grouped property access: Property "customType" with type "MyCustomVariantType", which is neither a value nor an object type diff --git a/tests/auto/qml/qqmllanguage/data/invokableCtors.qml b/tests/auto/qml/qqmllanguage/data/invokableCtors.qml new file mode 100644 index 0000000000..35a8d7bf08 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/invokableCtors.qml @@ -0,0 +1,12 @@ +import QtQml as QQ +import Test as VV + +QQ.QtObject { + property QQ.QtObject oo: new QQ.QtObject() + property QQ.QtObject pp: new QQ.QtObject(oo) + property VV.vv v: new VV.vv("green") + + property VV.InvokableSingleton i: new VV.InvokableSingleton(5, oo) + property VV.InvokableExtended k: new VV.InvokableExtended() + property VV.InvokableUncreatable l: new VV.InvokableUncreatable() +} diff --git a/tests/auto/qml/qqmllanguage/data/isNullOrUndefined_interpreter.qml b/tests/auto/qml/qqmllanguage/data/isNullOrUndefined_interpreter.qml new file mode 100644 index 0000000000..460d4667f8 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/isNullOrUndefined_interpreter.qml @@ -0,0 +1,22 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQml + +QtObject { + id: self + property int a: 3 + property var result + Component.onCompleted: { + var sum = 0 + let f = function() { + return self.notthere ?? self.a + } + + // Not enough times for the jit to kick in (should run on the interpreter) + for (let i = 0; i < 1; i++) { + sum = sum + f() + } + result = sum + } +} diff --git a/tests/auto/qml/qqmllanguage/data/isNullOrUndefined_jit.qml b/tests/auto/qml/qqmllanguage/data/isNullOrUndefined_jit.qml new file mode 100644 index 0000000000..43c82f436c --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/isNullOrUndefined_jit.qml @@ -0,0 +1,22 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQml + +QtObject { + id: self + property int a: 3 + property int result + Component.onCompleted: { + var sum = 0 + let f = function() { + return self.notthere ?? self.a + } + + // Enough times for the jit to kick in (should run on the jit) + for (let i = 0; i < 50; i++) { + sum = sum + f() + } + result = sum + } +} diff --git a/tests/auto/qml/qqmllanguage/data/jitExceptions.qml b/tests/auto/qml/qqmllanguage/data/jitExceptions.qml new file mode 100644 index 0000000000..c5e11af31d --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/jitExceptions.qml @@ -0,0 +1,16 @@ +import QtQml + +QtObject { + function burn() { + return control.font + } + + Component.onCompleted: { + for (var a = 0; a < 10; ++a) { + try { burn() } catch(e) {} + } + + burn(); + } + +} diff --git a/tests/auto/qml/qqmllanguage/data/jsonArrayProperty.qml b/tests/auto/qml/qqmllanguage/data/jsonArrayProperty.qml new file mode 100644 index 0000000000..5bd563a288 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/jsonArrayProperty.qml @@ -0,0 +1,191 @@ +import QtQml +import TypeWithQJsonArrayProperty + +TypeWithQJsonArrayProperty { + function jsArray() { return [1, 2, 3] } + + jsonArray: jsArray() + + property list<int> concatenatedJsonArray: jsonArray.concat([4, 5, 6]) + property list<int> concatenatedJsArray: jsArray().concat([4, 5, 6]) + + property bool entriesMatch: { + var iterator = jsonArray.entries(); + for (var [index, element] of jsArray().entries()) { + var v = iterator.next().value; + if (index !== v[0] || element !== v[1]) { + console.log(index, v[0], element, v[1]); + return false; + } + } + + var iterator = jsArray().entries(); + for (var [index, element] of jsonArray.entries()) { + var v = iterator.next().value; + if (index !== v[0] || element !== v[1]) { + console.log(index, v[0], element, v[1]); + return false; + } + } + + return true; + } + + property bool jsonArrayEvery: jsonArray.every(element => element != 0) + property bool jsArrayEvery: jsArray().every(element => element != 0) + + property list<int> jsonArrayFiltered: jsonArray.filter(element => element > 2) + property list<int> jsArrayFiltered: jsArray().filter(element => element > 2) + + property int jsonArrayFind: jsonArray.find(element => element === 2) + property int jsArrayFind: jsArray().find(element => element === 2) + + property int jsonArrayFindIndex: jsonArray.findIndex(element => element === 1) + property int jsArrayFindIndex: jsArray().findIndex(element => element === 1) + + property string jsonArrayForEach + property string jsArrayForEach + + property bool jsonArrayIncludes: jsonArray.includes(3) + property bool jsArrayIncludes: jsArray().includes(3) + + property int jsonArrayIndexOf: jsonArray.indexOf(2) + property int jsArrayIndexOf: jsArray().indexOf(2) + + property string jsonArrayJoin: jsonArray.join() + property string jsArrayJoin: jsArray().join() + + property bool keysMatch: { + var iterator = jsonArray.keys(); + for (var index of jsArray().keys()) { + var v = iterator.next().value; + if (index !== v) { + console.log(index, v); + return false; + } + } + + var iterator = jsArray().keys(); + for (var index of jsonArray.keys()) { + var v = iterator.next().value; + if (index !== v) { + console.log(index, v); + return false; + } + } + + return true; + } + + property int jsonArrayLastIndexOf: jsonArray.lastIndexOf(1) + property int jsArrayLastIndexOf: jsArray().lastIndexOf(1) + + property list<string> jsonArrayMap: jsonArray.map(element => element.toString()) + property list<string> jsArrayMap: jsArray().map(element => element.toString()) + + property int jsonArrayReduce: jsonArray.reduce((acc, element) => acc - element, 40) + property int jsArrayReduce: jsArray().reduce((acc, element) => acc - element, 40) + + property string jsonArrayReduceRight: jsonArray.reduceRight((acc, element) => acc + element.toString(), "") + property string jsArrayReduceRight: jsArray().reduceRight((acc, element) => acc + element.toString(), "") + + property list<int> jsonArraySlice: jsonArray.slice(0, 1) + property list<int> jsArraySlice: jsArray().slice(0, 1) + + property bool jsonArraySome: jsonArray.some(element => element === 1) + property bool jsArraySome: jsArray().some(element => element === 1) + + property string stringifiedLocaleJsonArray: jsonArray.toLocaleString() + property string stringifiedLocaleJsArray: jsArray().toLocaleString() + + property string stringifiedJsonArray: jsonArray.toString() + property string stringifiedJsArray: jsArray().toString() + + property bool valuesMatch: { + var iterator = jsonArray.values(); + for (var obj of jsArray().values()) { + var v = iterator.next().value; + if (obj !== v) { + console.log(obj, v); + return false; + } + } + + var iterator = jsArray().values(); + for (var obj of jsonArray.values()) { + var v = iterator.next().value; + if (obj !== v) { + console.log(obj, v); + return false; + } + } + + return true; + } + + // In-place mutation methods. + // Set by onCompleted if mutating jsonArray and then accessing it + // respects the mutation and the mutation behaves as for an array. + property bool jsonArrayWasCopiedWithin: false + property bool jsonArrayWasFilled: false + property bool jsonArrayWasPopped: false + property bool jsonArrayWasPushed: false + property bool jsonArrayWasReversed: false + property bool jsonArrayWasShifted: false + property bool jsonArrayWasSpliced: false + property bool jsonArrayWasUnshifted: false + property bool jsonArrayWasSorted: false + + Component.onCompleted: { + function equals(lhs, rhs) { + return lhs.toString() === rhs.toString() + } + + jsonArray.forEach(element => jsonArrayForEach += "-" + element + "-"); + jsArray().forEach(element => jsArrayForEach += "-" + element + "-"); + + var array = jsArray() + + jsonArray.copyWithin(1, 0, 1) + array.copyWithin(1, 0, 1) + jsonArrayWasCopiedWithin = equals(jsonArray, array) + + jsonArray.fill(7, 0, 1) + array.fill(7, 0, 1) + jsonArrayWasFilled = equals(jsonArray, array) + + jsonArray.pop() + array.pop() + jsonArrayWasPopped = equals(jsonArray, array) + + jsonArray.push(23) + jsonArray.push(11) + jsonArray.push(54) + jsonArray.push(42) + array.push(23) + array.push(11) + array.push(54) + array.push(42) + jsonArrayWasPushed = equals(jsonArray, array) + + jsonArray.reverse() + array.reverse() + jsonArrayWasReversed = equals(jsonArray, array) + + jsonArray.shift() + array.shift() + jsonArrayWasShifted = equals(jsonArray, array) + + jsonArray.splice(2, 1, [1, 2], 7, [1, 5]) + array.splice(2, 1, [1, 2], 7, [1, 5]) + jsonArrayWasSpliced = equals(jsonArray, array) + + jsonArray.unshift(4, 71) + array.unshift(4, 71) + jsonArrayWasUnshifted = equals(jsonArray, array) + + jsonArray.sort() + array.sort() + jsonArrayWasSorted = equals(jsonArray, array) + } +} diff --git a/tests/auto/qml/qqmllanguage/data/longConversion.qml b/tests/auto/qml/qqmllanguage/data/longConversion.qml new file mode 100644 index 0000000000..fd9a9518ee --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/longConversion.qml @@ -0,0 +1,28 @@ +import QtQml +import Test + +QtObject { + property bool testProp: GetterObject.getFalse() + property bool testQProp: GetterObject.getQFalse() + + property bool fromLocal + property bool fromQLocal + + property bool fromBoolean + property bool fromQBoolean + + Component.onCompleted: { + let l = GetterObject.getFalse(); + fromLocal = l; + + let b = Boolean(l); + fromBoolean = b; + + let ql = GetterObject.getQFalse(); + fromQLocal = ql; + + + let qb = Boolean(ql); + fromQBoolean = qb; + } +} diff --git a/tests/auto/qml/qqmllanguage/data/manuallyCallSignalHandler.qml b/tests/auto/qml/qqmllanguage/data/manuallyCallSignalHandler.qml new file mode 100644 index 0000000000..1ee71e5fd2 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/manuallyCallSignalHandler.qml @@ -0,0 +1,11 @@ +import QtQml + +QtObject { + Component.onDestruction: { + console.log("evil!"); + } + + Component.onCompleted: { + Component.onDestruction() + } +} diff --git a/tests/auto/qml/qqmllanguage/data/nestedVectors.qml b/tests/auto/qml/qqmllanguage/data/nestedVectors.qml new file mode 100644 index 0000000000..0bcea52133 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/nestedVectors.qml @@ -0,0 +1,27 @@ +import Test +import QtQml + +NestedVectors { + id: self + + property var list1 + + Component.onCompleted: { + list1 = self.getList() + + let list2 = [] + let data1 = [] + data1.push(2) + data1.push(3) + data1.push(4) + + let data2 = [] + data2.push(5) + data2.push(6) + + list2.push(data1) + list2.push(data2) + + self.setList(list2) + } +} diff --git a/tests/auto/qml/qqmllanguage/data/objectDeletionNotify.1.qml b/tests/auto/qml/qqmllanguage/data/objectDeletionNotify.1.qml index acd5463a3c..ac5622f9fb 100644 --- a/tests/auto/qml/qqmllanguage/data/objectDeletionNotify.1.qml +++ b/tests/auto/qml/qqmllanguage/data/objectDeletionNotify.1.qml @@ -10,7 +10,7 @@ Item { } } - property bool expectNull: null + property bool expectNull: { return null; } function setExpectNull(b) { success = false; diff --git a/tests/auto/qml/qqmllanguage/data/objectDeletionNotify.2.qml b/tests/auto/qml/qqmllanguage/data/objectDeletionNotify.2.qml index ed0e0d10f0..3c18739c32 100644 --- a/tests/auto/qml/qqmllanguage/data/objectDeletionNotify.2.qml +++ b/tests/auto/qml/qqmllanguage/data/objectDeletionNotify.2.qml @@ -10,7 +10,7 @@ Item { } } - property bool expectNull: null + property bool expectNull: { return null; } function setExpectNull(b) { success = false; diff --git a/tests/auto/qml/qqmllanguage/data/objectDeletionNotify.3.qml b/tests/auto/qml/qqmllanguage/data/objectDeletionNotify.3.qml index f5e94ba715..e2e560199f 100644 --- a/tests/auto/qml/qqmllanguage/data/objectDeletionNotify.3.qml +++ b/tests/auto/qml/qqmllanguage/data/objectDeletionNotify.3.qml @@ -10,7 +10,7 @@ Item { } } - property bool expectNull: null + property bool expectNull: { return null; } function setExpectNull(b) { success = false; diff --git a/tests/auto/qml/qqmllanguage/data/objectInList.qml b/tests/auto/qml/qqmllanguage/data/objectInList.qml new file mode 100644 index 0000000000..53c8c3cdd1 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/objectInList.qml @@ -0,0 +1,17 @@ +import QtQml + +QtObject { + objectName: "parent" + property list<QtObject> child + property Component c: QtObject { objectName: "child" } + + function doCreate() { + child.push(c.createObject(null)); + } + + Component.onCompleted: { + // Extra function call so that the created object cannot be on the stack + doCreate(); + gc(); + } +} diff --git a/tests/auto/qml/qqmllanguage/data/objectMethodClone.qml b/tests/auto/qml/qqmllanguage/data/objectMethodClone.qml new file mode 100644 index 0000000000..e21179ea14 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/objectMethodClone.qml @@ -0,0 +1,23 @@ +import QtQml + +QtObject { + id: window + + property int doneClicks: 0 + + property UIToolBar t1: UIToolBar { + objectName: window.objectName + onDoneClicked: window.doneClicks++ + } + + property UIToolBar t2: UIToolBar { + objectName: window.objectName + onDoneClicked: window.doneClicks++ + } + + property Timer timer: Timer { + interval: 10 + running: true + onTriggered: window.objectName = "bar" + } +} diff --git a/tests/auto/qml/qqmllanguage/data/optimizedSequenceShift.qml b/tests/auto/qml/qqmllanguage/data/optimizedSequenceShift.qml new file mode 100644 index 0000000000..32765895a0 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/optimizedSequenceShift.qml @@ -0,0 +1,14 @@ +import QtQml + +QtObject { + id: root + + property int changes: 0 + + property list<int> numbers: [1, 2, 3, 4, 5] + onNumbersChanged: ++changes + + property var one: numbers.shift.bind([1,2,3])() + + Component.onCompleted: root.numbers.shift() +} diff --git a/tests/auto/qml/qqmllanguage/data/optionalChainCallOnNullProperty.qml b/tests/auto/qml/qqmllanguage/data/optionalChainCallOnNullProperty.qml new file mode 100644 index 0000000000..00029e3953 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/optionalChainCallOnNullProperty.qml @@ -0,0 +1,10 @@ +import QtQml + +QtObject { + id: root + property QtObject target: null + + Component.onCompleted: { + target?.destroy( ) + } +} diff --git a/tests/auto/qml/qqmllanguage/data/overrideDefaultProperty.qml b/tests/auto/qml/qqmllanguage/data/overrideDefaultProperty.qml new file mode 100644 index 0000000000..69f9316c51 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/overrideDefaultProperty.qml @@ -0,0 +1,6 @@ +import QtQuick + +Item { + property list<var> data: [] + Item {} +} diff --git a/tests/auto/qml/qqmllanguage/data/retainThis.qml b/tests/auto/qml/qqmllanguage/data/retainThis.qml new file mode 100644 index 0000000000..7a372ee236 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/retainThis.qml @@ -0,0 +1,49 @@ +import QmlOtherThis +import QtQml + +QtObject { + property var cppMethod: objA.greet + property var cppMethod2: objA.sum + + property Greeter a: Greeter { id: objA; objectName: "objA" } + property Greeter b: Greeter { id: objB; objectName: "objB" } + + function doCall() { + cppMethod.call(objB) + cppMethod2(5, 6) + } + + property var cppMethod3; + function doRetrieve(g) { + cppMethod3 = g.greet; + } + + function doCall2() { + cppMethod3(); + } + + property Greeter c: Greeter { + id: objC + objectName: "objC" + + property var cppMethod: objC.sum + + function doCall() { + cppMethod(7, 7) + } + } + + Component.onCompleted: { + doCall(); + doCall(); + + doRetrieve(objA); + doCall2(); + doRetrieve(objB); + doCall2(); + + objC.doCall(); + objC.cppMethod = objB.sum; + objC.doCall(); + } +} diff --git a/tests/auto/qml/qqmllanguage/data/signatureEnforced.qml b/tests/auto/qml/qqmllanguage/data/signatureEnforced.qml index e2ddd75b55..ec00d5d2b3 100644 --- a/tests/auto/qml/qqmllanguage/data/signatureEnforced.qml +++ b/tests/auto/qml/qqmllanguage/data/signatureEnforced.qml @@ -8,7 +8,7 @@ QtObject { property withLength withLength: 5 function a(r: rect) { - r.x = 77 // does not write back + r.x = 77 // does write back, but this is an evil thing to do. } function b(s: string) : int { @@ -28,8 +28,11 @@ QtObject { property int n: c(99) // creates a withLength property int o: rect.y + function bad(b: int) { return b } + Component.onCompleted: { a(rect) d(rect) + bad(15) } } diff --git a/tests/auto/qml/qqmllanguage/data/signatureIgnored.qml b/tests/auto/qml/qqmllanguage/data/signatureIgnored.qml index 0e9d0f42dc..bdb373040d 100644 --- a/tests/auto/qml/qqmllanguage/data/signatureIgnored.qml +++ b/tests/auto/qml/qqmllanguage/data/signatureIgnored.qml @@ -1,6 +1,6 @@ +pragma FunctionSignatureBehavior: Ignored import StaticTest import QtQml - QtObject { property rect rect: ({ x: 12, y: 13 }) property withLength withLength: 5 diff --git a/tests/auto/qml/qqmllanguage/data/singleton/RegisteredCompositeSingletonType.qml b/tests/auto/qml/qqmllanguage/data/singleton/RegisteredCompositeSingletonType.qml index 5359157b31..b86477e40a 100644 --- a/tests/auto/qml/qqmllanguage/data/singleton/RegisteredCompositeSingletonType.qml +++ b/tests/auto/qml/qqmllanguage/data/singleton/RegisteredCompositeSingletonType.qml @@ -1,5 +1,5 @@ // Copyright (C) 2013 BlackBerry Limited. All rights reserved. -// 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 pragma Singleton diff --git a/tests/auto/qml/qqmllanguage/data/singleton/js/jspragma.js b/tests/auto/qml/qqmllanguage/data/singleton/js/jspragma.js index 6e28624bb0..50ed2b0e66 100644 --- a/tests/auto/qml/qqmllanguage/data/singleton/js/jspragma.js +++ b/tests/auto/qml/qqmllanguage/data/singleton/js/jspragma.js @@ -1,5 +1,5 @@ // Copyright (C) 2013 BlackBerry Limited. All rights reserved. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only .pragma library diff --git a/tests/auto/qml/qqmllanguage/data/singletonTest17.qml b/tests/auto/qml/qqmllanguage/data/singletonTest17.qml index 4a987e31c0..7c7bffa4ac 100644 --- a/tests/auto/qml/qqmllanguage/data/singletonTest17.qml +++ b/tests/auto/qml/qqmllanguage/data/singletonTest17.qml @@ -1,5 +1,5 @@ // Copyright (C) 2013 BlackBerry Limited. All rights reserved. -// 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 org.qtproject.Test 1.0 diff --git a/tests/auto/qml/qqmllanguage/data/typedObjectList.qml b/tests/auto/qml/qqmllanguage/data/typedObjectList.qml new file mode 100644 index 0000000000..7e6f6e8dd9 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/typedObjectList.qml @@ -0,0 +1,10 @@ +import QtQml + +QtObject { + property var b; + property Component c: QtObject {} + + function returnList(a: Component) : list<Component> { return [a] } + + Component.onCompleted: b = { b: returnList(c) } +} diff --git a/tests/auto/qml/qqmllanguage/data/unregisteredValueTypeConversion.qml b/tests/auto/qml/qqmllanguage/data/unregisteredValueTypeConversion.qml new file mode 100644 index 0000000000..70484d3f0e --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/unregisteredValueTypeConversion.qml @@ -0,0 +1,10 @@ +import QtQml +import Test + +UnregisteredValueTypeHandler { + Component.onCompleted: { + consume(produce()) + consume(produceDerived()) + consume(produceGadgeted()) + } +} diff --git a/tests/auto/qml/qqmllanguage/data/variantObjectList.qml b/tests/auto/qml/qqmllanguage/data/variantObjectList.qml new file mode 100644 index 0000000000..9ec7d4f90f --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/variantObjectList.qml @@ -0,0 +1,17 @@ +import QtQml +import People + +QtObject { + id: root + + property QtObject b: QtObject { id: g1; objectName: "Leo Hodges" } + property QtObject c: QtObject { id: g2; objectName: "Jack Smith" } + property QtObject d: QtObject { id: g3; objectName: "Anne Brown" } + + property Component pc: Component { + id: partyComp + BirthdayParty {} + } + + property BirthdayParty q: partyComp.createObject(root, { guests: [g1, g2, g3] }) +} diff --git a/tests/auto/qml/qqmllanguage/testhelper/CMakeLists.txt b/tests/auto/qml/qqmllanguage/testhelper/CMakeLists.txt new file mode 100644 index 0000000000..6a58889335 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/testhelper/CMakeLists.txt @@ -0,0 +1,13 @@ +qt_policy(SET QTP0001 NEW) +qt_add_library(tst_qqmllanguage_qmlmodule STATIC) +qt_autogen_tools_initial_setup(tst_qqmllanguage_qmlmodule) +qt_add_qml_module(tst_qqmllanguage_qmlmodule + URI testhelper + VERSION 1.0 + SOURCES + "declarativelyregistered.h" + "declarativelyregistered.cpp" +) + +qt_autogen_tools_initial_setup(tst_qqmllanguage_qmlmoduleplugin) +target_link_libraries(tst_qqmllanguage PRIVATE tst_qqmllanguage_qmlmoduleplugin) diff --git a/tests/auto/qml/qqmllanguage/testhelper/declarativelyregistered.cpp b/tests/auto/qml/qqmllanguage/testhelper/declarativelyregistered.cpp new file mode 100644 index 0000000000..24fcd83d42 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/testhelper/declarativelyregistered.cpp @@ -0,0 +1,7 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include "declarativelyregistered.h" + +PurelyDeclarativeSingleton::PurelyDeclarativeSingleton() = default; + +#include "moc_declarativelyregistered.cpp" diff --git a/tests/auto/qml/qqmllanguage/testhelper/declarativelyregistered.h b/tests/auto/qml/qqmllanguage/testhelper/declarativelyregistered.h new file mode 100644 index 0000000000..4845cc68b9 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/testhelper/declarativelyregistered.h @@ -0,0 +1,20 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef DECLARATIVELYREGISTERED_LANGUAGE_H +#define DECLARATIVELYREGISTERED_LANGUAGE_H + +#include <QtCore/qobject.h> +#include <QtQml/qqmlregistration.h> + +class PurelyDeclarativeSingleton : public QObject +{ + Q_OBJECT + QML_SINGLETON + QML_ELEMENT +public: + PurelyDeclarativeSingleton(); +}; + + +#endif diff --git a/tests/auto/qml/qqmllanguage/testtypes.cpp b/tests/auto/qml/qqmllanguage/testtypes.cpp index f571429b07..526cca4b5b 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.cpp +++ b/tests/auto/qml/qqmllanguage/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" #include <private/qv4qmlcontext_p.h> @@ -55,6 +56,8 @@ void registerTypes() qmlRegisterTypeNotAvailable("Test",1,0,"UnavailableType", "UnavailableType is unavailable for testing"); + qmlRegisterTypesAndRevisions<DerivedFromUnexposedBase>("Test", 1); + qmlRegisterType<MyQmlObject>("Test.Version",1,0,"MyQmlObject"); qmlRegisterType<MyTypeObject>("Test.Version",1,0,"MyTypeObject"); qmlRegisterType<MyTypeObject>("Test.Version",2,0,"MyTypeObject"); @@ -92,6 +95,8 @@ void registerTypes() qmlRegisterType<MyArrayBufferTestClass>("Test", 1, 0, "MyArrayBufferTestClass"); + qmlRegisterTypesAndRevisions<EnumPropsManyUnderlyingTypes>("Test", 1); + qmlRegisterType<LazyDeferredSubObject>("Test", 1, 0, "LazyDeferredSubObject"); qmlRegisterType<DeferredProperties>("Test", 1, 0, "DeferredProperties"); qmlRegisterType<ImmediateProperties>("Test", 1, 0, "ImmediateProperties"); @@ -148,6 +153,35 @@ void registerTypes() qmlRegisterTypesAndRevisions<BaseValueType>("ValueTypes", 1); qmlRegisterTypesAndRevisions<DerivedValueType>("ValueTypes", 1); + qmlRegisterTypesAndRevisions<GetterObject>("Test", 1); + + qmlRegisterNamespaceAndRevisions(&TypedEnums::staticMetaObject, "TypedEnums", 1); + qmlRegisterTypesAndRevisions<ObjectWithEnums>("TypedEnums", 1); + qmlRegisterTypesAndRevisions<GadgetWithEnums>("TypedEnums", 1); + + QMetaType::registerConverter<UnregisteredValueDerivedType, UnregisteredValueBaseType>(); + qmlRegisterTypesAndRevisions<UnregisteredValueTypeHandler>("Test", 1); + + qmlRegisterTypesAndRevisions<Greeter>("QmlOtherThis", 1); + qmlRegisterTypesAndRevisions<BirthdayParty>("People", 1); + qmlRegisterTypesAndRevisions<AttachedInCtor>("Test", 1); + + qmlRegisterTypesAndRevisions<ByteArrayReceiver>("Test", 1); + + qmlRegisterTypesAndRevisions<Counter>("Test", 1); + + qmlRegisterTypesAndRevisions<Singleton>("EnumScopeTest", 1); + qmlRegisterTypesAndRevisions<NonSingleton>("EnumScopeTest", 1); + qmlRegisterTypesAndRevisions<EnumProviderSingletonQml>("EnumScopeTest", 1); + + qmlRegisterTypesAndRevisions<TypeWithQJsonArrayProperty>("TypeWithQJsonArrayProperty", 1); + qmlRegisterTypesAndRevisions< + InvokableSingleton, + InvokableExtended, + InvokableUncreatable, + InvokableValueType + >("Test", 1); + qmlRegisterTypesAndRevisions<NestedVectors>("Test", 1); } QVariant myCustomVariantTypeConverter(const QString &data) @@ -170,7 +204,7 @@ void CustomBinding::componentComplete() { Q_ASSERT(m_target); - foreach (const QV4::CompiledData::Binding *binding, bindings) { + for (const QV4::CompiledData::Binding *binding : std::as_const(bindings)) { QString name = compilationUnit->stringAt(binding->propertyNameIndex); int bindingId = binding->value.compiledScriptIndex; @@ -248,3 +282,19 @@ UncreatableSingleton *UncreatableSingleton::instance() static UncreatableSingleton instance; return &instance; } + +QT_BEGIN_NAMESPACE +const QMetaObject *QtPrivate::MetaObjectForType<FakeDynamicObject *, void>::metaObjectFunction(const QMetaTypeInterface *) +{ + static auto ptr = []{ + QMetaObjectBuilder builder(&FakeDynamicObject::staticMetaObject); + builder.setFlags(DynamicMetaObject); + auto mo = builder.toMetaObject(); + QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, [mo]() { + delete mo; + }); + return mo; + }(); + return ptr; +} +QT_END_NAMESPACE diff --git a/tests/auto/qml/qqmllanguage/testtypes.h b/tests/auto/qml/qqmllanguage/testtypes.h index 0777ed103d..ce6abf3504 100644 --- a/tests/auto/qml/qqmllanguage/testtypes.h +++ b/tests/auto/qml/qqmllanguage/testtypes.h @@ -1,11 +1,12 @@ // 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 #include <QtCore/qobject.h> #include <QtCore/qrect.h> #include <QtCore/qdatetime.h> +#include <QtCore/qjsonarray.h> #include <QtGui/qtransform.h> #include <QtGui/qcolor.h> #include <QtGui/qvector2d.h> @@ -18,6 +19,8 @@ #include <QtQml/qqmlpropertyvaluesource.h> #include <QtQml/qqmlscriptstring.h> #include <QtQml/qqmlproperty.h> + +#include <private/qqmlcomponentattached_p.h> #include <private/qqmlcustomparser_p.h> QVariant myCustomVariantTypeConverter(const QString &data); @@ -42,6 +45,77 @@ struct MyCustomVariantType }; Q_DECLARE_METATYPE(MyCustomVariantType); + +class Group : public QObject +{ + Q_OBJECT + + Q_PROPERTY(int value MEMBER value) + +public: + Group(QObject *parent = nullptr) : QObject(parent) {} + int value = 0; +}; + + +struct GroupGadget +{ + Q_GADGET + + Q_PROPERTY(int value MEMBER value) + +public: + friend bool operator==(GroupGadget g1, GroupGadget g2) { return g1.value == g2.value; } + friend bool operator!=(GroupGadget g1, GroupGadget g2) { return !(g1 == g2); } + int value = 0; +}; + +struct FakeDynamicObject : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString value MEMBER value) + +public: + FakeDynamicObject() {} + QString value; +}; + +QT_BEGIN_NAMESPACE +namespace QtPrivate { +// don't do this at home – we override the meta-object which QMetaType collects for +// FakeDynamicObject* properties +template<> +struct MetaObjectForType<FakeDynamicObject *, void> +{ + static const QMetaObject *metaObjectFunction(const QMetaTypeInterface *); +}; +} +QT_END_NAMESPACE + +class UnexposedBase : public QObject +{ + Q_OBJECT + + Q_PROPERTY(Group *group MEMBER group) + Q_PROPERTY(GroupGadget groupGadget MEMBER groupGadget) + Q_PROPERTY(FakeDynamicObject *dynamic MEMBER dynamic) +public: + UnexposedBase(QObject *parent = nullptr) : QObject(parent) + { + group = new Group(this); + } + Group *group; + GroupGadget groupGadget; + FakeDynamicObject *dynamic = nullptr; +}; + +class DerivedFromUnexposedBase : public UnexposedBase +{ + Q_OBJECT + QML_ELEMENT +}; + + class MyAttachedObject : public QObject { Q_OBJECT @@ -1292,6 +1366,40 @@ public: } }; +class EnumPropsManyUnderlyingTypes : public QObject +{ + Q_OBJECT + QML_ELEMENT +public: + enum si8 : qint8 { ResolvedValue = 1}; + enum ui8 : quint8 {}; + enum si16 : qint16 {}; + enum ui16 : quint16 {}; + enum ui64 : qint64 {}; + enum si64 : quint64 {}; + Q_ENUM(si8) + Q_ENUM(ui8) + Q_ENUM(si16) + Q_ENUM(ui16) + Q_ENUM(si64) + Q_ENUM(ui64) + + + Q_PROPERTY(si8 si8prop MEMBER si8prop) + Q_PROPERTY(ui8 ui8prop MEMBER ui8prop) + Q_PROPERTY(si16 si16prop MEMBER si16prop) + Q_PROPERTY(ui16 ui16prop MEMBER ui16prop) + Q_PROPERTY(si64 si64prop MEMBER si64prop) + Q_PROPERTY(ui64 ui64prop MEMBER ui64prop) + + si8 si8prop = si8(0); + ui8 ui8prop = ui8(0); + si16 si16prop = si16(0); + ui16 ui16prop = ui16(0); + si64 si64prop = si64(0); + ui64 ui64prop = ui64(0); +}; + Q_DECLARE_METATYPE(MyEnum2Class::EnumB) Q_DECLARE_METATYPE(MyEnum1Class::EnumA) Q_DECLARE_METATYPE(Qt::TextFormat) @@ -1547,6 +1655,7 @@ class BareSingleton : public QObject Q_OBJECT QML_SINGLETON QML_ELEMENT + QML_ADDED_IN_VERSION(1, 0) public: BareSingleton(QObject *parent = nullptr) : QObject(parent) @@ -1560,6 +1669,7 @@ class UncreatableSingleton : public QObject Q_OBJECT QML_SINGLETON QML_ELEMENT + QML_ADDED_IN_VERSION(1, 0) public: static UncreatableSingleton *instance(); @@ -2386,6 +2496,32 @@ public: } }; + +struct ForeignNamespace +{ + Q_GADGET +public: + enum Abc { A, B, C, D }; + Q_ENUM(Abc) +}; + +class ForeignNamespaceForeign +{ + Q_GADGET + QML_ELEMENT + QML_FOREIGN_NAMESPACE(ForeignNamespace) +}; + +class LeakingForeignNamespaceForeign : public QObject, public ForeignNamespaceForeign +{ + Q_OBJECT + QML_ELEMENT + +public: + enum AnotherAbc { D, C, B, A }; + Q_ENUM(AnotherAbc) +}; + struct ValueTypeWithLength { Q_GADGET @@ -2405,4 +2541,506 @@ private: int m_length = 19; }; +struct ValueTypeWithString +{ + Q_GADGET + QML_VALUE_TYPE(withString) + QML_CONSTRUCTIBLE_VALUE + +public: + Q_INVOKABLE ValueTypeWithString(const QString &v = QString()) : m_string(v) {} + QString toString() const { return m_string; } + +private: + QString m_string; +}; + +class GetterObject : public QObject { + Q_OBJECT + QML_ELEMENT + QML_SINGLETON +public: + explicit GetterObject(QObject *parent = nullptr) : QObject{parent} {} + + // always returns a 0 as uint64_t + Q_INVOKABLE uint64_t getFalse() const { return 0; } + Q_INVOKABLE uint64_t getTrue() const { return 1; } + + Q_INVOKABLE quint64 getQFalse() const { return 0; } + Q_INVOKABLE quint64 getQTrue() const { return 1; } +}; + +class EnumProviderSingleton : public QObject { + Q_OBJECT + +public: + enum class Expected { + Value = 42 + }; + Q_ENUM(Expected) + + EnumProviderSingleton(QObject* parent = nullptr) : QObject(parent) {} +}; + +class EnumProviderSingletonQml { + Q_GADGET + QML_FOREIGN(EnumProviderSingleton) + QML_NAMED_ELEMENT(EnumProviderSingleton) + QML_SINGLETON + +public: + static EnumProviderSingleton* create(QQmlEngine*, QJSEngine*) { + return new EnumProviderSingleton(); + } + +private: + EnumProviderSingletonQml() = default; +}; + + + +namespace TypedEnums { +Q_NAMESPACE +QML_ELEMENT + +enum E8S : qint8 { + E8SA = std::numeric_limits<qint8>::min(), + E8SB = -5, + E8SC = -1, + E8SD = 0, + E8SE = 1, + E8SF = 5, + E8SG = std::numeric_limits<qint8>::max(), +}; +Q_ENUM_NS(E8S); + +enum E8U : quint8 { + E8UA = 0, + E8UB = 1, + E8UC = 5, + E8UD = 1 << 7, + E8UE = std::numeric_limits<quint8>::max(), +}; +Q_ENUM_NS(E8U); + +enum E16S : qint16 { + E16SA = std::numeric_limits<qint16>::min(), + E16SB = -5, + E16SC = -1, + E16SD = 0, + E16SE = 1, + E16SF = 5, + E16SG = std::numeric_limits<qint16>::max(), +}; +Q_ENUM_NS(E16S); + +enum E16U : quint16 { + E16UA = 0, + E16UB = 1, + E16UC = 5, + E16UD = 1 << 15, + E16UE = std::numeric_limits<quint16>::max(), +}; +Q_ENUM_NS(E16U); + +enum E32S : qint32 { + E32SA = std::numeric_limits<qint32>::min(), + E32SB = -5, + E32SC = -1, + E32SD = 0, + E32SE = 1, + E32SF = 5, + E32SG = std::numeric_limits<qint32>::max(), +}; +Q_ENUM_NS(E32S); + +enum E32U : quint32 { + E32UA = 0, + E32UB = 1, + E32UC = 5, + E32UD = 1u << 31, + E32UE = std::numeric_limits<quint32>::max(), +}; +Q_ENUM_NS(E32U); + +enum E64S : qint64 { + E64SA = std::numeric_limits<qint64>::min(), + E64SB = -5, + E64SC = -1, + E64SD = 0, + E64SE = 1, + E64SF = 5, + E64SG = std::numeric_limits<qint64>::max(), +}; +Q_ENUM_NS(E64S); + +enum E64U : quint64 { + E64UA = 0, + E64UB = 1, + E64UC = 5, + E64UD = 1ull << 63, + E64UE = std::numeric_limits<quint64>::max(), +}; +Q_ENUM_NS(E64U); +} + +class GadgetWithEnums +{ + Q_GADGET + QML_VALUE_TYPE(gadgetWithEnums) + Q_PROPERTY(TypedEnums::E8S e8s MEMBER m_e8s); + Q_PROPERTY(TypedEnums::E8U e8u MEMBER m_e8u); + Q_PROPERTY(TypedEnums::E16S e16s MEMBER m_e16s); + Q_PROPERTY(TypedEnums::E16U e16u MEMBER m_e16u); + Q_PROPERTY(TypedEnums::E32S e32s MEMBER m_e32s); + Q_PROPERTY(TypedEnums::E32U e32u MEMBER m_e32u); + Q_PROPERTY(TypedEnums::E64S e64s MEMBER m_e64s); + Q_PROPERTY(TypedEnums::E64U e64u MEMBER m_e64u); +public: + TypedEnums::E8S m_e8s = {}; + TypedEnums::E8U m_e8u = {}; + TypedEnums::E16S m_e16s = {}; + TypedEnums::E16U m_e16u = {}; + TypedEnums::E32S m_e32s = {}; + TypedEnums::E32U m_e32u = {}; + TypedEnums::E64S m_e64s = {}; + TypedEnums::E64U m_e64u = {}; +private: + friend bool operator==(const GadgetWithEnums &a, const GadgetWithEnums &b) + { + return a.m_e8s == b.m_e8s && a.m_e8u == b.m_e8u && a.m_e16s == b.m_e16s + && a.m_e16u == b.m_e16u && a.m_e32s == b.m_e32s && a.m_e32u == b.m_e32u + && a.m_e64s == b.m_e64s && a.m_e64u == b.m_e64u; + } + friend bool operator!=(const GadgetWithEnums &a, const GadgetWithEnums &b) + { + return !(a == b); + } +}; + +class ObjectWithEnums : public QObject +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(TypedEnums::E8S e8s MEMBER m_e8s NOTIFY changed); + Q_PROPERTY(TypedEnums::E8U e8u MEMBER m_e8u NOTIFY changed); + Q_PROPERTY(TypedEnums::E16S e16s MEMBER m_e16s NOTIFY changed); + Q_PROPERTY(TypedEnums::E16U e16u MEMBER m_e16u NOTIFY changed); + Q_PROPERTY(TypedEnums::E32S e32s MEMBER m_e32s NOTIFY changed); + Q_PROPERTY(TypedEnums::E32U e32u MEMBER m_e32u NOTIFY changed); + Q_PROPERTY(TypedEnums::E64S e64s MEMBER m_e64s NOTIFY changed); + Q_PROPERTY(TypedEnums::E64U e64u MEMBER m_e64u NOTIFY changed); + Q_PROPERTY(GadgetWithEnums g MEMBER m_g NOTIFY changed); +public: + ObjectWithEnums(QObject *parent = nullptr) : QObject(parent) {} + TypedEnums::E8S m_e8s = {}; + TypedEnums::E8U m_e8u = {}; + TypedEnums::E16S m_e16s = {}; + TypedEnums::E16U m_e16u = {}; + TypedEnums::E32S m_e32s = {}; + TypedEnums::E32U m_e32u = {}; + TypedEnums::E64S m_e64s = {}; + TypedEnums::E64U m_e64u = {}; + GadgetWithEnums m_g; +Q_SIGNALS: + void changed(); +}; + +struct UnregisteredValueBaseType +{ + int foo = 12; +}; + +struct UnregisteredValueDerivedType: public UnregisteredValueBaseType +{ + int bar = 13; +}; + +struct GadgetedValueBaseType +{ + Q_GADGET + int foo = 12; +}; + +struct GadgetedValueDerivedType: public GadgetedValueBaseType +{ + Q_GADGET + int bar = 13; +}; + +class UnregisteredValueTypeHandler: public QObject +{ + Q_OBJECT + QML_ELEMENT +public: + int consumed = 0; + int gadgeted = 0; + +public slots: + UnregisteredValueBaseType produce() { return UnregisteredValueBaseType(); } + UnregisteredValueDerivedType produceDerived() { return UnregisteredValueDerivedType(); } + void consume(UnregisteredValueBaseType) { ++consumed; } + + GadgetedValueDerivedType produceGadgeted() { return GadgetedValueDerivedType(); } + void consume(GadgetedValueBaseType) { ++gadgeted; } +}; + +class Greeter : public QObject +{ + Q_OBJECT + QML_ELEMENT + +public: + Greeter(QObject *parent = nullptr) : QObject(parent) {} + + Q_INVOKABLE void greet() + { + qDebug().noquote() << objectName() << "says hello"; + } + + Q_INVOKABLE void sum(int a, int b) + { + qDebug().noquote() << objectName() << QString("says %1 + %2 = %3").arg(a).arg(b).arg(a + b); + } +}; + +class Attachment : public QObject { + Q_OBJECT +public: + Attachment(QObject *parent = nullptr) : QObject(parent) {} +}; + +class AttachedInCtor : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_ATTACHED(Attachment) + +public: + AttachedInCtor(QObject *parent = nullptr) + : QObject(parent) + { + attached = qmlAttachedPropertiesObject<AttachedInCtor>(this, true); + } + + static Attachment *qmlAttachedProperties(QObject *object) { + return new Attachment(object); + } + + QObject *attached = nullptr; +}; + +class BirthdayParty : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQmlListProperty<QObject> guests READ guests) + Q_CLASSINFO("DefaultProperty", "guests") + QML_ELEMENT + +public: + using QObject::QObject; + QQmlListProperty<QObject> guests() { return {this, &m_guests}; } + qsizetype guestCount() const { return m_guests.count(); } + QObject *guest(qsizetype i) const { return m_guests.at(i); } + +private: + QList<QObject *> m_guests; +}; + +class ByteArrayReceiver : public QObject +{ + Q_OBJECT + QML_ELEMENT + +public: + QList<QByteArray> byteArrays; + + Q_INVOKABLE void byteArrayTest(const QByteArray &ba) + { + byteArrays.push_back(ba); + } +}; + +class CounterAttachedBaseType: public QObject +{ + Q_OBJECT + QML_ANONYMOUS + Q_PROPERTY (int value READ value NOTIFY valueChanged) + +public: + CounterAttachedBaseType(QObject *parent = nullptr) : QObject(parent) {} + + int value() { return m_value; } + Q_SIGNAL void valueChanged(); + +protected: + int m_value = 98; +}; + + +class CounterAttachedType: public CounterAttachedBaseType +{ + Q_OBJECT + QML_ANONYMOUS + +public: + CounterAttachedType(QObject *parent = nullptr) : CounterAttachedBaseType(parent) {} + + Q_INVOKABLE void increase() { + ++m_value; + Q_EMIT valueChanged(); + } +}; + +class Counter : public QObject +{ + Q_OBJECT + QML_ATTACHED(CounterAttachedBaseType) + QML_ELEMENT + +public: + static CounterAttachedBaseType *qmlAttachedProperties(QObject *o) + { + return new CounterAttachedType(o); + } +}; + + +class Singleton: public QObject +{ + Q_OBJECT + Q_PROPERTY(EnumType enumProperty READ enumProperty CONSTANT) + Q_CLASSINFO("RegisterEnumClassesUnscoped", "false") + QML_ELEMENT + QML_SINGLETON +public: + explicit Singleton(QObject* parent = nullptr) : QObject(parent) {} + enum class EnumType { + EnumValue1, + EnumValue2 + }; + Q_ENUM(EnumType); + EnumType enumProperty() const { + return EnumType::EnumValue2; + } +}; + +class NonSingleton: public QObject +{ + Q_OBJECT + Q_PROPERTY(EnumType enumProperty READ enumProperty CONSTANT) + Q_CLASSINFO("RegisterEnumClassesUnscoped", "false") + QML_ELEMENT +public: + explicit NonSingleton(QObject* parent = nullptr) : QObject(parent) {} + enum class EnumType { + EnumValue1, + EnumValue2 + }; + Q_ENUM(EnumType); + EnumType enumProperty() const { + return EnumType::EnumValue2; + } +}; + +class TypeWithQJsonArrayProperty : public QObject { + Q_OBJECT + QML_ELEMENT + + Q_PROPERTY(QJsonArray jsonArray READ jsonArray WRITE setJsonArray NOTIFY jsonArrayChanged) + +public: + TypeWithQJsonArrayProperty(QObject *parent = nullptr) : QObject(parent) {} + + const QJsonArray& jsonArray() { return m_jsonArray; } + void setJsonArray(const QJsonArray& a) { m_jsonArray = a; } + +signals: + void jsonArrayChanged(); + +private: + QJsonArray m_jsonArray; +}; + +class InvokableSingleton : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_SINGLETON +public: + InvokableSingleton() = default; + Q_INVOKABLE InvokableSingleton(int a, QObject *parent) : QObject(parent), m_a(a) {} + + int m_a = 0; +}; + +class InvokableExtension : public QObject +{ + Q_OBJECT +public: + Q_INVOKABLE InvokableExtension(QObject *parent = nullptr) : QObject(parent) {} +}; + +class InvokableExtended : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_EXTENDED(InvokableExtension) + +public: + Q_INVOKABLE InvokableExtended() = default; +}; + +class InvokableUncreatable : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("no") + +public: + Q_INVOKABLE InvokableUncreatable() = default; +}; + +class InvokableValueType +{ + Q_GADGET + QML_VALUE_TYPE(vv) +public: + Q_INVOKABLE InvokableValueType() = default; + Q_INVOKABLE InvokableValueType(const QString &s) : m_s(s) {} + QString m_s; +}; + +class NestedVectors : public QObject +{ + Q_OBJECT + QML_ELEMENT +public: + NestedVectors(QObject *parent = nullptr) : QObject(parent) + { + std::vector<int> data; + data.push_back(1); + data.push_back(2); + data.push_back(3); + m_list.push_back(data); + data.clear(); + data.push_back(4); + data.push_back(5); + m_list.push_back(data); + } + + Q_INVOKABLE std::vector<std::vector<int>> getList() + { + return m_list; + } + + Q_INVOKABLE void setList(std::vector<std::vector<int>> list) + { + m_list = list; + } + +private: + std::vector<std::vector<int>> m_list; +}; + #endif // TESTTYPES_H diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index fee65fcb17..2212a40eee 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> @@ -37,7 +38,7 @@ #include <deque> -#if defined(Q_OS_MAC) +#if defined(Q_OS_DARWIN) #include <unistd.h> #endif @@ -47,7 +48,7 @@ DEFINE_BOOL_CONFIG_OPTION(qmlCheckTypes, QML_CHECK_TYPES) static inline bool isCaseSensitiveFileSystem(const QString &path) { Q_UNUSED(path); -#if defined(Q_OS_MAC) +#if defined(Q_OS_DARWIN) return pathconf(path.toLatin1().constData(), _PC_CASE_SENSITIVE); #elif defined(Q_OS_WIN) return false; @@ -248,6 +249,8 @@ private slots: void compositeSingletonSelectors(); void compositeSingletonRegistered(); void compositeSingletonCircular(); + void compositeSingletonRequiredProperties(); + void compositeSingletonRequiredProperties_data(); void singletonsHaveContextAndEngine(); @@ -316,6 +319,7 @@ private slots: void inlineComponentFoundBeforeOtherImports(); void inlineComponentDuplicateNameError(); void inlineComponentWithAliasInstantiatedWithNewProperties(); + void inlineComponentWithImplicitComponent(); void selfReference(); void selfReferencingSingleton(); @@ -358,6 +362,8 @@ private slots: void hangOnWarning(); + void groupPropertyFromNonExposedBaseClass(); + void listEnumConversion(); void deepInlineComponentScriptBinding(); @@ -402,10 +408,62 @@ 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(); + + void nestedVectors(); + void optimizedSequenceShift(); + private: QQmlEngine engine; QStringList defaultImportPathList; @@ -492,11 +550,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 +1439,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 +1765,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 +1786,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 +2097,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 +2132,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 +2641,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 +2921,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 +3920,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 +3949,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 +4097,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 +4284,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 +4302,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 +4547,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); @@ -4844,6 +4917,36 @@ void tst_qqmllanguage::compositeSingletonCircular() QCOMPARE(o->property("value").toInt(), 2); } +void tst_qqmllanguage::compositeSingletonRequiredProperties() +{ + QFETCH(QString, warning); + QFETCH(QString, singletonName); + QQmlEngine engine; + engine.addImportPath(dataDirectory()); + { + QTest::ignoreMessage(QtMsgType::QtWarningMsg, qPrintable(warning)); + std::unique_ptr<QObject> singleton {engine.singletonInstance<QObject *>( + "SingletonWithRequiredProperties", + singletonName + )}; + QVERIFY(!singleton); + } +} + +void tst_qqmllanguage::compositeSingletonRequiredProperties_data() +{ + QTest::addColumn<QString>("warning"); + QTest::addColumn<QString>("singletonName"); + + QString warning1 = testFileUrl("SingletonWithRequiredProperties/SingletonWithRequired1.qml").toString() + + ":5:5: Required property i was not initialized"; + QString warning2 = testFileUrl("SingletonWithRequiredProperties/SingletonWithRequired2.qml").toString() + + ":6:9: Required property i was not initialized"; + + QTest::addRow("toplevelRequired") << warning1 << "SingletonWithRequired1"; + QTest::addRow("subObjectRequired") << warning2 << "SingletonWithRequired2"; +} + void tst_qqmllanguage::singletonsHaveContextAndEngine() { QObject *qmlSingleton = nullptr; @@ -5348,24 +5451,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()); @@ -5654,6 +5763,9 @@ void tst_qqmllanguage::retrieveQmlTypeId() QVERIFY(qmlTypeId("Test", 1, 0, "MyExtendedUncreateableBaseClass") >= 0); QVERIFY(qmlTypeId("Test", 1, 0, "MyUncreateableBaseClass") >= 0); QVERIFY(qmlTypeId("Test", 1, 0, "MyTypeObjectSingleton") >= 0); + + // Must also work for declaratively registered types whose module wasn't imported so far + QVERIFY(qmlTypeId("testhelper", 1, 0, "PurelyDeclarativeSingleton") >= 0); } void tst_qqmllanguage::polymorphicFunctionLookup() @@ -5749,7 +5861,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 +5870,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 +5884,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 +5921,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 +6208,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 +6515,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 +6873,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 +6897,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 +6917,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 +7568,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 +7882,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 +7945,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 +8013,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 +8052,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 +8061,906 @@ 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 QML/point using a type " + "assertion. This behavior is deprecated. Add 'pragma " + "ValueTypeBehavior: Assertable' to prevent it."_L1)); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":14: Coercing between incompatible value types mistakenly " + "yields null rather than undefined. Add 'pragma " + "ValueTypeBehavior: Assertable' to prevent this."_L1)); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":15: Coercing from instances of object types to value " + "types mistakenly yields null rather than undefined. Add " + "'pragma ValueTypeBehavior: Assertable' to prevent " + "this."_L1)); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":16: Coercing a value to QML/size 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)); + QTest::ignoreMessage( + QtWarningMsg, + "Could not find any constructor for value type QQmlSizeFValueType to call " + "with value 11"); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":18: Coercing a value to QML/size using a type " + "assertion. This behavior is deprecated. Add 'pragma " + "ValueTypeBehavior: Assertable' to prevent it."_L1)); + + QTest::ignoreMessage( + QtWarningMsg, + qPrintable(url.toString() + ":19: Coercing a value to QML/size 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"); + + const QVariant p = o->property("p"); + QCOMPARE(p.metaType(), QMetaType::fromType<std::nullptr_t>()); + + const QVariant q = o->property("q"); + QCOMPARE(q.metaType(), QMetaType::fromType<std::nullptr_t>()); + + const QVariant r = o->property("r"); + QCOMPARE(r.metaType(), QMetaType::fromType<QSizeF>()); + QCOMPARE(r.value<QSizeF>(), QSizeF()); + + const QVariant s = o->property("s"); + QCOMPARE(s.metaType(), QMetaType()); + + const QVariant t = o->property("t"); + QCOMPARE(t.metaType(), QMetaType::fromType<QSizeF>()); + QCOMPARE(t.value<QSizeF>(), QSizeF()); + + const QVariant u = o->property("u"); + QCOMPARE(u.metaType(), QMetaType::fromType<QSizeF>()); + QCOMPARE(u.value<QSizeF>(), QSizeF()); +} + +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()); + QVERIFY(!o->property("p").isValid()); + QVERIFY(!o->property("q").isValid()); + QVERIFY(!o->property("r").isValid()); + QVERIFY(!o->property("s").isValid()); + QVERIFY(!o->property("t").isValid()); + QVERIFY(!o->property("u").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("concatenatedJsonArray"), o->property("concatenatedJsArray")); + 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()); + 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); +} + +void tst_qqmllanguage::nestedVectors() +{ + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("nestedVectors.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + NestedVectors *n = qobject_cast<NestedVectors *>(o.data()); + QVERIFY(n); + + const std::vector<std::vector<int>> expected1 { { 1, 2, 3 }, { 4, 5 } }; + const QVariant list1 = n->property("list1"); + QCOMPARE(list1.metaType(), QMetaType::fromType<std::vector<std::vector<int>>>()); + QCOMPARE(list1.value<std::vector<std::vector<int>>>(), expected1); + + const std::vector<std::vector<int>> expected2 { { 2, 3, 4 }, { 5, 6 } }; + QCOMPARE(n->getList(), expected2); +} + +void tst_qqmllanguage::optimizedSequenceShift() +{ + QQmlEngine e; + QQmlComponent c(&e, testFileUrl("optimizedSequenceShift.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + QCOMPARE(o->property("changes").toInt(), 2); + + const QVariant one = o->property("one"); + QCOMPARE(one.metaType(), QMetaType::fromType<int>()); + QCOMPARE(one.toInt(), 1); +} + QTEST_MAIN(tst_qqmllanguage) #include "tst_qqmllanguage.moc" |