aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2021-11-23 19:00:29 +0100
committerUlf Hermann <ulf.hermann@qt.io>2021-12-03 12:09:26 +0100
commit58ff7aa4fed5b3a27b6a859fc02f9ee27dd1a6cc (patch)
treecd8d455066d5e9e6d14988e945f4ba421765b743
parentc2f50cf90d143d96313ab3cfdbb9e4552a095cbb (diff)
Compile QML files ahead of time with qmlcachegen
qmlcachegen compiles bindings and functions to C++ as far as QQmlJSAotCompiler can. It does respect "pragma Strict" and rejects the file if it's violated. Furthermore, it sets up the logger to follow the qt.qml.compiler.aot logging category. By default it's completely silent. Compiling the examples with qmlcachegen exposes a bug in the type resolver where it returns an invalid generic type. It should never do that. Fix it by returning JSValue. [ChangeLog][QtQml][Important Behavior Changes] QML bindings and functions are now compiled to C++ by qmlcachegen, if possible. Use the qt.qml.compiler.aot logging category to receive diagnostics about the compilation. Task-number: QTBUG-98305 Change-Id: I6953812c3fd20b68339617a5714fcbe16a384360 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
-rw-r--r--src/qml/doc/src/cmake/qt_add_qml_module.qdoc10
-rw-r--r--tests/auto/qml/CMakeLists.txt1
-rw-r--r--tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp5
-rw-r--r--tests/auto/qml/qmlcppcodegen/CMakeLists.txt11
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/AccessModelMethodsFromOutside.qml48
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/BaseMember.qml9
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/BindingExpression.qml59
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt134
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/Dummy.qml44
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/Enums.qml29
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/Panel.qml156
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/ProgressBar/Keyframe.qml6
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/ProgressBar/KeyframeGroup.qml8
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/ProgressBar/ProgressBar.ui.qml175
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/ProgressBar/Root.qml152
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/ProgressBar/Timeline.qml10
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/ProgressBar/TimelineAnimation.qml6
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/ProgressBar/built-with-Qt_Large.pngbin0 -> 49186 bytes
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/RootWithoutId.qml33
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/Test.qml17
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/TestCase.qml1987
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/aliasLookup.qml8
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/anchorsFill.qml12
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/array.qml6
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/asCast.qml50
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/attachedBaseEnum.qml5
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/bindToValueType.qml9
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/birthdayparty.cpp87
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/birthdayparty.h117
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/callContextPropertyLookupResult.qml5
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/childobject.qml15
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/colorAsVariant.qml7
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/colorString.qml13
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/componentReturnType.qml15
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/compositeTypeMethod.qml12
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/compositesingleton.qml8
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/construct.qml12
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/contextParam.qml5
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/conversions.qml130
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/conversions2.qml93
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/cppbaseclass.h63
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/curlygrouped.qml7
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/deadShoeSize.qml6
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/dialog.qml21
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/dynamicscene.qml33
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/enumInvalid.qml6
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/enumScope.qml5
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/enumsInOtherObject.qml9
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/enumsUser.qml8
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/equalsUndefined.qml18
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/excessiveParameters.qml14
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/extendedTypes.qml11
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/failures.qml15
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/fileDialog.qml73
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/funcWithParams.qml7
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/globals.qml24
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/idAccess.qml15
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/immediateQuit.qml8
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/imports/Module/NotCompiled.qml6
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/imports/Module/qmldir2
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/imports/QmlBench/Globals.qml10
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/imports/QmlBench/qmldir2
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/importsFromImportPath.qml5
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/intEnumCompare.qml9
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/intOverflow.qml7
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/interactive.qml68
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/interceptor.qml53
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/jsMathObject.qml64
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/jsimport.qml6
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/jsmoduleimport.qml7
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/layouts.qml131
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/library.js4
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/listIndices.qml14
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/listlength.qml8
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/math.qml6
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/methods.qml56
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/modulePrefix.qml10
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/noQQmlData.qml10
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/nonNotifyable.qml14
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/noscope.qml10
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/nullAccess.qml7
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/objectwithmethod.h77
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/outOfBounds.qml6
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/overriddenMember.qml6
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/ownProperty.qml12
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/page.qml41
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/parentProp.qml25
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/person.cpp59
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/person.h61
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/pressAndHoldButton.qml38
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/propertyChanges.qml9
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/registerelimination.qml44
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/scopeVsObject.qml6
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/script.js6
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/script.mjs4
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/shared/Slider.qml122
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/shifts.qml9
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/signal.qml6
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/signalHandler.qml10
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/specificParent.qml11
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/stringLength.qml6
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/testlogger.js76
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/text.qml20
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/undefinedResets.qml6
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/unknownParameter.qml7
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/unusedAttached.qml5
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/urlString.qml13
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/valueTypeProperty.qml6
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/voidfunction.qml6
-rw-r--r--tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp1623
-rw-r--r--tools/qmlcachegen/qmlcachegen.cpp116
111 files changed, 6763 insertions, 34 deletions
diff --git a/src/qml/doc/src/cmake/qt_add_qml_module.qdoc b/src/qml/doc/src/cmake/qt_add_qml_module.qdoc
index f7690e89d6..49d85f8f42 100644
--- a/src/qml/doc/src/cmake/qt_add_qml_module.qdoc
+++ b/src/qml/doc/src/cmake/qt_add_qml_module.qdoc
@@ -259,6 +259,16 @@ set_target_properties(someTarget PROPERTIES
This explicitly selects qmlcachegen as the program to be used, even if
better alternatives are available.
+Furthermore, you can pass extra arguments to qmlcachegen, by setting the
+\c QT_QMLCACHEGEN_ARGUMENTS option. In particular, the \c --only-bytecode
+option will turn off compilation of QML script code to C++. For example:
+
+\badcode
+set_target_properties(someTarget PROPERTIES
+ QT_QMLCACHEGEN_ARGUMENTS "--only-bytecode"
+)
+\endcode
+
\target qmllint-auto
\section2 Linting QML sources
diff --git a/tests/auto/qml/CMakeLists.txt b/tests/auto/qml/CMakeLists.txt
index 0a43b809a7..51f824c49d 100644
--- a/tests/auto/qml/CMakeLists.txt
+++ b/tests/auto/qml/CMakeLists.txt
@@ -70,6 +70,7 @@ if(QT_FEATURE_private_tests)
# Calls qt6_target_qml_sources() directly, which needs CMake 3.19+
add_subdirectory(qmlcachegen)
endif()
+ add_subdirectory(qmlcppcodegen)
add_subdirectory(animation)
add_subdirectory(qqmlecmascript)
add_subdirectory(qqmlanybinding)
diff --git a/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp b/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp
index 0ae3195234..70b1c259e1 100644
--- a/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp
+++ b/tests/auto/qml/qmlcachegen/tst_qmlcachegen.cpp
@@ -736,6 +736,7 @@ void tst_qmlcachegen::inlineComponent()
QVERIFY2(ok, errors);
QQmlEngine engine;
CleanlyLoadingComponent component(&engine, testFileUrl("inlineComponentWithId.qml"));
+ QVERIFY2(component.isReady(), qPrintable(component.errorString()));
QTest::ignoreMessage(QtMsgType::QtInfoMsg, "42");
QScopedPointer<QObject> obj(component.create());
QVERIFY(!obj.isNull());
@@ -749,7 +750,9 @@ void tst_qmlcachegen::posthocRequired()
CleanlyLoadingComponent component(&engine, testFileUrl("posthocrequired.qml"));
QScopedPointer<QObject> obj(component.create());
QVERIFY(obj.isNull() && component.isError());
- QVERIFY(component.errorString().contains(QStringLiteral("Required property x was not initialized")));
+ QVERIFY2(component.errorString().contains(
+ QStringLiteral("Required property x was not initialized")),
+ qPrintable(component.errorString()));
}
void tst_qmlcachegen::scriptStringCachegenInteraction()
diff --git a/tests/auto/qml/qmlcppcodegen/CMakeLists.txt b/tests/auto/qml/qmlcppcodegen/CMakeLists.txt
new file mode 100644
index 0000000000..4c4cf0b1e5
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/CMakeLists.txt
@@ -0,0 +1,11 @@
+add_subdirectory(data)
+
+qt_internal_add_test(tst_qmlcppcodegen
+ SOURCES
+ tst_qmlcppcodegen.cpp
+ LIBRARIES
+ Qt::Qml
+ Qt::Gui
+ codegen_test_module
+ codegen_test_moduleplugin
+)
diff --git a/tests/auto/qml/qmlcppcodegen/data/AccessModelMethodsFromOutside.qml b/tests/auto/qml/qmlcppcodegen/data/AccessModelMethodsFromOutside.qml
new file mode 100644
index 0000000000..6683e0be45
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/AccessModelMethodsFromOutside.qml
@@ -0,0 +1,48 @@
+/******************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Quick Ultralite module.
+**
+** $QT_BEGIN_LICENSE:COMM$
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+import QtQuick 2.15
+
+Item {
+ property real cost1: fruitModel.get(1).cost
+ property string name1: fruitModel.get(1).name
+ property real cost2: fruitModel.get(2).cost
+ property string name2: fruitModel.get(2).name
+ ListModel {
+ id: fruitModel
+
+ ListElement {
+ name: "Apple"
+ cost: 2.2
+ }
+ ListElement {
+ name: "Orange"
+ cost: 3
+ }
+ ListElement {
+ name: "Banana"
+ cost: 1.95
+ }
+ }
+ Component.onCompleted: {
+ console.log(fruitModel.data(fruitModel.index(1, 0), 0))
+ console.log(fruitModel.get(0).name)
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/BaseMember.qml b/tests/auto/qml/qmlcppcodegen/data/BaseMember.qml
new file mode 100644
index 0000000000..cf2567452a
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/BaseMember.qml
@@ -0,0 +1,9 @@
+pragma Strict
+import QtQml
+
+QtObject {
+ id: self
+ property int ppp: 3
+ property int ppp2: ppp * 3
+ property int ppp3: self.ppp * 4
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/BindingExpression.qml b/tests/auto/qml/qmlcppcodegen/data/BindingExpression.qml
new file mode 100644
index 0000000000..f4961037f4
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/BindingExpression.qml
@@ -0,0 +1,59 @@
+/******************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Quick Ultralite module.
+**
+** $QT_BEGIN_LICENSE:COMM$
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+import QtQuick 2.15
+
+Dummy {
+ id: root
+ y : 10;
+ width : 200;
+ height : root.width;
+
+ Dummy {
+ id: child;
+ height: 100;
+ y: root.y + (root.height - child.height) / 2;
+ }
+
+ property string mass: {
+ var result = "nothing";
+ if (child.y > 100)
+ result = "heavy";
+ else
+ result = "light";
+ return result;
+ }
+
+ property real test_division: width / 1000 + 50;
+ property real test_ternary: false ? 1 : true ? 2.2 : 1; // the type must be real
+
+ property int test_switch: {
+ switch (width % 3) {
+ case 0:
+ return 130;
+ case 1:
+ return 380;
+ case 2:
+ return 630;
+ }
+ }
+}
+
diff --git a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt
new file mode 100644
index 0000000000..fa147be78f
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt
@@ -0,0 +1,134 @@
+set(cpp_sources
+ birthdayparty.cpp
+ birthdayparty.h
+ cppbaseclass.h
+ objectwithmethod.h
+ person.cpp
+ person.h
+)
+
+set(qml_files
+ AccessModelMethodsFromOutside.qml
+ BaseMember.qml
+ BindingExpression.qml
+ Dummy.qml
+ Enums.qml
+ Panel.qml
+ ProgressBar/Keyframe.qml
+ ProgressBar/KeyframeGroup.qml
+ ProgressBar/ProgressBar.ui.qml
+ ProgressBar/Root.qml
+ ProgressBar/Timeline.qml
+ ProgressBar/TimelineAnimation.qml
+ RootWithoutId.qml
+ Test.qml
+ TestCase.qml
+ aliasLookup.qml
+ anchorsFill.qml
+ array.qml
+ asCast.qml
+ attachedBaseEnum.qml
+ bindToValueType.qml
+ callContextPropertyLookupResult.qml
+ childobject.qml
+ colorAsVariant.qml
+ colorString.qml
+ componentReturnType.qml
+ compositeTypeMethod.qml
+ compositesingleton.qml
+ construct.qml
+ contextParam.qml
+ conversions.qml
+ conversions2.qml
+ curlygrouped.qml
+ deadShoeSize.qml
+ dialog.qml
+ dynamicscene.qml
+ enumInvalid.qml
+ enumScope.qml
+ enumsInOtherObject.qml
+ enumsUser.qml
+ equalsUndefined.qml
+ excessiveParameters.qml
+ extendedTypes.qml
+ failures.qml
+ fileDialog.qml
+ funcWithParams.qml
+ globals.qml
+ idAccess.qml
+ immediateQuit.qml
+ imports/QmlBench/Globals.qml
+ importsFromImportPath.qml
+ intEnumCompare.qml
+ intOverflow.qml
+ interactive.qml
+ interceptor.qml
+ jsMathObject.qml
+ jsimport.qml
+ jsmoduleimport.qml
+ layouts.qml
+ library.js
+ listIndices.qml
+ listlength.qml
+ math.qml
+ methods.qml
+ modulePrefix.qml
+ noQQmlData.qml
+ nonNotifyable.qml
+ noscope.qml
+ nullAccess.qml
+ outOfBounds.qml
+ overriddenMember.qml
+ ownProperty.qml
+ page.qml
+ parentProp.qml
+ pressAndHoldButton.qml
+ registerelimination.qml
+ scopeVsObject.qml
+ script.js
+ script.mjs
+ shared/Slider.qml
+ shifts.qml
+ signal.qml
+ signalHandler.qml
+ specificParent.qml
+ stringLength.qml
+ testlogger.js
+ text.qml
+ undefinedResets.qml
+ unknownParameter.qml
+ unusedAttached.qml
+ urlString.qml
+ valueTypeProperty.qml
+ voidfunction.qml
+)
+
+set(resource_files
+ ProgressBar/built-with-Qt_Large.png
+ imports/QmlBench/qmldir
+)
+
+set_source_files_properties("shared/Slider.qml"
+ PROPERTIES QT_RESOURCE_ALIAS "Slider.qml"
+)
+
+qt_add_library(codegen_test_module STATIC)
+qt_autogen_tools_initial_setup(codegen_test_module)
+
+set_target_properties(codegen_test_module PROPERTIES
+ # We really want qmlcachegen here, even if qmlsc is available
+ QT_QMLCACHEGEN_EXECUTABLE qmlcachegen
+)
+
+qt6_add_qml_module(codegen_test_module
+ VERSION 1.0
+ URI TestTypes
+ IMPORT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/imports/"
+ SOURCES
+ ${cpp_sources}
+ QML_FILES
+ ${qml_files}
+ RESOURCES
+ ${resource_files}
+ OUTPUT_DIRECTORY TestTypes # Make sure tst_qmlcachegen doesn't see our output
+)
diff --git a/tests/auto/qml/qmlcppcodegen/data/Dummy.qml b/tests/auto/qml/qmlcppcodegen/data/Dummy.qml
new file mode 100644
index 0000000000..09d4a6eb51
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/Dummy.qml
@@ -0,0 +1,44 @@
+/******************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt JavaScript to C++ compiler.
+**
+** $QT_BEGIN_LICENSE:COMM$
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+import QtQuick
+
+Item {
+ enum DummyEnum { DummyValue1, DummyValue2, DummyValue3 = 33 }
+ property int value
+ property Dummy child
+ property int dummyEnum
+
+ component Group: QtObject {
+ property Item item;
+ }
+
+ property Group group
+
+ signal triggered()
+ signal signalWithArg(int one, bool two)
+ property real onValue
+ property real offValue
+
+ function someFunction(a: int, b: bool, c: Dummy, d: real, e: int) : int { return 42 }
+ property string strProp
+ function concat(a: string, b: string) : string { return a + b }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/Enums.qml b/tests/auto/qml/qmlcppcodegen/data/Enums.qml
new file mode 100644
index 0000000000..0324e56ae9
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/Enums.qml
@@ -0,0 +1,29 @@
+import QtQml
+import QtQuick.Layouts
+
+QtObject {
+ id: root
+ property int appState: Enums.AppState.Blue
+ property string color: "blue"
+
+ enum AppState {
+ Red,
+ Green,
+ Blue
+ }
+
+ onAppStateChanged: {
+ if (appState === Enums.AppState.Green)
+ root.color = "green"
+ else if (appState === Enums.AppState.Red)
+ root.color = "red"
+ }
+
+ property Timer timer: Timer {
+ onTriggered: root.appState = Enums.AppState.Green
+ running: true
+ interval: 100
+ }
+
+ Layout.alignment: Qt.AlignCenter
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/Panel.qml b/tests/auto/qml/qmlcppcodegen/data/Panel.qml
new file mode 100644
index 0000000000..e28a98327d
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/Panel.qml
@@ -0,0 +1,156 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+
+Item {
+ property string name
+ property var notes
+
+ property real horizontalVelocity: 0
+
+ id: page
+
+ Image {
+ fillMode: Image.PreserveAspectCrop
+ clip: true
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ onClicked: page.focus = false;
+ }
+
+ Item {
+ property font font
+ property color color
+ property int style
+ property color styleColor
+ objectName: page.name; x: 15; y: 8; height: 40; width: 370
+ font.pixelSize: 18; font.bold: true; color: "white"
+ style: Text.Outline; styleColor: "black"
+ }
+
+ Repeater {
+ model: page.notes
+ Item {
+ id: stickyPage
+ required property string noteText
+
+ property int randomX: Math.random() * (page.ListView.view.width-0.5*stickyImage.width) +100
+ property int randomY: Math.random() * (page.ListView.view.height-0.5*stickyImage.height) +50
+
+ x: randomX; y: randomY
+
+ rotation: -page.horizontalVelocity / 100
+ Behavior on rotation {
+ SpringAnimation { spring: 2.0; damping: 0.15 }
+ }
+
+ Item {
+ id: sticky
+ scale: 0.7
+
+ Image {
+ id: stickyImage
+ x: 8 + -width * 0.6 / 2; y: -20
+ source: "note-yellow.png"
+ scale: 0.6; transformOrigin: Item.TopLeft
+ }
+
+ Item {
+ property font font
+ property bool readOnly
+ property string text
+ id: myText
+ x: -104; y: 36; width: 215; height: 200
+ font.pixelSize: 24
+ readOnly: false
+ rotation: -8
+ text: stickyPage.noteText
+ }
+
+ Item {
+ x: stickyImage.x; y: -20
+ width: stickyImage.width * stickyImage.scale
+ height: stickyImage.height * stickyImage.scale
+
+ MouseArea {
+ id: mouse
+ anchors.fill: parent
+ drag.target: stickyPage
+ drag.minimumY: 0
+ drag.maximumY: page.height - 80
+ drag.minimumX: 100
+ drag.maximumX: page.width - 140
+ onClicked: myText.forceActiveFocus()
+ }
+ }
+ }
+
+ Image {
+ x: -width / 2; y: -height * 0.5 / 2
+ source: "tack.png"
+ scale: 0.7; transformOrigin: Item.TopLeft
+ }
+
+ states: State {
+ name: "pressed"
+ when: mouse.pressed
+ PropertyChanges { target: sticky; rotation: 8; scale: 1 }
+ PropertyChanges { target: page; z: 8 }
+ }
+
+ transitions: Transition {
+ NumberAnimation { properties: "rotation,scale"; duration: 200 }
+ }
+ }
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/ProgressBar/Keyframe.qml b/tests/auto/qml/qmlcppcodegen/data/ProgressBar/Keyframe.qml
new file mode 100644
index 0000000000..0076031fcc
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/ProgressBar/Keyframe.qml
@@ -0,0 +1,6 @@
+import QtQuick
+
+UniformAnimator {
+ property real frame
+ property var value
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/ProgressBar/KeyframeGroup.qml b/tests/auto/qml/qmlcppcodegen/data/ProgressBar/KeyframeGroup.qml
new file mode 100644
index 0000000000..5cf857f622
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/ProgressBar/KeyframeGroup.qml
@@ -0,0 +1,8 @@
+import QtQml
+
+QtObject {
+ property QtObject target
+ property string property
+ default property list<Keyframe> keyframes
+ property url keyframeSource
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/ProgressBar/ProgressBar.ui.qml b/tests/auto/qml/qmlcppcodegen/data/ProgressBar/ProgressBar.ui.qml
new file mode 100644
index 0000000000..3815af1c2e
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/ProgressBar/ProgressBar.ui.qml
@@ -0,0 +1,175 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Design Studio.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.9
+import QtQuick.Window 2.3
+import QtQuick.Layouts 1.0
+import QtQuick.Controls 2.3
+
+Rectangle {
+ visible: true
+ width: 640
+ height: 480
+ color: "#242424"
+
+ ColumnLayout {
+ x: 20
+ y: 152
+ spacing: 20
+
+ Root {
+ id: root
+ }
+
+ Root {
+ id: root1
+ }
+
+ Root {
+ id: root2
+ }
+ }
+
+ Timeline {
+ id: timeline
+ enabled: true
+ endFrame: 4000
+ startFrame: 0
+
+ KeyframeGroup {
+ target: root
+ property: "progress"
+
+ Keyframe {
+ value: 10
+ frame: 0
+ }
+
+ Keyframe {
+ easing.bezierCurve: [0.86, 0.00, 0.07, 1.00, 1, 1]
+ value: 90
+ frame: 2000
+ }
+
+ Keyframe {
+ easing.bezierCurve: [0.86, 0.00, 0.07, 1.00, 1, 1]
+ value: 10
+ frame: 4000
+ }
+ }
+
+ KeyframeGroup {
+ target: root1
+ property: "progress"
+
+ Keyframe {
+ easing.bezierCurve: [0.17, 0.84, 0.44, 1.00, 1, 1]
+ value: 90
+ frame: 4000
+ }
+
+ Keyframe {
+ easing.bezierCurve: [0.17, 0.84, 0.44, 1.00, 1, 1]
+ value: 20
+ frame: 2000
+ }
+
+ Keyframe {
+ value: 90
+ frame: 0
+ }
+ }
+
+ KeyframeGroup {
+ target: root2
+ property: "progress"
+
+ Keyframe {
+ value: 15
+ frame: 0
+ }
+
+ Keyframe {
+ easing.bezierCurve: [0.79, 0.14, 0.15, 0.86, 1, 1]
+ value: 85
+ frame: 2000
+ }
+
+ Keyframe {
+ easing.bezierCurve: [0.79, 0.14, 0.15, 0.86, 1, 1]
+ value: 15
+ frame: 4000
+ }
+ }
+
+ animations: [
+ TimelineAnimation {
+ id: propertyAnimation
+ target: timeline
+ property: "currentFrame"
+ running: true
+ to: timeline.endFrame
+ from: timeline.startFrame
+ loops: -1
+ duration: 1000
+ }
+ ]
+ }
+
+ Image {
+ id: image
+ x: 518
+ y: 0
+ width: 102
+ height: 137
+ fillMode: Image.PreserveAspectFit
+ source: "built-with-Qt_Large.png"
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/ProgressBar/Root.qml b/tests/auto/qml/qmlcppcodegen/data/ProgressBar/Root.qml
new file mode 100644
index 0000000000..ab0ecacc4d
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/ProgressBar/Root.qml
@@ -0,0 +1,152 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the examples of the Qt Design Studio.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.9
+
+Item {
+ id: root
+ width: 600
+ height: 65
+ property alias progress: timeline.currentFrame
+
+ Rectangle {
+ id: pb_back
+ color: "#9f9f9f"
+ radius: 4
+ anchors.right: parent.right
+ anchors.rightMargin: 0
+ anchors.left: parent.left
+ anchors.leftMargin: 0
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 0
+ anchors.top: parent.top
+ anchors.topMargin: 30
+
+ Rectangle {
+ id: pb_front
+ width: 200
+ color: "#ffffff"
+ anchors.bottomMargin: 5
+ anchors.left: parent.left
+ anchors.leftMargin: 5
+ anchors.bottom: parent.bottom
+ anchors.top: parent.top
+ anchors.topMargin: 5
+ }
+ }
+
+ Text {
+ id: text1
+ color: "#ffffff"
+ text: Math.round(root.progress)
+ font.pixelSize: 18
+ }
+
+ Timeline {
+ id: timeline
+ enabled: true
+ startFrame: 0
+ endFrame: 100
+
+ KeyframeGroup {
+ target: text1
+ property: "color"
+
+ Keyframe {
+ value: "#8de98d"
+ frame: 0
+ }
+
+ Keyframe {
+ value: "#de4f4f"
+ frame: 50
+ }
+
+ Keyframe {
+ value: "#f0c861"
+ frame: 100
+ }
+ }
+
+ KeyframeGroup {
+ target: pb_front
+ property: "width"
+
+ Keyframe {
+ value: 0
+ frame: 0
+ }
+
+ Keyframe {
+ value: 590
+ frame: 100
+ }
+ }
+
+ KeyframeGroup {
+ target: pb_front
+ property: "color"
+ Keyframe {
+ value: "#8de98d"
+ frame: 0
+ }
+
+ Keyframe {
+ value: "#de4f4f"
+ frame: 50
+ }
+
+ Keyframe {
+ value: "#f0c861"
+ frame: 100
+ }
+ }
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/ProgressBar/Timeline.qml b/tests/auto/qml/qmlcppcodegen/data/ProgressBar/Timeline.qml
new file mode 100644
index 0000000000..503d0aefa4
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/ProgressBar/Timeline.qml
@@ -0,0 +1,10 @@
+import QtQml
+
+QtObject {
+ property real startFrame
+ property real endFrame
+ property real currentFrame
+ default property list<KeyframeGroup> keyframeGroupes
+ property list<TimelineAnimation> animations
+ property bool enabled
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/ProgressBar/TimelineAnimation.qml b/tests/auto/qml/qmlcppcodegen/data/ProgressBar/TimelineAnimation.qml
new file mode 100644
index 0000000000..b97e8956bf
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/ProgressBar/TimelineAnimation.qml
@@ -0,0 +1,6 @@
+import QtQuick
+
+NumberAnimation {
+ property bool pingPong
+ signal finished()
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/ProgressBar/built-with-Qt_Large.png b/tests/auto/qml/qmlcppcodegen/data/ProgressBar/built-with-Qt_Large.png
new file mode 100644
index 0000000000..75ec0fa080
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/ProgressBar/built-with-Qt_Large.png
Binary files differ
diff --git a/tests/auto/qml/qmlcppcodegen/data/RootWithoutId.qml b/tests/auto/qml/qmlcppcodegen/data/RootWithoutId.qml
new file mode 100644
index 0000000000..c350fc3a4b
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/RootWithoutId.qml
@@ -0,0 +1,33 @@
+/******************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Quick Ultralite module.
+**
+** $QT_BEGIN_LICENSE:COMM$
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+import QtQuick 2.15
+
+Item {
+ property bool foo: false
+
+ property alias bar: item.visible
+
+ Item {
+ id: item
+ visible: parent.foo
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/Test.qml b/tests/auto/qml/qmlcppcodegen/data/Test.qml
new file mode 100644
index 0000000000..d7263a2bff
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/Test.qml
@@ -0,0 +1,17 @@
+pragma Strict
+import TestTypes 1.0
+
+CppBaseClass {
+ enum EE {
+ AA, BB, CC
+ }
+
+ property int foo: 1 + 2
+ property int ppp: 4
+
+ // constant, binding will be removed.
+ cppProp: 3 + 4
+
+ // An actual binding. Can't be removed because cppProp may be manually set.
+ cppProp2: cppProp * 2
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/TestCase.qml b/tests/auto/qml/qmlcppcodegen/data/TestCase.qml
new file mode 100644
index 0000000000..82e830ba88
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/TestCase.qml
@@ -0,0 +1,1987 @@
+/******************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt JavaScript to C++ compiler.
+**
+** $QT_BEGIN_LICENSE:COMM$
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+import QtQuick 2.0
+import QtQuick.Window 2.0 // used for qtest_verifyItem
+import QtTest 1.2
+import "testlogger.js" as TestLogger
+
+/*!
+ \qmltype TestCase
+ \inqmlmodule QtTest
+ \brief Represents a unit test case.
+ \since 4.8
+ \ingroup qtquicktest
+
+ \section1 Introduction to QML Test Cases
+
+ Test cases are written as JavaScript functions within a TestCase
+ type:
+
+ \code
+ import QtQuick 2.0
+ import QtTest 1.2
+
+ TestCase {
+ name: "MathTests"
+
+ function test_math() {
+ compare(2 + 2, 4, "2 + 2 = 4")
+ }
+
+ function test_fail() {
+ compare(2 + 2, 5, "2 + 2 = 5")
+ }
+ }
+ \endcode
+
+ Functions whose names start with "test_" are treated as test cases
+ to be executed. The \l name property is used to prefix the functions
+ in the output:
+
+ \code
+ ********* Start testing of MathTests *********
+ Config: Using QTest library 4.7.2, Qt 4.7.2
+ PASS : MathTests::initTestCase()
+ FAIL! : MathTests::test_fail() 2 + 2 = 5
+ Actual (): 4
+ Expected (): 5
+ Loc: [/home/.../tst_math.qml(12)]
+ PASS : MathTests::test_math()
+ PASS : MathTests::cleanupTestCase()
+ Totals: 3 passed, 1 failed, 0 skipped
+ ********* Finished testing of MathTests *********
+ \endcode
+
+ Because of the way JavaScript properties work, the order in which the
+ test functions are found is unpredictable. To assist with predictability,
+ the test framework will sort the functions on ascending order of name.
+ This can help when there are two tests that must be run in order.
+
+ Multiple TestCase types can be supplied. The test program will exit
+ once they have all completed. If a test case doesn't need to run
+ (because a precondition has failed), then \l optional can be set to true.
+
+ \section1 Data-driven Tests
+
+ Table data can be provided to a test using a function name that ends
+ with "_data". Alternatively, the \c init_data() function can be used
+ to provide default test data for all test functions in a TestCase type:
+
+
+ \code
+ import QtQuick 2.0
+ import QtTest 1.2
+
+ TestCase {
+ name: "DataTests"
+
+ function init_data() {
+ return [
+ {tag:"init_data_1", a:1, b:2, answer: 3},
+ {tag:"init_data_2", a:2, b:4, answer: 6}
+ ];
+ }
+
+ function test_table_data() {
+ return [
+ {tag: "2 + 2 = 4", a: 2, b: 2, answer: 4 },
+ {tag: "2 + 6 = 8", a: 2, b: 6, answer: 8 },
+ ]
+ }
+
+ function test_table(data) {
+ //data comes from test_table_data
+ compare(data.a + data.b, data.answer)
+ }
+
+ function test__default_table(data) {
+ //data comes from init_data
+ compare(data.a + data.b, data.answer)
+ }
+ }
+ \endcode
+
+ The test framework will iterate over all of the rows in the table
+ and pass each row to the test function. As shown, the columns can be
+ extracted for use in the test. The \c tag column is special - it is
+ printed by the test framework when a row fails, to help the reader
+ identify which case failed amongst a set of otherwise passing tests.
+
+ \section1 Benchmarks
+
+ Functions whose names start with "benchmark_" will be run multiple
+ times with the Qt benchmark framework, with an average timing value
+ reported for the runs. This is equivalent to using the \c{QBENCHMARK}
+ macro in the C++ version of QTestLib.
+
+ \code
+ TestCase {
+ id: top
+ name: "CreateBenchmark"
+
+ function benchmark_create_component() {
+ var component = Qt.createComponent("item.qml")
+ var obj = component.createObject(top)
+ obj.destroy()
+ component.destroy()
+ }
+ }
+
+ RESULT : CreateBenchmark::benchmark_create_component:
+ 0.23 msecs per iteration (total: 60, iterations: 256)
+ PASS : CreateBenchmark::benchmark_create_component()
+ \endcode
+
+ To get the effect of the \c{QBENCHMARK_ONCE} macro, prefix the test
+ function name with "benchmark_once_".
+
+ \section1 Simulating Keyboard and Mouse Events
+
+ The keyPress(), keyRelease(), and keyClick() methods can be used
+ to simulate keyboard events within unit tests. The events are
+ delivered to the currently focused QML item. You can pass either
+ a Qt.Key enum value or a latin1 char (string of length one)
+
+ \code
+ Rectangle {
+ width: 50; height: 50
+ focus: true
+
+ TestCase {
+ name: "KeyClick"
+ when: windowShown
+
+ function test_key_click() {
+ keyClick(Qt.Key_Left)
+ keyClick("a")
+ ...
+ }
+ }
+ }
+ \endcode
+
+ The mousePress(), mouseRelease(), mouseClick(), mouseDoubleClickSequence()
+ and mouseMove() methods can be used to simulate mouse events in a
+ similar fashion.
+
+ \b{Note:} keyboard and mouse events can only be delivered once the
+ main window has been shown. Attempts to deliver events before then
+ will fail. Use the \l when and windowShown properties to track
+ when the main window has been shown.
+
+ \section1 Managing Dynamically Created Test Objects
+
+ A typical pattern with QML tests is to
+ \l {Dynamic QML Object Creation from JavaScript}{dynamically create}
+ an item and then destroy it at the end of the test function:
+
+ \code
+ TestCase {
+ id: testCase
+ name: "MyTest"
+ when: windowShown
+
+ function test_click() {
+ var item = Qt.createQmlObject("import QtQuick 2.0; Item {}", testCase);
+ verify(item);
+
+ // Test item...
+
+ item.destroy();
+ }
+ }
+ \endcode
+
+ The problem with this pattern is that any failures in the test function
+ will cause the call to \c item.destroy() to be skipped, leaving the item
+ hanging around in the scene until the test case has finished. This can
+ result in interference with future tests; for example, by blocking input
+ events or producing unrelated debug output that makes it difficult to
+ follow the code's execution.
+
+ By calling \l createTemporaryQmlObject() instead, the object is guaranteed
+ to be destroyed at the end of the test function:
+
+ \code
+ TestCase {
+ id: testCase
+ name: "MyTest"
+ when: windowShown
+
+ function test_click() {
+ var item = createTemporaryQmlObject("import QtQuick 2.0; Item {}", testCase);
+ verify(item);
+
+ // Test item...
+
+ // Don't need to worry about destroying "item" here.
+ }
+ }
+ \endcode
+
+ For objects that are created via the \l {Component::}{createObject()} function
+ of \l Component, the \l createTemporaryObject() function can be used.
+
+ \sa {QtTest::SignalSpy}{SignalSpy}, {Qt Quick Test}
+*/
+
+
+Item {
+ id: testCase
+ visible: false
+ TestUtil {
+ id:util
+ }
+
+ /*!
+ \qmlproperty string TestCase::name
+
+ This property defines the name of the test case for result reporting.
+ The default value is an empty string.
+
+ \code
+ TestCase {
+ name: "ButtonTests"
+ ...
+ }
+ \endcode
+ */
+ property string name
+
+ /*!
+ \qmlproperty bool TestCase::when
+
+ This property should be set to true when the application wants
+ the test cases to run. The default value is true. In the following
+ example, a test is run when the user presses the mouse button:
+
+ \code
+ Rectangle {
+ id: foo
+ width: 640; height: 480
+ color: "cyan"
+
+ MouseArea {
+ id: area
+ anchors.fill: parent
+ }
+
+ property bool bar: true
+
+ TestCase {
+ name: "ItemTests"
+ when: area.pressed
+ id: test1
+
+ function test_bar() {
+ verify(bar)
+ }
+ }
+ }
+ \endcode
+
+ The test application will exit once all \l TestCase types
+ have been triggered and have run. The \l optional property can
+ be used to exclude a \l TestCase type.
+
+ \sa optional, completed
+ */
+ property bool when: true
+
+ /*!
+ \qmlproperty bool TestCase::completed
+
+ This property will be set to true once the test case has completed
+ execution. Test cases are only executed once. The initial value
+ is false.
+
+ \sa running, when
+ */
+ property bool completed: false
+
+ /*!
+ \qmlproperty bool TestCase::running
+
+ This property will be set to true while the test case is running.
+ The initial value is false, and the value will become false again
+ once the test case completes.
+
+ \sa completed, when
+ */
+ property bool running: false
+
+ /*!
+ \qmlproperty bool TestCase::optional
+
+ Multiple \l TestCase types can be supplied in a test application.
+ The application will exit once they have all completed. If a test case
+ does not need to run (because a precondition has failed), then this
+ property can be set to true. The default value is false.
+
+ \code
+ TestCase {
+ when: false
+ optional: true
+ function test_not_run() {
+ verify(false)
+ }
+ }
+ \endcode
+
+ \sa when, completed
+ */
+ property bool optional: false
+
+ /*!
+ \qmlproperty bool TestCase::windowShown
+
+ This property will be set to true after the QML viewing window has
+ been displayed. Normally test cases run as soon as the test application
+ is loaded and before a window is displayed. If the test case involves
+ visual types and behaviors, then it may need to be delayed until
+ after the window is shown.
+
+ \code
+ Button {
+ id: button
+ onClicked: text = "Clicked"
+ TestCase {
+ name: "ClickTest"
+ when: windowShown
+ function test_click() {
+ button.clicked();
+ compare(button.text, "Clicked");
+ }
+ }
+ }
+ \endcode
+ */
+ property bool windowShown: QTestRootObject.windowShown
+
+ // Internal private state. Identifiers prefixed with qtest are reserved.
+ /*! \internal */
+ property bool qtest_prevWhen: true
+ /*! \internal */
+ property int qtest_testId: -1
+ /*! \internal */
+ property bool qtest_componentCompleted : false
+ /*! \internal */
+ property var qtest_testCaseResult
+ /*! \internal */
+ property var qtest_results: qtest_results_normal
+ /*! \internal */
+ TestResult { id: qtest_results_normal }
+ /*! \internal */
+ property var qtest_events: qtest_events_normal
+ TestEvent { id: qtest_events_normal }
+ /*! \internal */
+ property var qtest_temporaryObjects: []
+
+ /*!
+ \qmlmethod TestCase::fail(message = "")
+
+ Fails the current test case, with the optional \a message.
+ Similar to \c{QFAIL(message)} in C++.
+ */
+ function fail(msg) {
+ if (msg === undefined)
+ msg = "";
+ qtest_results.fail(msg, util.callerFile(), util.callerLine())
+ throw new Error("QtQuickTest::fail")
+ }
+
+ /*! \internal */
+ function qtest_fail(msg, frame) {
+ if (msg === undefined)
+ msg = "";
+ qtest_results.fail(msg, util.callerFile(frame), util.callerLine(frame))
+ throw new Error("QtQuickTest::fail")
+ }
+
+ /*!
+ \qmlmethod TestCase::verify(condition, message = "")
+
+ Fails the current test case if \a condition is false, and
+ displays the optional \a message. Similar to \c{QVERIFY(condition)}
+ or \c{QVERIFY2(condition, message)} in C++.
+ */
+ function verify(cond, msg) {
+ if (arguments.length > 2)
+ qtest_fail("More than two arguments given to verify(). Did you mean tryVerify() or tryCompare()?", 1)
+
+ if (msg === undefined)
+ msg = "";
+ if (!qtest_results.verify(cond, msg, util.callerFile(), util.callerLine()))
+ throw new Error("QtQuickTest::fail")
+ }
+
+ /*!
+ \since 5.8
+ \qmlmethod TestCase::tryVerify(function, timeout = 5000, message = "")
+
+ Fails the current test case if \a function does not evaluate to
+ \c true before the specified \a timeout (in milliseconds) has elapsed.
+ The function is evaluated multiple times until the timeout is
+ reached. An optional \a message is displayed upon failure.
+
+ This function is intended for testing applications where a condition
+ changes based on asynchronous events. Use verify() for testing
+ synchronous condition changes, and tryCompare() for testing
+ asynchronous property changes.
+
+ For example, in the code below, it's not possible to use tryCompare(),
+ because the \c currentItem property might be \c null for a short period
+ of time:
+
+ \code
+ tryCompare(listView.currentItem, "text", "Hello");
+ \endcode
+
+ Instead, we can use tryVerify() to first check that \c currentItem
+ isn't \c null, and then use a regular compare afterwards:
+
+ \code
+ tryVerify(function(){ return listView.currentItem })
+ compare(listView.currentItem.text, "Hello")
+ \endcode
+
+ \sa verify(), compare(), tryCompare(), SignalSpy::wait()
+ */
+ function tryVerify(expressionFunction, timeout, msg) {
+ if (!expressionFunction || !(expressionFunction instanceof Function)) {
+ qtest_results.fail("First argument must be a function", util.callerFile(), util.callerLine())
+ throw new Error("QtQuickTest::fail")
+ }
+
+ if (timeout && typeof(timeout) !== "number") {
+ qtest_results.fail("timeout argument must be a number", util.callerFile(), util.callerLine())
+ throw new Error("QtQuickTest::fail")
+ }
+
+ if (msg && typeof(msg) !== "string") {
+ qtest_results.fail("message argument must be a string", util.callerFile(), util.callerLine())
+ throw new Error("QtQuickTest::fail")
+ }
+
+ if (!timeout)
+ timeout = 5000
+
+ if (msg === undefined)
+ msg = "function returned false"
+
+ if (!expressionFunction())
+ wait(0)
+
+ var i = 0
+ while (i < timeout && !expressionFunction()) {
+ wait(50)
+ i += 50
+ }
+
+ if (!qtest_results.verify(expressionFunction(), msg, util.callerFile(), util.callerLine()))
+ throw new Error("QtQuickTest::fail")
+ }
+
+ /*!
+ \since 5.13
+ \qmlmethod bool TestCase::isPolishScheduled(object item)
+
+ Returns \c true if \l {QQuickItem::}{updatePolish()} has not been called
+ on \a item since the last call to \l {QQuickItem::}{polish()},
+ otherwise returns \c false.
+
+ When assigning values to properties in QML, any layouting the item
+ must do as a result of the assignment might not take effect immediately,
+ but can instead be postponed until the item is polished. For these cases,
+ you can use this function to ensure that the item has been polished
+ before the execution of the test continues. For example:
+
+ \code
+ verify(isPolishScheduled(item))
+ verify(waitForItemPolished(item))
+ \endcode
+
+ Without the call to \c isPolishScheduled() above, the
+ call to \c waitForItemPolished() might see that no polish
+ was scheduled and therefore pass instantly, assuming that
+ the item had already been polished. This function
+ makes it obvious why an item wasn't polished and allows tests to
+ fail early under such circumstances.
+
+ \sa waitForItemPolished(), QQuickItem::polish(), QQuickItem::updatePolish()
+ */
+ function isPolishScheduled(item) {
+ if (!item || typeof item !== "object") {
+ qtest_results.fail("Argument must be a valid Item; actual type is " + typeof item,
+ util.callerFile(), util.callerLine())
+ throw new Error("QtQuickTest::fail")
+ }
+
+ return qtest_results.isPolishScheduled(item)
+ }
+
+ /*!
+ \since 5.13
+ \qmlmethod bool waitForItemPolished(object item, int timeout = 5000)
+
+ Waits for \a timeout milliseconds or until
+ \l {QQuickItem::}{updatePolish()} has been called on \a item.
+
+ Returns \c true if \c updatePolish() was called on \a item within
+ \a timeout milliseconds, otherwise returns \c false.
+
+ \sa isPolishScheduled(), QQuickItem::polish(), QQuickItem::updatePolish()
+ */
+ function waitForItemPolished(item, timeout) {
+ if (!item || typeof item !== "object") {
+ qtest_results.fail("First argument must be a valid Item; actual type is " + typeof item,
+ util.callerFile(), util.callerLine())
+ throw new Error("QtQuickTest::fail")
+ }
+
+ if (timeout !== undefined && typeof(timeout) != "number") {
+ qtest_results.fail("Second argument must be a number; actual type is " + typeof timeout,
+ util.callerFile(), util.callerLine())
+ throw new Error("QtQuickTest::fail")
+ }
+
+ if (!timeout)
+ timeout = 5000
+
+ return qtest_results.waitForItemPolished(item, timeout)
+ }
+
+ /*!
+ \since 5.9
+ \qmlmethod object TestCase::createTemporaryQmlObject(string qml, object parent, string filePath)
+
+ This function dynamically creates a QML object from the given \a qml
+ string with the specified \a parent. The returned object will be
+ destroyed (if it was not already) after \l cleanup() has finished
+ executing, meaning that objects created with this function are
+ guaranteed to be destroyed after each test, regardless of whether or
+ not the tests fail.
+
+ If there was an error while creating the object, \c null will be
+ returned.
+
+ If \a filePath is specified, it will be used for error reporting for
+ the created object.
+
+ This function calls
+ \l {QtQml::Qt::createQmlObject()}{Qt.createQmlObject()} internally.
+
+ \sa {Managing Dynamically Created Test Objects}
+ */
+ function createTemporaryQmlObject(qml, parent, filePath) {
+ if (typeof qml !== "string") {
+ qtest_results.fail("First argument must be a string of QML; actual type is " + typeof qml,
+ util.callerFile(), util.callerLine());
+ throw new Error("QtQuickTest::fail");
+ }
+
+ if (!parent || typeof parent !== "object") {
+ qtest_results.fail("Second argument must be a valid parent object; actual type is " + typeof parent,
+ util.callerFile(), util.callerLine());
+ throw new Error("QtQuickTest::fail");
+ }
+
+ if (filePath !== undefined && typeof filePath !== "string") {
+ qtest_results.fail("Third argument must be a file path string; actual type is " + typeof filePath,
+ util.callerFile(), util.callerLine());
+ throw new Error("QtQuickTest::fail");
+ }
+
+ var object = Qt.createQmlObject(qml, parent, filePath);
+ qtest_temporaryObjects.push(object);
+ return object;
+ }
+
+ /*!
+ \since 5.9
+ \qmlmethod object TestCase::createTemporaryObject(Component component, object parent, object properties)
+
+ This function dynamically creates a QML object from the given
+ \a component with the specified optional \a parent and \a properties.
+ The returned object will be destroyed (if it was not already) after
+ \l cleanup() has finished executing, meaning that objects created with
+ this function are guaranteed to be destroyed after each test,
+ regardless of whether or not the tests fail.
+
+ If there was an error while creating the object, \c null will be
+ returned.
+
+ This function calls
+ \l {QtQml::Component::createObject()}{component.createObject()}
+ internally.
+
+ \sa {Managing Dynamically Created Test Objects}
+ */
+ function createTemporaryObject(component, parent, properties) {
+ if (typeof component !== "object") {
+ qtest_results.fail("First argument must be a Component; actual type is " + typeof component,
+ util.callerFile(), util.callerLine());
+ throw new Error("QtQuickTest::fail");
+ }
+
+ if (properties && typeof properties !== "object") {
+ qtest_results.fail("Third argument must be an object; actual type is " + typeof properties,
+ util.callerFile(), util.callerLine());
+ throw new Error("QtQuickTest::fail");
+ }
+
+ var object = component.createObject(parent, properties ? properties : ({}));
+ qtest_temporaryObjects.push(object);
+ return object;
+ }
+
+ /*!
+ \internal
+
+ Destroys all temporary objects that still exist.
+ */
+ function qtest_destroyTemporaryObjects() {
+ for (var i = 0; i < qtest_temporaryObjects.length; ++i) {
+ var temporaryObject = qtest_temporaryObjects[i];
+ // ### the typeof check can be removed when QTBUG-57749 is fixed
+ if (temporaryObject && typeof temporaryObject.destroy === "function")
+ temporaryObject.destroy();
+ }
+ qtest_temporaryObjects = [];
+ }
+
+ /*! \internal */
+ // Determine what is o.
+ // Discussions and reference: http://philrathe.com/articles/equiv
+ // Test suites: http://philrathe.com/tests/equiv
+ // Author: Philippe Rathé <prathe@gmail.com>
+ function qtest_typeof(o) {
+ if (typeof o === "undefined") {
+ return "undefined";
+
+ // consider: typeof null === object
+ } else if (o === null) {
+ return "null";
+
+ } else if (o.constructor === String) {
+ return "string";
+
+ } else if (o.constructor === Boolean) {
+ return "boolean";
+
+ } else if (o.constructor === Number) {
+
+ if (isNaN(o)) {
+ return "nan";
+ } else {
+ return "number";
+ }
+ // consider: typeof [] === object
+ } else if (o instanceof Array) {
+ return "array";
+
+ // consider: typeof new Date() === object
+ } else if (o instanceof Date) {
+ return "date";
+
+ // consider: /./ instanceof Object;
+ // /./ instanceof RegExp;
+ // typeof /./ === "function"; // => false in IE and Opera,
+ // true in FF and Safari
+ } else if (o instanceof RegExp) {
+ return "regexp";
+
+ } else if (typeof o === "object") {
+ if ("mapFromItem" in o && "mapToItem" in o) {
+ return "declarativeitem"; // @todo improve detection of declarative items
+ } else if ("x" in o && "y" in o && "z" in o) {
+ return "vector3d"; // Qt 3D vector
+ }
+ return "object";
+ } else if (o instanceof Function) {
+ return "function";
+ } else {
+ return undefined;
+ }
+ }
+
+ /*! \internal */
+ // Test for equality
+ // Large parts contain sources from QUnit or http://philrathe.com
+ // Discussions and reference: http://philrathe.com/articles/equiv
+ // Test suites: http://philrathe.com/tests/equiv
+ // Author: Philippe Rathé <prathe@gmail.com>
+ function qtest_compareInternal(act, exp) {
+ var success = false;
+ if (act === exp) {
+ success = true; // catch the most you can
+ } else if (act === null || exp === null || typeof act === "undefined" || typeof exp === "undefined") {
+ success = false; // don't lose time with error prone cases
+ } else {
+ var typeExp = qtest_typeof(exp), typeAct = qtest_typeof(act)
+ if (typeExp !== typeAct) {
+ // allow object vs string comparison (e.g. for colors)
+ // else break on different types
+ if ((typeExp === "string" && (typeAct === "object") || typeAct == "declarativeitem")
+ || ((typeExp === "object" || typeExp == "declarativeitem") && typeAct === "string")) {
+ success = (act == exp)
+ }
+ } else if (typeExp === "string" || typeExp === "boolean" ||
+ typeExp === "null" || typeExp === "undefined") {
+ if (exp instanceof act.constructor || act instanceof exp.constructor) {
+ // to catch short annotaion VS 'new' annotation of act declaration
+ // e.g. var i = 1;
+ // var j = new Number(1);
+ success = (act == exp)
+ } else {
+ success = (act === exp)
+ }
+ } else if (typeExp === "nan") {
+ success = isNaN(act);
+ } else if (typeExp === "number") {
+ // Use act fuzzy compare if the two values are floats
+ if (Math.abs(act - exp) <= 0.00001) {
+ success = true
+ }
+ } else if (typeExp === "array") {
+ success = qtest_compareInternalArrays(act, exp)
+ } else if (typeExp === "object") {
+ success = qtest_compareInternalObjects(act, exp)
+ } else if (typeExp === "declarativeitem") {
+ success = qtest_compareInternalObjects(act, exp) // @todo improve comparison of declarative items
+ } else if (typeExp === "vector3d") {
+ success = (Math.abs(act.x - exp.x) <= 0.00001 &&
+ Math.abs(act.y - exp.y) <= 0.00001 &&
+ Math.abs(act.z - exp.z) <= 0.00001)
+ } else if (typeExp === "date") {
+ success = (act.valueOf() === exp.valueOf())
+ } else if (typeExp === "regexp") {
+ success = (act.source === exp.source && // the regex itself
+ act.global === exp.global && // and its modifers (gmi) ...
+ act.ignoreCase === exp.ignoreCase &&
+ act.multiline === exp.multiline)
+ }
+ }
+ return success
+ }
+
+ /*! \internal */
+ function qtest_compareInternalObjects(act, exp) {
+ var i;
+ var eq = true; // unless we can proove it
+ var aProperties = [], bProperties = []; // collection of strings
+
+ // comparing constructors is more strict than using instanceof
+ if (act.constructor !== exp.constructor) {
+ return false;
+ }
+
+ for (i in act) { // be strict: don't ensures hasOwnProperty and go deep
+ aProperties.push(i); // collect act's properties
+ if (!qtest_compareInternal(act[i], exp[i])) {
+ eq = false;
+ break;
+ }
+ }
+
+ for (i in exp) {
+ bProperties.push(i); // collect exp's properties
+ }
+
+ if (aProperties.length == 0 && bProperties.length == 0) { // at least a special case for QUrl
+ return eq && (JSON.stringify(act) == JSON.stringify(exp));
+ }
+
+ // Ensures identical properties name
+ return eq && qtest_compareInternal(aProperties.sort(), bProperties.sort());
+
+ }
+
+ /*! \internal */
+ function qtest_compareInternalArrays(actual, expected) {
+ if (actual.length != expected.length) {
+ return false
+ }
+
+ for (var i = 0, len = actual.length; i < len; i++) {
+ if (!qtest_compareInternal(actual[i], expected[i])) {
+ return false
+ }
+ }
+
+ return true
+ }
+
+ /*!
+ \qmlmethod TestCase::compare(actual, expected, message = "")
+
+ Fails the current test case if \a actual is not the same as
+ \a expected, and displays the optional \a message. Similar
+ to \c{QCOMPARE(actual, expected)} in C++.
+
+ \sa tryCompare(), fuzzyCompare
+ */
+ function compare(actual, expected, msg) {
+ var act = qtest_results.stringify(actual)
+ var exp = qtest_results.stringify(expected)
+
+ var success = qtest_compareInternal(actual, expected)
+ if (msg === undefined) {
+ if (success)
+ msg = "COMPARE()"
+ else
+ msg = "Compared values are not the same"
+ }
+ if (!qtest_results.compare(success, msg, act, exp, util.callerFile(), util.callerLine())) {
+ throw new Error("QtQuickTest::fail")
+ }
+ }
+
+ /*!
+ \qmlmethod TestCase::fuzzyCompare(actual, expected, delta, message = "")
+
+ Fails the current test case if the difference betwen \a actual and \a expected
+ is greater than \a delta, and displays the optional \a message. Similar
+ to \c{qFuzzyCompare(actual, expected)} in C++ but with a required \a delta value.
+
+ This function can also be used for color comparisons if both the \a actual and
+ \a expected values can be converted into color values. If any of the differences
+ for RGBA channel values are greater than \a delta, the test fails.
+
+ \sa tryCompare(), compare()
+ */
+ function fuzzyCompare(actual, expected, delta, msg) {
+ if (delta === undefined)
+ qtest_fail("A delta value is required for fuzzyCompare", 2)
+
+ var success = qtest_results.fuzzyCompare(actual, expected, delta)
+ if (msg === undefined) {
+ if (success)
+ msg = "FUZZYCOMPARE()"
+ else
+ msg = "Compared values are not the same with delta(" + delta + ")"
+ }
+
+ if (!qtest_results.compare(success, msg, actual, expected, util.callerFile(), util.callerLine())) {
+ throw new Error("QtQuickTest::fail")
+ }
+ }
+
+ /*!
+ \qmlmethod object TestCase::grabImage(item)
+
+ Returns a snapshot image object of the given \a item.
+
+ The returned image object has the following properties:
+ \list
+ \li width Returns the width of the underlying image (since 5.10)
+ \li height Returns the height of the underlying image (since 5.10)
+ \li size Returns the size of the underlying image (since 5.10)
+ \endlist
+
+ Additionally, the returned image object has the following methods:
+ \list
+ \li \c {red(x, y)} Returns the red channel value of the pixel at \e x, \e y position
+ \li \c {green(x, y)} Returns the green channel value of the pixel at \e x, \e y position
+ \li \c {blue(x, y)} Returns the blue channel value of the pixel at \e x, \e y position
+ \li \c {alpha(x, y)} Returns the alpha channel value of the pixel at \e x, \e y position
+ \li \c {pixel(x, y)} Returns the color value of the pixel at \e x, \e y position
+ \li \c {equals(image)} Returns \c true if this image is identical to \e image -
+ see \l QImage::operator== (since 5.6)
+
+ For example:
+
+ \code
+ var image = grabImage(rect);
+ compare(image.red(10, 10), 255);
+ compare(image.pixel(20, 20), Qt.rgba(255, 0, 0, 255));
+
+ rect.width += 10;
+ var newImage = grabImage(rect);
+ verify(!newImage.equals(image));
+ \endcode
+
+ \li \c {save(path)} Saves the image to the given \e path. If the image cannot
+ be saved, an exception will be thrown. (since 5.10)
+
+ This can be useful to perform postmortem analysis on failing tests, for
+ example:
+
+ \code
+ var image = grabImage(rect);
+ try {
+ compare(image.width, 100);
+ } catch (ex) {
+ image.save("debug.png");
+ throw ex;
+ }
+ \endcode
+
+ \endlist
+ */
+ function grabImage(item) {
+ return qtest_results.grabImage(item);
+ }
+
+ /*!
+ \since 5.4
+ \qmlmethod QtObject TestCase::findChild(parent, objectName)
+
+ Returns the first child of \a parent with \a objectName, or \c null if
+ no such item exists. Both visual and non-visual children are searched
+ recursively, with visual children being searched first.
+
+ \code
+ compare(findChild(item, "childObject"), expectedChildObject);
+ \endcode
+ */
+ function findChild(parent, objectName) {
+ // First, search the visual item hierarchy.
+ var child = qtest_findVisualChild(parent, objectName);
+ if (child)
+ return child;
+
+ // If it's not a visual child, it might be a QObject child.
+ return qtest_results.findChild(parent, objectName);
+ }
+
+ /*! \internal */
+ function qtest_findVisualChild(parent, objectName) {
+ if (!parent || parent.children === undefined)
+ return null;
+
+ for (var i = 0; i < parent.children.length; ++i) {
+ // Is this direct child of ours the child we're after?
+ var child = parent.children[i];
+ if (child.objectName === objectName)
+ return child;
+ }
+
+ for (i = 0; i < parent.children.length; ++i) {
+ // Try the direct child's children.
+ child = qtest_findVisualChild(parent.children[i], objectName);
+ if (child)
+ return child;
+ }
+ return null;
+ }
+
+ /*!
+ \qmlmethod TestCase::tryCompare(obj, property, expected, timeout = 5000, message = "")
+
+ Fails the current test case if the specified \a property on \a obj
+ is not the same as \a expected, and displays the optional \a message.
+ The test will be retried multiple times until the
+ \a timeout (in milliseconds) is reached.
+
+ This function is intended for testing applications where a property
+ changes value based on asynchronous events. Use compare() for testing
+ synchronous property changes.
+
+ \code
+ tryCompare(img, "status", BorderImage.Ready)
+ compare(img.width, 120)
+ compare(img.height, 120)
+ compare(img.horizontalTileMode, BorderImage.Stretch)
+ compare(img.verticalTileMode, BorderImage.Stretch)
+ \endcode
+
+ SignalSpy::wait() provides an alternative method to wait for a
+ signal to be emitted.
+
+ \sa compare(), SignalSpy::wait()
+ */
+ function tryCompare(obj, prop, value, timeout, msg) {
+ if (arguments.length == 1 || (typeof(prop) != "string" && typeof(prop) != "number")) {
+ qtest_results.fail("A property name as string or index is required for tryCompare",
+ util.callerFile(), util.callerLine())
+ throw new Error("QtQuickTest::fail")
+ }
+ if (arguments.length == 2) {
+ qtest_results.fail("A value is required for tryCompare",
+ util.callerFile(), util.callerLine())
+ throw new Error("QtQuickTest::fail")
+ }
+ if (timeout !== undefined && typeof(timeout) != "number") {
+ qtest_results.fail("timeout should be a number",
+ util.callerFile(), util.callerLine())
+ throw new Error("QtQuickTest::fail")
+ }
+ if (!timeout)
+ timeout = 5000
+ if (msg === undefined)
+ msg = "property " + prop
+ if (!qtest_compareInternal(obj[prop], value))
+ wait(0)
+ var i = 0
+ while (i < timeout && !qtest_compareInternal(obj[prop], value)) {
+ wait(50)
+ i += 50
+ }
+ var actual = obj[prop]
+ var act = qtest_results.stringify(actual)
+ var exp = qtest_results.stringify(value)
+ var success = qtest_compareInternal(actual, value)
+ if (!qtest_results.compare(success, msg, act, exp, util.callerFile(), util.callerLine()))
+ throw new Error("QtQuickTest::fail")
+ }
+
+ /*!
+ \qmlmethod TestCase::skip(message = "")
+
+ Skips the current test case and prints the optional \a message.
+ If this is a data-driven test, then only the current row is skipped.
+ Similar to \c{QSKIP(message)} in C++.
+ */
+ function skip(msg) {
+ if (msg === undefined)
+ msg = ""
+ qtest_results.skip(msg, util.callerFile(), util.callerLine())
+ throw new Error("QtQuickTest::skip")
+ }
+
+ /*!
+ \qmlmethod TestCase::expectFail(tag, message)
+
+ In a data-driven test, marks the row associated with \a tag as
+ expected to fail. When the fail occurs, display the \a message,
+ abort the test, and mark the test as passing. Similar to
+ \c{QEXPECT_FAIL(tag, message, Abort)} in C++.
+
+ If the test is not data-driven, then \a tag must be set to
+ an empty string.
+
+ \sa expectFailContinue()
+ */
+ function expectFail(tag, msg) {
+ if (tag === undefined) {
+ warn("tag argument missing from expectFail()")
+ tag = ""
+ }
+ if (msg === undefined) {
+ warn("message argument missing from expectFail()")
+ msg = ""
+ }
+ if (!qtest_results.expectFail(tag, msg, util.callerFile(), util.callerLine()))
+ throw new Error("QtQuickTest::expectFail")
+ }
+
+ /*!
+ \qmlmethod TestCase::expectFailContinue(tag, message)
+
+ In a data-driven test, marks the row associated with \a tag as
+ expected to fail. When the fail occurs, display the \a message,
+ and then continue the test. Similar to
+ \c{QEXPECT_FAIL(tag, message, Continue)} in C++.
+
+ If the test is not data-driven, then \a tag must be set to
+ an empty string.
+
+ \sa expectFail()
+ */
+ function expectFailContinue(tag, msg) {
+ if (tag === undefined) {
+ warn("tag argument missing from expectFailContinue()")
+ tag = ""
+ }
+ if (msg === undefined) {
+ warn("message argument missing from expectFailContinue()")
+ msg = ""
+ }
+ if (!qtest_results.expectFailContinue(tag, msg, util.callerFile(), util.callerLine()))
+ throw new Error("QtQuickTest::expectFail")
+ }
+
+ /*!
+ \qmlmethod TestCase::warn(message)
+
+ Prints \a message as a warning message. Similar to
+ \c{QWARN(message)} in C++.
+
+ \sa ignoreWarning()
+ */
+ function warn(msg) {
+ if (msg === undefined)
+ msg = ""
+ qtest_results.warn(msg, util.callerFile(), util.callerLine());
+ }
+
+ /*!
+ \qmlmethod TestCase::ignoreWarning(message)
+
+ Marks \a message as an ignored warning message. When it occurs,
+ the warning will not be printed and the test passes. If the message
+ does not occur, then the test will fail. Similar to
+ \c{QTest::ignoreMessage(QtWarningMsg, message)} in C++.
+
+ Since Qt 5.12, \a message can be either a string, or a regular
+ expression providing a pattern of messages to ignore.
+
+ For example, the following snippet will ignore a string warning message:
+ \qml
+ ignoreWarning("Something sort of bad happened")
+ \endqml
+
+ And the following snippet will ignore a regular expression matching a
+ number of possible warning messages:
+ \qml
+ ignoreWarning(new RegExp("[0-9]+ bad things happened"))
+ \endqml
+
+ \note Despite being a JavaScript RegExp object, it will not be
+ interpreted as such; instead, the pattern will be passed to
+ \l QRegularExpression.
+
+ \sa warn()
+ */
+ function ignoreWarning(msg) {
+ if (msg === undefined)
+ msg = ""
+ qtest_results.ignoreWarning(msg)
+ }
+
+ /*!
+ \qmlmethod TestCase::wait(ms)
+
+ Waits for \a ms milliseconds while processing Qt events.
+
+ \sa sleep(), waitForRendering()
+ */
+ function wait(ms) {
+ qtest_results.wait(ms)
+ }
+
+ /*!
+ \qmlmethod TestCase::waitForRendering(item, timeout = 5000)
+
+ Waits for \a timeout milliseconds or until the \a item is rendered by the renderer.
+ Returns true if \c item is rendered in \a timeout milliseconds, otherwise returns false.
+ The default \a timeout value is 5000.
+
+ \sa sleep(), wait()
+ */
+ function waitForRendering(item, timeout) {
+ if (timeout === undefined)
+ timeout = 5000
+ if (!qtest_verifyItem(item, "waitForRendering"))
+ return
+ return qtest_results.waitForRendering(item, timeout)
+ }
+
+ /*!
+ \qmlmethod TestCase::sleep(ms)
+
+ Sleeps for \a ms milliseconds without processing Qt events.
+
+ \sa wait(), waitForRendering()
+ */
+ function sleep(ms) {
+ qtest_results.sleep(ms)
+ }
+
+ /*!
+ \qmlmethod TestCase::keyPress(key, modifiers = Qt.NoModifier, delay = -1)
+
+ Simulates pressing a \a key with optional \a modifiers on the currently
+ focused item. If \a delay is larger than 0, the test will wait for
+ \a delay milliseconds.
+
+ The event will be sent to the TestCase window or, in case of multiple windows,
+ to the current active window. See \l QGuiApplication::focusWindow() for more details.
+
+ \b{Note:} At some point you should release the key using keyRelease().
+
+ \sa keyRelease(), keyClick()
+ */
+ function keyPress(key, modifiers, delay) {
+ if (modifiers === undefined)
+ modifiers = Qt.NoModifier
+ if (delay == undefined)
+ delay = -1
+ if (typeof(key) == "string" && key.length == 1) {
+ if (!qtest_events.keyPressChar(key, modifiers, delay))
+ qtest_fail("window not shown", 2)
+ } else {
+ if (!qtest_events.keyPress(key, modifiers, delay))
+ qtest_fail("window not shown", 2)
+ }
+ }
+
+ /*!
+ \qmlmethod TestCase::keyRelease(key, modifiers = Qt.NoModifier, delay = -1)
+
+ Simulates releasing a \a key with optional \a modifiers on the currently
+ focused item. If \a delay is larger than 0, the test will wait for
+ \a delay milliseconds.
+
+ The event will be sent to the TestCase window or, in case of multiple windows,
+ to the current active window. See \l QGuiApplication::focusWindow() for more details.
+
+ \sa keyPress(), keyClick()
+ */
+ function keyRelease(key, modifiers, delay) {
+ if (modifiers === undefined)
+ modifiers = Qt.NoModifier
+ if (delay == undefined)
+ delay = -1
+ if (typeof(key) == "string" && key.length == 1) {
+ if (!qtest_events.keyReleaseChar(key, modifiers, delay))
+ qtest_fail("window not shown", 2)
+ } else {
+ if (!qtest_events.keyRelease(key, modifiers, delay))
+ qtest_fail("window not shown", 2)
+ }
+ }
+
+ /*!
+ \qmlmethod TestCase::keyClick(key, modifiers = Qt.NoModifier, delay = -1)
+
+ Simulates clicking of \a key with optional \a modifiers on the currently
+ focused item. If \a delay is larger than 0, the test will wait for
+ \a delay milliseconds.
+
+ The event will be sent to the TestCase window or, in case of multiple windows,
+ to the current active window. See \l QGuiApplication::focusWindow() for more details.
+
+ \sa keyPress(), keyRelease()
+ */
+ function keyClick(key, modifiers, delay) {
+ if (modifiers === undefined)
+ modifiers = Qt.NoModifier
+ if (delay == undefined)
+ delay = -1
+ if (typeof(key) == "string" && key.length == 1) {
+ if (!qtest_events.keyClickChar(key, modifiers, delay))
+ qtest_fail("window not shown", 2)
+ } else {
+ if (!qtest_events.keyClick(key, modifiers, delay))
+ qtest_fail("window not shown", 2)
+ }
+ }
+
+ /*!
+ \since 5.10
+ \qmlmethod TestCase::keySequence(keySequence)
+
+ Simulates typing of \a keySequence. The key sequence can be set
+ to one of the \l{QKeySequence::StandardKey}{standard keyboard shortcuts}, or
+ it can be described with a string containing a sequence of up to four key
+ presses.
+
+ Each event shall be sent to the TestCase window or, in case of multiple windows,
+ to the current active window. See \l QGuiApplication::focusWindow() for more details.
+
+ \sa keyPress(), keyRelease(), {GNU Emacs Style Key Sequences},
+ {QtQuick::Shortcut::sequence}{Shortcut.sequence}
+ */
+ function keySequence(keySequence) {
+ if (!qtest_events.keySequence(keySequence))
+ qtest_fail("window not shown", 2)
+ }
+
+ /*!
+ \qmlmethod TestCase::mousePress(item, x = item.width / 2, y = item.height / 2, button = Qt.LeftButton, modifiers = Qt.NoModifier, delay = -1)
+
+ Simulates pressing a mouse \a button with optional \a modifiers
+ on an \a item. The position is defined by \a x and \a y.
+ If \a x or \a y are not defined the position will be the center of \a item.
+ If \a delay is specified, the test will wait for the specified amount of
+ milliseconds before the press.
+
+ The position given by \a x and \a y is transformed from the co-ordinate
+ system of \a item into window co-ordinates and then delivered.
+ If \a item is obscured by another item, or a child of \a item occupies
+ that position, then the event will be delivered to the other item instead.
+
+ \sa mouseRelease(), mouseClick(), mouseDoubleClickSequence(), mouseMove(), mouseDrag(), mouseWheel()
+ */
+ function mousePress(item, x, y, button, modifiers, delay) {
+ if (!qtest_verifyItem(item, "mousePress"))
+ return
+
+ if (button === undefined)
+ button = Qt.LeftButton
+ if (modifiers === undefined)
+ modifiers = Qt.NoModifier
+ if (delay == undefined)
+ delay = -1
+ if (x === undefined)
+ x = item.width / 2
+ if (y === undefined)
+ y = item.height / 2
+ if (!qtest_events.mousePress(item, x, y, button, modifiers, delay))
+ qtest_fail("window not shown", 2)
+ }
+
+ /*!
+ \qmlmethod TestCase::mouseRelease(item, x = item.width / 2, y = item.height / 2, button = Qt.LeftButton, modifiers = Qt.NoModifier, delay = -1)
+
+ Simulates releasing a mouse \a button with optional \a modifiers
+ on an \a item. The position of the release is defined by \a x and \a y.
+ If \a x or \a y are not defined the position will be the center of \a item.
+ If \a delay is specified, the test will wait for the specified amount of
+ milliseconds before releasing the button.
+
+ The position given by \a x and \a y is transformed from the co-ordinate
+ system of \a item into window co-ordinates and then delivered.
+ If \a item is obscured by another item, or a child of \a item occupies
+ that position, then the event will be delivered to the other item instead.
+
+ \sa mousePress(), mouseClick(), mouseDoubleClickSequence(), mouseMove(), mouseDrag(), mouseWheel()
+ */
+ function mouseRelease(item, x, y, button, modifiers, delay) {
+ if (!qtest_verifyItem(item, "mouseRelease"))
+ return
+
+ if (button === undefined)
+ button = Qt.LeftButton
+ if (modifiers === undefined)
+ modifiers = Qt.NoModifier
+ if (delay == undefined)
+ delay = -1
+ if (x === undefined)
+ x = item.width / 2
+ if (y === undefined)
+ y = item.height / 2
+ if (!qtest_events.mouseRelease(item, x, y, button, modifiers, delay))
+ qtest_fail("window not shown", 2)
+ }
+
+ /*!
+ \qmlmethod TestCase::mouseDrag(item, x, y, dx, dy, button = Qt.LeftButton, modifiers = Qt.NoModifier, delay = -1)
+
+ Simulates dragging the mouse on an \a item with \a button pressed and optional \a modifiers
+ The initial drag position is defined by \a x and \a y,
+ and drag distance is defined by \a dx and \a dy. If \a delay is specified,
+ the test will wait for the specified amount of milliseconds before releasing the button.
+
+ The position given by \a x and \a y is transformed from the co-ordinate
+ system of \a item into window co-ordinates and then delivered.
+ If \a item is obscured by another item, or a child of \a item occupies
+ that position, then the event will be delivered to the other item instead.
+
+ \sa mousePress(), mouseClick(), mouseDoubleClickSequence(), mouseMove(), mouseRelease(), mouseWheel()
+ */
+ function mouseDrag(item, x, y, dx, dy, button, modifiers, delay) {
+ if (!qtest_verifyItem(item, "mouseDrag"))
+ return
+
+ if (item.x === undefined || item.y === undefined)
+ return
+ if (button === undefined)
+ button = Qt.LeftButton
+ if (modifiers === undefined)
+ modifiers = Qt.NoModifier
+ if (delay == undefined)
+ delay = -1
+ var moveDelay = Math.max(1, delay === -1 ? qtest_events.defaultMouseDelay : delay)
+
+ // Divide dx and dy to have intermediate mouseMove while dragging
+ // Fractions of dx/dy need be superior to the dragThreshold
+ // to make the drag works though
+ var intermediateDx = Math.round(dx/3)
+ if (Math.abs(intermediateDx) < (util.dragThreshold + 1))
+ intermediateDx = 0
+ var intermediateDy = Math.round(dy/3)
+ if (Math.abs(intermediateDy) < (util.dragThreshold + 1))
+ intermediateDy = 0
+
+ mousePress(item, x, y, button, modifiers, delay)
+
+ // Trigger dragging by dragging past the drag threshold, but making sure to only drag
+ // along a certain axis if a distance greater than zero was given for that axis.
+ var dragTriggerXDistance = dx > 0 ? (util.dragThreshold + 1) : 0
+ var dragTriggerYDistance = dy > 0 ? (util.dragThreshold + 1) : 0
+ mouseMove(item, x + dragTriggerXDistance, y + dragTriggerYDistance, moveDelay, button)
+ if (intermediateDx !== 0 || intermediateDy !== 0) {
+ mouseMove(item, x + intermediateDx, y + intermediateDy, moveDelay, button)
+ mouseMove(item, x + 2*intermediateDx, y + 2*intermediateDy, moveDelay, button)
+ }
+ mouseMove(item, x + dx, y + dy, moveDelay, button)
+ mouseRelease(item, x + dx, y + dy, button, modifiers, delay)
+ }
+
+ /*!
+ \qmlmethod TestCase::mouseClick(item, x = item.width / 2, y = item.height / 2, button = Qt.LeftButton, modifiers = Qt.NoModifier, delay = -1)
+
+ Simulates clicking a mouse \a button with optional \a modifiers
+ on an \a item. The position of the click is defined by \a x and \a y.
+ If \a x and \a y are not defined the position will be the center of \a item.
+ If \a delay is specified, the test will wait for the specified amount of
+ milliseconds before pressing and before releasing the button.
+
+ The position given by \a x and \a y is transformed from the co-ordinate
+ system of \a item into window co-ordinates and then delivered.
+ If \a item is obscured by another item, or a child of \a item occupies
+ that position, then the event will be delivered to the other item instead.
+
+ \sa mousePress(), mouseRelease(), mouseDoubleClickSequence(), mouseMove(), mouseDrag(), mouseWheel()
+ */
+ function mouseClick(item, x, y, button, modifiers, delay) {
+ if (!qtest_verifyItem(item, "mouseClick"))
+ return
+
+ if (button === undefined)
+ button = Qt.LeftButton
+ if (modifiers === undefined)
+ modifiers = Qt.NoModifier
+ if (delay == undefined)
+ delay = -1
+ if (x === undefined)
+ x = item.width / 2
+ if (y === undefined)
+ y = item.height / 2
+ if (!qtest_events.mouseClick(item, x, y, button, modifiers, delay))
+ qtest_fail("window not shown", 2)
+ }
+
+ /*!
+ \qmlmethod TestCase::mouseDoubleClickSequence(item, x = item.width / 2, y = item.height / 2, button = Qt.LeftButton, modifiers = Qt.NoModifier, delay = -1)
+
+ Simulates the full sequence of events generated by double-clicking a mouse
+ \a button with optional \a modifiers on an \a item.
+
+ This method reproduces the sequence of mouse events generated when a user makes
+ a double click: Press-Release-Press-DoubleClick-Release.
+
+ The position of the click is defined by \a x and \a y.
+ If \a x and \a y are not defined the position will be the center of \a item.
+ If \a delay is specified, the test will wait for the specified amount of
+ milliseconds before pressing and before releasing the button.
+
+ The position given by \a x and \a y is transformed from the co-ordinate
+ system of \a item into window co-ordinates and then delivered.
+ If \a item is obscured by another item, or a child of \a item occupies
+ that position, then the event will be delivered to the other item instead.
+
+ This QML method was introduced in Qt 5.5.
+
+ \sa mousePress(), mouseRelease(), mouseClick(), mouseMove(), mouseDrag(), mouseWheel()
+ */
+ function mouseDoubleClickSequence(item, x, y, button, modifiers, delay) {
+ if (!qtest_verifyItem(item, "mouseDoubleClickSequence"))
+ return
+
+ if (button === undefined)
+ button = Qt.LeftButton
+ if (modifiers === undefined)
+ modifiers = Qt.NoModifier
+ if (delay == undefined)
+ delay = -1
+ if (x === undefined)
+ x = item.width / 2
+ if (y === undefined)
+ y = item.height / 2
+ if (!qtest_events.mouseDoubleClickSequence(item, x, y, button, modifiers, delay))
+ qtest_fail("window not shown", 2)
+ }
+
+ /*!
+ \qmlmethod TestCase::mouseMove(item, x = item.width / 2, y = item.height / 2, delay = -1, buttons = Qt.NoButton)
+
+ Moves the mouse pointer to the position given by \a x and \a y within
+ \a item, while holding \a buttons if given. Since Qt 6.0, if \a x and
+ \a y are not defined, the position will be the center of \a item.
+
+ If a \a delay (in milliseconds) is given, the test will wait before
+ moving the mouse pointer.
+
+ The position given by \a x and \a y is transformed from the co-ordinate
+ system of \a item into window co-ordinates and then delivered.
+ If \a item is obscured by another item, or a child of \a item occupies
+ that position, then the event will be delivered to the other item instead.
+
+ \sa mousePress(), mouseRelease(), mouseClick(), mouseDoubleClickSequence(), mouseDrag(), mouseWheel()
+ */
+ function mouseMove(item, x, y, delay, buttons) {
+ if (!qtest_verifyItem(item, "mouseMove"))
+ return
+
+ if (delay == undefined)
+ delay = -1
+ if (buttons == undefined)
+ buttons = Qt.NoButton
+ if (x === undefined)
+ x = item.width / 2
+ if (y === undefined)
+ y = item.height / 2
+ if (!qtest_events.mouseMove(item, x, y, delay, buttons))
+ qtest_fail("window not shown", 2)
+ }
+
+ /*!
+ \qmlmethod TestCase::mouseWheel(item, x, y, xDelta, yDelta, button = Qt.LeftButton, modifiers = Qt.NoModifier, delay = -1)
+
+ Simulates rotating the mouse wheel on an \a item with \a button pressed and optional \a modifiers.
+ The position of the wheel event is defined by \a x and \a y.
+ If \a delay is specified, the test will wait for the specified amount of milliseconds before releasing the button.
+
+ The position given by \a x and \a y is transformed from the co-ordinate
+ system of \a item into window co-ordinates and then delivered.
+ If \a item is obscured by another item, or a child of \a item occupies
+ that position, then the event will be delivered to the other item instead.
+
+ The \a xDelta and \a yDelta contain the wheel rotation distance in eighths of a degree. see \l QWheelEvent::angleDelta() for more details.
+
+ \sa mousePress(), mouseClick(), mouseDoubleClickSequence(), mouseMove(), mouseRelease(), mouseDrag(), QWheelEvent::angleDelta()
+ */
+ function mouseWheel(item, x, y, xDelta, yDelta, buttons, modifiers, delay) {
+ if (!qtest_verifyItem(item, "mouseWheel"))
+ return
+
+ if (delay == undefined)
+ delay = -1
+ if (buttons == undefined)
+ buttons = Qt.NoButton
+ if (modifiers === undefined)
+ modifiers = Qt.NoModifier
+ if (xDelta == undefined)
+ xDelta = 0
+ if (yDelta == undefined)
+ yDelta = 0
+ if (!qtest_events.mouseWheel(item, x, y, buttons, modifiers, xDelta, yDelta, delay))
+ qtest_fail("window not shown", 2)
+ }
+
+ /*!
+ \qmlmethod TouchEventSequence TestCase::touchEvent(object item)
+
+ \since 5.9
+
+ Begins a sequence of touch events through a simulated touchscreen (QPointingDevice).
+ Events are delivered to the window containing \a item.
+
+ The returned object is used to enumerate events to be delivered through a single
+ QTouchEvent. Touches are delivered to the window containing the TestCase unless
+ otherwise specified.
+
+ \code
+ Rectangle {
+ width: 640; height: 480
+
+ MultiPointTouchArea {
+ id: area
+ anchors.fill: parent
+
+ property bool touched: false
+
+ onPressed: touched = true
+ }
+
+ TestCase {
+ name: "ItemTests"
+ when: windowShown
+ id: test1
+
+ function test_touch() {
+ var touch = touchEvent(area);
+ touch.press(0, area, 10, 10);
+ touch.commit();
+ verify(area.touched);
+ }
+ }
+ }
+ \endcode
+
+ \sa TouchEventSequence::press(), TouchEventSequence::move(), TouchEventSequence::release(), TouchEventSequence::stationary(), TouchEventSequence::commit(), QInputDevice::DeviceType
+ */
+
+ function touchEvent(item) {
+ if (!qtest_verifyItem(item, "touchEvent"))
+ return
+
+ return {
+ _defaultItem: item,
+ _sequence: qtest_events.touchEvent(item),
+
+ press: function (id, target, x, y) {
+ if (!target)
+ target = this._defaultItem;
+ if (id === undefined)
+ qtest_fail("No id given to TouchEventSequence::press", 1);
+ if (x === undefined)
+ x = target.width / 2;
+ if (y === undefined)
+ y = target.height / 2;
+ this._sequence.press(id, target, x, y);
+ return this;
+ },
+
+ move: function (id, target, x, y) {
+ if (!target)
+ target = this._defaultItem;
+ if (id === undefined)
+ qtest_fail("No id given to TouchEventSequence::move", 1);
+ if (x === undefined)
+ x = target.width / 2;
+ if (y === undefined)
+ y = target.height / 2;
+ this._sequence.move(id, target, x, y);
+ return this;
+ },
+
+ stationary: function (id) {
+ if (id === undefined)
+ qtest_fail("No id given to TouchEventSequence::stationary", 1);
+ this._sequence.stationary(id);
+ return this;
+ },
+
+ release: function (id, target, x, y) {
+ if (!target)
+ target = this._defaultItem;
+ if (id === undefined)
+ qtest_fail("No id given to TouchEventSequence::release", 1);
+ if (x === undefined)
+ x = target.width / 2;
+ if (y === undefined)
+ y = target.height / 2;
+ this._sequence.release(id, target, x, y);
+ return this;
+ },
+
+ commit: function () {
+ this._sequence.commit();
+ return this;
+ }
+ };
+ }
+
+ // Functions that can be overridden in subclasses for init/cleanup duties.
+ /*!
+ \qmlmethod TestCase::initTestCase()
+
+ This function is called before any other test functions in the
+ \l TestCase type. The default implementation does nothing.
+ The application can provide its own implementation to perform
+ test case initialization.
+
+ \sa cleanupTestCase(), init()
+ */
+ function initTestCase() {}
+
+ /*!
+ \qmlmethod TestCase::cleanupTestCase()
+
+ This function is called after all other test functions in the
+ \l TestCase type have completed. The default implementation
+ does nothing. The application can provide its own implementation
+ to perform test case cleanup.
+
+ \sa initTestCase(), cleanup()
+ */
+ function cleanupTestCase() {}
+
+ /*!
+ \qmlmethod TestCase::init()
+
+ This function is called before each test function that is
+ executed in the \l TestCase type. The default implementation
+ does nothing. The application can provide its own implementation
+ to perform initialization before each test function.
+
+ \sa cleanup(), initTestCase()
+ */
+ function init() {}
+
+ /*!
+ \qmlmethod TestCase::cleanup()
+
+ This function is called after each test function that is
+ executed in the \l TestCase type. The default implementation
+ does nothing. The application can provide its own implementation
+ to perform cleanup after each test function.
+
+ \sa init(), cleanupTestCase()
+ */
+ function cleanup() {}
+
+ /*! \internal */
+ function qtest_verifyItem(item, method) {
+ try {
+ if (!(item instanceof Item) &&
+ !(item instanceof Window)) {
+ // it's a QObject, but not a type
+ qtest_fail("TypeError: %1 requires an Item or Window type".arg(method), 2);
+ return false;
+ }
+ } catch (e) { // it's not a QObject
+ qtest_fail("TypeError: %1 requires an Item or Window type".arg(method), 3);
+ return false;
+ }
+
+ return true;
+ }
+
+ /*! \internal */
+ function qtest_runInternal(prop, arg) {
+ try {
+ qtest_testCaseResult = testCase[prop](arg)
+ } catch (e) {
+ qtest_testCaseResult = []
+ if (e.message.indexOf("QtQuickTest::") != 0) {
+ // Test threw an unrecognized exception - fail.
+ qtest_results.fail("Uncaught exception: " + e.message,
+ e.fileName, e.lineNumber)
+ }
+ }
+ return !qtest_results.failed
+ }
+
+ /*! \internal */
+ function qtest_runFunction(prop, arg) {
+ qtest_runInternal("init")
+ if (!qtest_results.skipped) {
+ qtest_runInternal(prop, arg)
+ qtest_results.finishTestData()
+ qtest_runInternal("cleanup")
+ qtest_destroyTemporaryObjects()
+ qtest_results.finishTestDataCleanup()
+ // wait(0) will call processEvents() so objects marked for deletion
+ // in the test function will be deleted.
+ wait(0)
+ }
+ }
+
+ /*! \internal */
+ function qtest_runBenchmarkFunction(prop, arg) {
+ qtest_results.startMeasurement()
+ do {
+ qtest_results.beginDataRun()
+ do {
+ // Run the initialization function.
+ qtest_runInternal("init")
+ if (qtest_results.skipped)
+ break
+
+ // Execute the benchmark function.
+ if (prop.indexOf("benchmark_once_") != 0)
+ qtest_results.startBenchmark(TestResult.RepeatUntilValidMeasurement, qtest_results.dataTag)
+ else
+ qtest_results.startBenchmark(TestResult.RunOnce, qtest_results.dataTag)
+ while (!qtest_results.isBenchmarkDone()) {
+ var success = qtest_runInternal(prop, arg)
+ qtest_results.finishTestData()
+ if (!success)
+ break
+ qtest_results.nextBenchmark()
+ }
+ qtest_results.stopBenchmark()
+
+ // Run the cleanup function.
+ qtest_runInternal("cleanup")
+ qtest_results.finishTestDataCleanup()
+ // wait(0) will call processEvents() so objects marked for deletion
+ // in the test function will be deleted.
+ wait(0)
+ } while (!qtest_results.measurementAccepted())
+ qtest_results.endDataRun()
+ } while (qtest_results.needsMoreMeasurements())
+ }
+
+ /*! \internal */
+ function qtest_run() {
+ if (TestLogger.log_start_test()) {
+ qtest_results.reset()
+ qtest_results.testCaseName = name
+ qtest_results.startLogging()
+ } else {
+ qtest_results.testCaseName = name
+ }
+ running = true
+
+ // Check the run list to see if this class is mentioned.
+ let checkNames = false
+ let testsToRun = {} // explicitly provided function names to run and their tags for data-driven tests
+
+ if (qtest_results.functionsToRun.length > 0) {
+ checkNames = true
+ var found = false
+
+ if (name.length > 0) {
+ for (var index in qtest_results.functionsToRun) {
+ let caseFuncName = qtest_results.functionsToRun[index]
+ if (caseFuncName.indexOf(name + "::") != 0)
+ continue
+
+ found = true
+ let funcName = caseFuncName.substring(name.length + 2)
+
+ if (!(funcName in testsToRun))
+ testsToRun[funcName] = []
+
+ let tagName = qtest_results.tagsToRun[index]
+ if (tagName.length > 0) // empty tags mean run all rows
+ testsToRun[funcName].push(tagName)
+ }
+ }
+ if (!found) {
+ completed = true
+ if (!TestLogger.log_complete_test(qtest_testId)) {
+ qtest_results.stopLogging()
+ Qt.quit()
+ }
+ qtest_results.testCaseName = ""
+ return
+ }
+ }
+
+ // Run the initTestCase function.
+ qtest_results.functionName = "initTestCase"
+ var runTests = true
+ if (!qtest_runInternal("initTestCase"))
+ runTests = false
+ qtest_results.finishTestData()
+ qtest_results.finishTestDataCleanup()
+ qtest_results.finishTestFunction()
+
+ // Run the test methods.
+ var testList = []
+ if (runTests) {
+ for (var prop in testCase) {
+ if (prop.indexOf("test_") != 0 && prop.indexOf("benchmark_") != 0)
+ continue
+ var tail = prop.lastIndexOf("_data");
+ if (tail != -1 && tail == (prop.length - 5))
+ continue
+ testList.push(prop)
+ }
+ testList.sort()
+ }
+
+ for (var index in testList) {
+ var prop = testList[index]
+
+ if (checkNames && !(prop in testsToRun))
+ continue
+
+ var datafunc = prop + "_data"
+ var isBenchmark = (prop.indexOf("benchmark_") == 0)
+ qtest_results.functionName = prop
+
+ if (!(datafunc in testCase))
+ datafunc = "init_data";
+
+ if (datafunc in testCase) {
+ if (qtest_runInternal(datafunc)) {
+ var table = qtest_testCaseResult
+ var haveData = false
+
+ let checkTags = (checkNames && testsToRun[prop].length > 0)
+
+ qtest_results.initTestTable()
+ for (var index in table) {
+ haveData = true
+ var row = table[index]
+ if (!row.tag)
+ row.tag = "row " + index // Must have something
+ if (checkTags) {
+ let tags = testsToRun[prop]
+ let tagIdx = tags.indexOf(row.tag)
+ if (tagIdx < 0)
+ continue
+ tags.splice(tagIdx, 1)
+ }
+ qtest_results.dataTag = row.tag
+ if (isBenchmark)
+ qtest_runBenchmarkFunction(prop, row)
+ else
+ qtest_runFunction(prop, row)
+ qtest_results.dataTag = ""
+ qtest_results.skipped = false
+ }
+ if (!haveData) {
+ if (datafunc === "init_data")
+ qtest_runFunction(prop, null, isBenchmark)
+ else
+ qtest_results.warn("no data supplied for " + prop + "() by " + datafunc + "()"
+ , util.callerFile(), util.callerLine());
+ }
+ qtest_results.clearTestTable()
+ }
+ } else if (isBenchmark) {
+ qtest_runBenchmarkFunction(prop, null, isBenchmark)
+ } else {
+ qtest_runFunction(prop, null, isBenchmark)
+ }
+ qtest_results.finishTestFunction()
+ qtest_results.skipped = false
+
+ if (checkNames && testsToRun[prop].length <= 0)
+ delete testsToRun[prop]
+ }
+
+ // Run the cleanupTestCase function.
+ qtest_results.skipped = false
+ qtest_results.functionName = "cleanupTestCase"
+ qtest_runInternal("cleanupTestCase")
+
+ // Complain about missing functions that we were supposed to run.
+ if (checkNames) {
+ let missingTests = []
+ for (var func in testsToRun) {
+ let caseFuncName = name + '::' + func
+ let tags = testsToRun[func]
+ if (tags.length <= 0)
+ missingTests.push(caseFuncName)
+ else
+ for (var i in tags)
+ missingTests.push(caseFuncName + ':' + tags[i])
+ }
+ missingTests.sort()
+ if (missingTests.length > 0)
+ qtest_results.fail("Could not find test functions: " + missingTests, "", 0)
+ }
+
+ // Clean up and exit.
+ running = false
+ completed = true
+ qtest_results.finishTestData()
+ qtest_results.finishTestDataCleanup()
+ qtest_results.finishTestFunction()
+ qtest_results.functionName = ""
+
+ // Stop if there are no more tests to be run.
+ if (!TestLogger.log_complete_test(qtest_testId)) {
+ qtest_results.stopLogging()
+ Qt.quit()
+ }
+ qtest_results.testCaseName = ""
+ }
+
+ onWhenChanged: {
+ if (when != qtest_prevWhen) {
+ qtest_prevWhen = when
+ if (when && !completed && !running && qtest_componentCompleted)
+ qtest_run()
+ }
+ }
+
+ onOptionalChanged: {
+ if (!completed) {
+ if (optional)
+ TestLogger.log_optional_test(qtest_testId)
+ else
+ TestLogger.log_mandatory_test(qtest_testId)
+ }
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/aliasLookup.qml b/tests/auto/qml/qmlcppcodegen/data/aliasLookup.qml
new file mode 100644
index 0000000000..defea11fc1
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/aliasLookup.qml
@@ -0,0 +1,8 @@
+import QtQml
+
+QtObject {
+ id: self
+ property real foo: 12.3
+ property alias a: self.foo
+ property string t: Math.round(self.a)
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/anchorsFill.qml b/tests/auto/qml/qmlcppcodegen/data/anchorsFill.qml
new file mode 100644
index 0000000000..3d18a40434
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/anchorsFill.qml
@@ -0,0 +1,12 @@
+import QtQuick
+
+Item {
+ width: 234
+ Item {
+ anchors.fill: parent
+ }
+
+ Item {
+ width: 47
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/array.qml b/tests/auto/qml/qmlcppcodegen/data/array.qml
new file mode 100644
index 0000000000..8ea957ea73
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/array.qml
@@ -0,0 +1,6 @@
+import QML
+
+QtObject {
+ property var values1: [1, 2, 3]
+ property var values2: []
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/asCast.qml b/tests/auto/qml/qmlcppcodegen/data/asCast.qml
new file mode 100644
index 0000000000..6f8e274827
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/asCast.qml
@@ -0,0 +1,50 @@
+/******************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt JavaScript to C++ compiler.
+**
+** $QT_BEGIN_LICENSE:COMM$
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+pragma Strict
+import QtQuick
+
+Item {
+ QtObject { id: object }
+ Item { id: item }
+ Rectangle { id: rectangle }
+ Dummy { id: dummy }
+
+ property QtObject objectAsObject: object as QtObject
+ property QtObject objectAsItem: object as Item
+ property QtObject objectAsRectangle: object as Rectangle
+ property QtObject objectAsDummy: object as Dummy
+
+ property QtObject itemAsObject: item as QtObject
+ property QtObject itemAsItem: item as Item
+ property QtObject itemAsRectangle: item as Rectangle
+ property QtObject itemAsDummy: item as Dummy
+
+ property QtObject rectangleAsObject: rectangle as QtObject
+ property QtObject rectangleAsItem: rectangle as Item
+ property QtObject rectangleAsRectangle: rectangle as Rectangle
+ property QtObject rectangleAsDummy: rectangle as Dummy
+
+ property QtObject dummyAsObject: dummy as QtObject
+ property QtObject dummyAsItem: dummy as Item
+ property QtObject dummyAsRectangle: dummy as Rectangle
+ property QtObject dummyAsDummy: dummy as Dummy
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/attachedBaseEnum.qml b/tests/auto/qml/qmlcppcodegen/data/attachedBaseEnum.qml
new file mode 100644
index 0000000000..3f6a3fc307
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/attachedBaseEnum.qml
@@ -0,0 +1,5 @@
+import QtQuick
+
+MouseArea {
+ drag.axis: Drag.YAxis
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/bindToValueType.qml b/tests/auto/qml/qmlcppcodegen/data/bindToValueType.qml
new file mode 100644
index 0000000000..2ce90fc84d
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/bindToValueType.qml
@@ -0,0 +1,9 @@
+pragma Strict
+
+import QtQuick
+import QtQuick.Templates as T
+
+T.Button {
+ icon.color: "green"
+ property color a: icon.color
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/birthdayparty.cpp b/tests/auto/qml/qmlcppcodegen/data/birthdayparty.cpp
new file mode 100644
index 0000000000..a4211d59be
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/birthdayparty.cpp
@@ -0,0 +1,87 @@
+/******************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt JavaScript to C++ compiler.
+**
+** $QT_BEGIN_LICENSE:COMM$
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#include "birthdayparty.h"
+
+BirthdayParty::BirthdayParty(QObject *parent)
+ : QObject(parent), m_host(nullptr)
+{
+}
+
+Person *BirthdayParty::host() const
+{
+ return m_host;
+}
+
+void BirthdayParty::setHost(Person *c)
+{
+ if (c != m_host) {
+ m_host = c;
+ emit hostChanged();
+ }
+}
+
+QQmlListProperty<Person> BirthdayParty::guests()
+{
+ return {this, &m_guests};
+}
+
+int BirthdayParty::guestCount() const
+{
+ return m_guests.count();
+}
+
+Person *BirthdayParty::guest(int index) const
+{
+ return m_guests.at(index);
+}
+
+void BirthdayParty::invite(const QString &name)
+{
+ auto *person = new Person(this);
+ person->setName(name);
+ m_guests.append(person);
+}
+
+BirthdayPartyAttached *BirthdayParty::qmlAttachedProperties(QObject *object)
+{
+ return new BirthdayPartyAttached(object);
+}
+
+BirthdayPartyAttached::BirthdayPartyAttached(QObject *parent)
+ : QObject(parent), m_rsvp(QDateTime(QDate(1911, 3, 4), QTime()))
+{
+}
+
+QDateTime BirthdayPartyAttached::rsvp() const
+{
+ return m_rsvp.value();
+}
+
+void BirthdayPartyAttached::setRsvp(const QDateTime &rsvp)
+{
+ m_rsvp.setValue(rsvp);
+}
+
+QBindable<QDateTime> BirthdayPartyAttached::rsvpBindable()
+{
+ return QBindable<QDateTime>(&m_rsvp);
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/birthdayparty.h b/tests/auto/qml/qmlcppcodegen/data/birthdayparty.h
new file mode 100644
index 0000000000..2310352d48
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/birthdayparty.h
@@ -0,0 +1,117 @@
+/******************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt JavaScript to C++ compiler.
+**
+** $QT_BEGIN_LICENSE:COMM$
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#ifndef BIRTHDAYPARTY_H
+#define BIRTHDAYPARTY_H
+
+#include <QObject>
+#include <QQmlListProperty>
+#include <QProperty>
+#include <QBindable>
+#include <QDateTime>
+#include "person.h"
+
+class BirthdayPartyAttached : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QDateTime rsvp READ rsvp WRITE setRsvp BINDABLE rsvpBindable)
+ QML_ANONYMOUS
+public:
+ BirthdayPartyAttached(QObject *parent);
+
+ QDateTime rsvp() const;
+ void setRsvp(const QDateTime &rsvp);
+ QBindable<QDateTime> rsvpBindable();
+
+private:
+ QProperty<QDateTime> m_rsvp;
+};
+
+class BirthDayPartyExtended : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(int eee READ eee WRITE setEee NOTIFY eeeChanged)
+public:
+
+ BirthDayPartyExtended(QObject *parent) : QObject(parent) {}
+
+ int eee() const { return m_eee; }
+ void setEee(int eee)
+ {
+ if (eee != m_eee) {
+ m_eee = eee;
+ emit eeeChanged();
+ }
+ }
+
+signals:
+ void eeeChanged();
+
+private:
+ int m_eee = 25;
+};
+
+class BirthdayParty : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(Person *host READ host WRITE setHost NOTIFY hostChanged FINAL)
+ Q_PROPERTY(QQmlListProperty<Person> guests READ guests)
+ QML_ELEMENT
+ QML_ATTACHED(BirthdayPartyAttached)
+ QML_EXTENDED(BirthDayPartyExtended)
+public:
+ BirthdayParty(QObject *parent = nullptr);
+
+ Person *host() const;
+ void setHost(Person *);
+
+ QQmlListProperty<Person> guests();
+ int guestCount() const;
+ Person *guest(int) const;
+
+ Q_INVOKABLE void invite(const QString &name);
+ static BirthdayPartyAttached *qmlAttachedProperties(QObject *object);
+
+signals:
+ void hostChanged();
+
+private:
+ Person *m_host;
+ QList<Person *> m_guests;
+};
+
+class NastyBase : public QObject {
+public:
+ NastyBase(QObject *parent) : QObject(parent) {}
+};
+
+class Nasty : public NastyBase
+{
+ Q_OBJECT
+ QML_ELEMENT
+ QML_ATTACHED(Nasty)
+
+public:
+ Nasty(QObject *parent = nullptr) : NastyBase(parent) {}
+ static Nasty *qmlAttachedProperties(QObject *object) { return new Nasty(object); }
+};
+
+#endif // BIRTHDAYPARTY_H
diff --git a/tests/auto/qml/qmlcppcodegen/data/callContextPropertyLookupResult.qml b/tests/auto/qml/qmlcppcodegen/data/callContextPropertyLookupResult.qml
new file mode 100644
index 0000000000..f371b56909
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/callContextPropertyLookupResult.qml
@@ -0,0 +1,5 @@
+import QtQml
+
+QtObject {
+ property Component c: Qt.createComponent("Dummy.qml")
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/childobject.qml b/tests/auto/qml/qmlcppcodegen/data/childobject.qml
new file mode 100644
index 0000000000..3775ee16bc
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/childobject.qml
@@ -0,0 +1,15 @@
+import QtQml
+import TestTypes
+
+QtObject {
+ property ObjectWithMethod child: ObjectWithMethod {
+ objectName: "kraut"
+ }
+ objectName: child.objectName
+ property int doneThing: child.doThing()
+ property int usingFinal: child.fff
+
+ function setChildObjectName(name: string) {
+ child.objectName = name
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/colorAsVariant.qml b/tests/auto/qml/qmlcppcodegen/data/colorAsVariant.qml
new file mode 100644
index 0000000000..1162a2f5b3
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/colorAsVariant.qml
@@ -0,0 +1,7 @@
+import QtQuick
+import QtQuick.Templates as T
+
+T.Button {
+ property color a: "green"
+ icon.color: a
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/colorString.qml b/tests/auto/qml/qmlcppcodegen/data/colorString.qml
new file mode 100644
index 0000000000..3c0bff257a
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/colorString.qml
@@ -0,0 +1,13 @@
+pragma Strict
+import QtQuick
+
+QtObject {
+ id: self
+ property color c: "#aabbcc"
+ property color d: "#ccbbaa"
+ property color e: "#" + "112233"
+ Component.onCompleted: {
+ c = "#dddddd";
+ self.d = "#aaaaaa";
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/componentReturnType.qml b/tests/auto/qml/qmlcppcodegen/data/componentReturnType.qml
new file mode 100644
index 0000000000..69c237c97d
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/componentReturnType.qml
@@ -0,0 +1,15 @@
+import QtQml.Models
+import QtQuick
+
+Item {
+ id: root
+ property int count: 0
+ Component {
+ id: delegate
+ Item {
+ Component.onCompleted: root.count++
+ }
+ }
+
+ Repeater { model: 10; delegate: delegate }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/compositeTypeMethod.qml b/tests/auto/qml/qmlcppcodegen/data/compositeTypeMethod.qml
new file mode 100644
index 0000000000..9bae4c575c
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/compositeTypeMethod.qml
@@ -0,0 +1,12 @@
+import QtQuick
+
+Item {
+ id: root
+ signal foo()
+
+ Timer {
+ interval: 10
+ running: true
+ onTriggered: root.foo()
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/compositesingleton.qml b/tests/auto/qml/qmlcppcodegen/data/compositesingleton.qml
new file mode 100644
index 0000000000..42db13154e
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/compositesingleton.qml
@@ -0,0 +1,8 @@
+import QtQml
+import QmlBench
+
+QtObject {
+ property real x: Globals.realProp
+ property real y: Globals.intProp
+ property bool smooth: Globals.boolProp
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/construct.qml b/tests/auto/qml/qmlcppcodegen/data/construct.qml
new file mode 100644
index 0000000000..19d3322992
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/construct.qml
@@ -0,0 +1,12 @@
+import QtQml
+
+QtObject {
+ property var foo: new Error("bar")
+ property int aaa: 12
+
+ function ouch() {
+ aaa = 13;
+ throw new Error("ouch")
+ aaa = 25;
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/contextParam.qml b/tests/auto/qml/qmlcppcodegen/data/contextParam.qml
new file mode 100644
index 0000000000..63c057ca79
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/contextParam.qml
@@ -0,0 +1,5 @@
+import QML
+
+QtObject {
+ property int foo: contextParam.foo
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/conversions.qml b/tests/auto/qml/qmlcppcodegen/data/conversions.qml
new file mode 100644
index 0000000000..dc76404f2f
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/conversions.qml
@@ -0,0 +1,130 @@
+import QML
+
+QtObject {
+ id: self
+ property var aNull: null
+ property var anInt: 5
+ property var aZero: 0
+ property var anArray: []
+ property var anObject: ({a: 12, b: 14, c: "somestring"})
+
+ property int cmpEqInt: {
+ if ({} == 12)
+ return 8;
+ if ([] == 12)
+ return 2;
+ if (null == 12)
+ return 3;
+ if (undefined == 12)
+ return 4;
+ if (12 == 12)
+ return 17;
+ return 5;
+ }
+
+ property bool nullIsNull: aNull == null
+ property bool intIsNull: anInt == null
+ property bool zeroIsNull: aZero == null
+ property bool arrayIsNull: anArray == null
+ property bool objectIsNull: anObject == null
+
+ property bool nullIsNotNull: aNull != null
+ property bool intIsNotNull: anInt != null
+ property bool zeroIsNotNull: aZero != null
+ property bool arrayIsNotNull: anArray != null
+ property bool objectIsNotNull: anObject != null
+
+ property bool boolEqualsBool: nullIsNull == zeroIsNull
+ property bool boolNotEqualsBool: nullIsNull != zeroIsNull
+
+ property var aInObject: "a" in anObject
+ property var lengthInArray: "length" in anArray
+ property var fooInNumber: "foo" in 12
+ property var numberInObject: 12 in anObject
+ property var numberInArray: 2 in [1, 12, 3]
+
+ property real varPlusVar: aInObject + lengthInArray
+ property real varMinusVar: lengthInArray - fooInNumber
+ property real varTimesVar: fooInNumber * numberInObject
+ property real varDivVar: numberInObject / numberInArray
+
+ property var stringPlusString: "12" + "20"
+ property var stringMinusString: "12" - "20"
+ property var stringTimesString: "12" * "20"
+ property var stringDivString: "12" / "20"
+
+ property string uglyString: "with\nnewline" + 'with"quot' + "with\\slashes";
+
+ property QtObject nullObject1: aInObject ? null : self
+ property QtObject nullObject2: {
+ if (lengthInArray)
+ return self
+ if (aInObject)
+ return null;
+ return undefined;
+ }
+
+ property var doneStuff
+ function doStuff(stuff) {
+ var a = {g: 0, h: 5};
+ var b = {i: 6, j: 7};
+ var c = {k: 9, l: 9};
+
+ a.g = 4;
+
+ doneStuff = a.g + b.i + c.l
+ return stuff[3] ? stuff[3] : stuff.z
+ }
+
+ property var passObjectLiteral: doStuff({"x": 13, "y": 17, "z": 11})
+ property var passArrayLiteral: doStuff([1, 2, 3, 4])
+
+ property bool neNull: {
+ var a = anArray[12]
+ if (a)
+ return true;
+ else
+ return false;
+ }
+
+ property bool eqNull: {
+ var a = anArray[12]
+ if (!a)
+ return true;
+ else
+ return false;
+ }
+
+ property string undefinedType: typeof undefined
+ property string booleanType: typeof true
+ property string numberType: typeof 12
+ property string stringType: typeof "bah"
+ property string objectType: typeof null
+ property string symbolType: typeof Symbol("baz")
+
+ property var modulos: [
+ -20 % -1,
+ 0 % 1,
+ 20 % 4.4,
+ -4.4 % true,
+ false % "11",
+ undefined % null,
+ {} % []
+ ]
+
+ property var unaryOps: {
+ var a = stringPlusString;
+ var b = stringPlusString;
+ var c = +stringPlusString;
+ var d = -stringPlusString;
+ var e = a++;
+ var f = b--;
+ return [ a, b, c, d, e, f ];
+ }
+
+ function retUndefined() {
+ var a = 5 + 12;
+ if (false)
+ return a;
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/conversions2.qml b/tests/auto/qml/qmlcppcodegen/data/conversions2.qml
new file mode 100644
index 0000000000..e7bde6c3e6
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/conversions2.qml
@@ -0,0 +1,93 @@
+import QtQuick
+import QtTest
+
+Item {
+ id: spy
+ visible: false
+
+ TestUtil {
+ id: util
+ }
+
+ property var target: null
+ property string signalName: ""
+ readonly property alias count: spy.qtest_count
+ readonly property alias valid:spy.qtest_valid
+ readonly property alias signalArguments:spy.qtest_signalArguments
+
+ function clear() {
+ qtest_count = 0
+ qtest_expectedCount = 0
+ qtest_signalArguments = []
+ }
+
+ function wait(timeout) {
+ if (timeout === undefined)
+ timeout = 5000
+ var expected = ++qtest_expectedCount
+ var i = 0
+ while (i < timeout && qtest_count < expected) {
+ qtest_results.wait(50)
+ i += 50
+ }
+ var success = (qtest_count >= expected)
+ if (!qtest_results.verify(success, "wait for signal " + signalName, util.callerFile(), util.callerLine()))
+ return false;
+ }
+
+ TestResult { id: qtest_results }
+
+ onTargetChanged: {
+ qtest_update()
+ }
+ onSignalNameChanged: {
+ qtest_update()
+ }
+
+ property var qtest_prevTarget: null
+ property string qtest_prevSignalName: ""
+ property int qtest_expectedCount: 0
+ property var qtest_signalArguments:[]
+ property int qtest_count: 0
+ property bool qtest_valid:false
+
+ function qtest_update() {
+ if (qtest_prevTarget != null) {
+ var prevHandlerName = qtest_signalHandlerName(qtest_prevSignalName)
+ var prevFunc = qtest_prevTarget[prevHandlerName]
+ if (prevFunc)
+ prevFunc.disconnect(spy.qtest_activated)
+ qtest_prevTarget = null
+ qtest_prevSignalName = ""
+ }
+ if (target != null && signalName != "") {
+ var func = target[signalName]
+ if (typeof func !== "function") {
+ func = target[qtest_signalHandlerName(signalName)]
+ }
+ if (func === undefined) {
+ spy.qtest_valid = false
+ console.log("Signal '" + signalName + "' not found")
+ } else {
+ qtest_prevTarget = target
+ qtest_prevSignalName = signalName
+ func.connect(spy.qtest_activated)
+ spy.qtest_valid = true
+ spy.qtest_signalArguments = []
+ }
+ } else {
+ spy.qtest_valid = false
+ }
+ }
+
+ function qtest_activated() {
+ ++qtest_count
+ spy.qtest_signalArguments[spy.qtest_signalArguments.length] = [1, 2, 3]
+ }
+
+ function qtest_signalHandlerName(sn) {
+ if (sn.substr(0, 2) === "on" && sn[2] === sn[2].toUpperCase())
+ return sn
+ return "on" + sn.substr(0, 1).toUpperCase() + sn.substr(1)
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/cppbaseclass.h b/tests/auto/qml/qmlcppcodegen/data/cppbaseclass.h
new file mode 100644
index 0000000000..e518ea0299
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/cppbaseclass.h
@@ -0,0 +1,63 @@
+/******************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt JavaScript to C++ compiler.
+**
+** $QT_BEGIN_LICENSE:COMM$
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#ifndef CPPBASECLASS_H
+#define CPPBASECLASS_H
+
+#include <QtCore/qobject.h>
+#include <QtCore/qproperty.h>
+#include <QtQml/qqml.h>
+
+class CppBaseClass : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(int cppProp MEMBER cppProp BINDABLE cppPropBindable FINAL)
+ Q_PROPERTY(int cppProp2 MEMBER cppProp2 BINDABLE cppProp2Bindable FINAL)
+ QML_ELEMENT
+public:
+ QProperty<int> cppProp;
+ QBindable<int> cppPropBindable() { return QBindable<int>(&cppProp); }
+
+ QProperty<int> cppProp2;
+ QBindable<int> cppProp2Bindable() { return QBindable<int>(&cppProp2); }
+
+ Q_INVOKABLE void doCall(QObject *foo);
+};
+
+inline void CppBaseClass::doCall(QObject *foo)
+{
+ cppProp = foo ? 17 : 18;
+}
+
+class CppSingleton : public QObject
+{
+ Q_OBJECT
+ QML_ELEMENT
+ QML_SINGLETON
+
+public:
+ explicit CppSingleton(QObject *parent = nullptr) : QObject(parent)
+ {
+ setObjectName(QStringLiteral("ItIsTheSingleton"));
+ }
+};
+
+#endif // CPPBASECLASS_H
diff --git a/tests/auto/qml/qmlcppcodegen/data/curlygrouped.qml b/tests/auto/qml/qmlcppcodegen/data/curlygrouped.qml
new file mode 100644
index 0000000000..0f0e8c03f2
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/curlygrouped.qml
@@ -0,0 +1,7 @@
+pragma Strict
+import QtQuick
+
+Item {
+ Item { id: foo }
+ anchors { left: foo.left }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/deadShoeSize.qml b/tests/auto/qml/qmlcppcodegen/data/deadShoeSize.qml
new file mode 100644
index 0000000000..4dddab900a
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/deadShoeSize.qml
@@ -0,0 +1,6 @@
+pragma Strict
+import TestTypes
+
+Person {
+ shoeSize: pain
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/dialog.qml b/tests/auto/qml/qmlcppcodegen/data/dialog.qml
new file mode 100644
index 0000000000..d33a082741
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/dialog.qml
@@ -0,0 +1,21 @@
+import QtQuick
+import QtQuick.Templates as T
+import QtQuick.Controls.Basic
+
+T.Dialog {
+ id: control
+ header: Label {
+ background: Rectangle {
+ width: parent.width + 1
+ height: parent.height - 1
+ }
+ }
+
+ property var n: 10
+ property var a: {
+ var x = 1;
+ for (var i=0; i<n; ++i)
+ x = x + x;
+ return x;
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/dynamicscene.qml b/tests/auto/qml/qmlcppcodegen/data/dynamicscene.qml
new file mode 100644
index 0000000000..7699e7f2be
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/dynamicscene.qml
@@ -0,0 +1,33 @@
+/******************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt JavaScript to C++ compiler.
+**
+** $QT_BEGIN_LICENSE:COMM$
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+import QtQml
+
+QtObject {
+ id: self
+ onObjectNameChanged: {
+ try {
+ Qt.createQmlObject("1", self, 'CustomObject');
+ } catch(err) {
+ }
+ }
+}
+
diff --git a/tests/auto/qml/qmlcppcodegen/data/enumInvalid.qml b/tests/auto/qml/qmlcppcodegen/data/enumInvalid.qml
new file mode 100644
index 0000000000..a14955d3e2
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/enumInvalid.qml
@@ -0,0 +1,6 @@
+import QtQml
+
+QtObject {
+ property bool c: Qt.red > 4
+ property bool d: Qt.red < 2
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/enumScope.qml b/tests/auto/qml/qmlcppcodegen/data/enumScope.qml
new file mode 100644
index 0000000000..d385346c87
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/enumScope.qml
@@ -0,0 +1,5 @@
+import QtQuick.Layouts
+
+GridLayout {
+ flow: GridLayout.TopToBottom
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/enumsInOtherObject.qml b/tests/auto/qml/qmlcppcodegen/data/enumsInOtherObject.qml
new file mode 100644
index 0000000000..5cf620ba68
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/enumsInOtherObject.qml
@@ -0,0 +1,9 @@
+import QtQml
+
+QtObject {
+ property Enums app: Enums {
+ appState: 0
+ }
+
+ property string color: app.color
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/enumsUser.qml b/tests/auto/qml/qmlcppcodegen/data/enumsUser.qml
new file mode 100644
index 0000000000..13e26737b7
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/enumsUser.qml
@@ -0,0 +1,8 @@
+pragma Strict
+import TestTypes
+
+QtObject {
+ function getEnum(): int {
+ return Test.AA + Test.BB + Test.CC
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/equalsUndefined.qml b/tests/auto/qml/qmlcppcodegen/data/equalsUndefined.qml
new file mode 100644
index 0000000000..86a2568b21
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/equalsUndefined.qml
@@ -0,0 +1,18 @@
+import QtQuick
+
+Item {
+ function wait(timeout) {
+ if (timeout === undefined)
+ timeout = 5000
+
+ var i = 0;
+ while (i < timeout) {
+ i += 50
+ }
+
+ return i
+ }
+
+ property var a: wait(10)
+ property var b: wait()
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/excessiveParameters.qml b/tests/auto/qml/qmlcppcodegen/data/excessiveParameters.qml
new file mode 100644
index 0000000000..1b801d4d02
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/excessiveParameters.qml
@@ -0,0 +1,14 @@
+import QtQml
+
+QtObject {
+ id: root
+ signal testSignal(string a, int b, string c, bool d, bool e, real f, real g, bool h, int i, int j, string k, int l, string m, string n)
+ signal foo()
+ onTestSignal: foo()
+
+ property Timer timer: Timer {
+ interval: 10
+ running: true
+ onTriggered: root.testSignal("a", 1, "b", true, true, 0.1, 0.1, true, 1, 1, "a", 1, "a", "a")
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/extendedTypes.qml b/tests/auto/qml/qmlcppcodegen/data/extendedTypes.qml
new file mode 100644
index 0000000000..a7012e1b29
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/extendedTypes.qml
@@ -0,0 +1,11 @@
+import QtQml
+
+QtObject {
+ property int a: Qt.LeftButton + 5
+ property size b: Qt.size(10, 20)
+ property int c: b.width + b.height
+ property string d: b.toString();
+ property int e: Locale.ImperialUKSystem
+
+ Component.onCompleted: console.log(a, b, c)
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/failures.qml b/tests/auto/qml/qmlcppcodegen/data/failures.qml
new file mode 100644
index 0000000000..d1af90c2e6
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/failures.qml
@@ -0,0 +1,15 @@
+import QtQml
+import TestTypes
+
+QtObject {
+ property string attachedForNonObject: objectName.Component.objectName
+ property string attachedForNasty: Nasty.objectName
+
+ property Nasty nasty: Nasty {
+ objectName: Component.objectName
+ }
+
+ onFooBar: console.log("nope")
+
+ function doesReturnValue() { return 5; }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/fileDialog.qml b/tests/auto/qml/qmlcppcodegen/data/fileDialog.qml
new file mode 100644
index 0000000000..febad45ca6
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/fileDialog.qml
@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Dialogs
+
+ApplicationWindow {
+ width: 640
+ height: 480
+
+ property alias dialog: dialog
+
+ function doneAccepted() {
+ dialog.done(FileDialog.Accepted)
+ }
+
+ function doneRejected() {
+ dialog.done(FileDialog.Rejected)
+ }
+
+ FileDialog {
+ id: dialog
+ objectName: "FileDialog"
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/funcWithParams.qml b/tests/auto/qml/qmlcppcodegen/data/funcWithParams.qml
new file mode 100644
index 0000000000..b0c9780463
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/funcWithParams.qml
@@ -0,0 +1,7 @@
+pragma Strict
+import QtQml
+
+QtObject {
+ function foo(a: int) : int { return a + 10 }
+ property int bar: foo(10 + 10)
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/globals.qml b/tests/auto/qml/qmlcppcodegen/data/globals.qml
new file mode 100644
index 0000000000..a17894cd17
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/globals.qml
@@ -0,0 +1,24 @@
+import QtQml
+
+QtObject {
+ id: root
+
+ property QtObject application: Qt.application
+
+ property Timer t: Timer {
+ interval: 1
+ running: true
+ onTriggered: Qt.exit(0)
+ }
+
+ property Connections c: Connections {
+ target: root.application
+ function onAboutToQuit() { console.log("End"); }
+ }
+
+ Component.onCompleted: console.log("Start", 2, Qt.application.arguments[1]);
+
+ property url somewhere: Qt.resolvedUrl("/somewhere/else.qml")
+ property url plain: "/not/here.qml"
+ property string somewhereString: somewhere
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/idAccess.qml b/tests/auto/qml/qmlcppcodegen/data/idAccess.qml
new file mode 100644
index 0000000000..2090926872
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/idAccess.qml
@@ -0,0 +1,15 @@
+import QtQuick
+
+Item {
+ id: root
+ y: z
+ onXChanged: {
+ root.y = 48
+ ttt.font.pointSize = 22
+ }
+ z: 12
+
+ Text {
+ id: ttt
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/immediateQuit.qml b/tests/auto/qml/qmlcppcodegen/data/immediateQuit.qml
new file mode 100644
index 0000000000..1da9d1201a
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/immediateQuit.qml
@@ -0,0 +1,8 @@
+import QtQml 2.0
+
+QtObject {
+ Component.onCompleted: {
+ console.log("End: " + Qt.application.arguments[1]);
+ Qt.quit()
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/imports/Module/NotCompiled.qml b/tests/auto/qml/qmlcppcodegen/data/imports/Module/NotCompiled.qml
new file mode 100644
index 0000000000..6f672b4af9
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/imports/Module/NotCompiled.qml
@@ -0,0 +1,6 @@
+import QtQml
+
+QtObject {
+ objectName: "not compiled"
+ property int foo: 12
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/imports/Module/qmldir b/tests/auto/qml/qmlcppcodegen/data/imports/Module/qmldir
new file mode 100644
index 0000000000..a1e0ebc5cd
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/imports/Module/qmldir
@@ -0,0 +1,2 @@
+module Module
+NotCompiled 1.0 NotCompiled.qml
diff --git a/tests/auto/qml/qmlcppcodegen/data/imports/QmlBench/Globals.qml b/tests/auto/qml/qmlcppcodegen/data/imports/QmlBench/Globals.qml
new file mode 100644
index 0000000000..6531a1e7ec
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/imports/QmlBench/Globals.qml
@@ -0,0 +1,10 @@
+pragma Singleton
+import QtQuick 2.0
+
+QtObject {
+ readonly property string stringProp: "stringValue"
+ readonly property int intProp: 10
+ readonly property real realProp: 4.5
+ readonly property color colorProp: "green"
+ readonly property bool boolProp: true
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/imports/QmlBench/qmldir b/tests/auto/qml/qmlcppcodegen/data/imports/QmlBench/qmldir
new file mode 100644
index 0000000000..66bddd1ca1
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/imports/QmlBench/qmldir
@@ -0,0 +1,2 @@
+module QmlBench
+singleton Globals 1.0 Globals.qml
diff --git a/tests/auto/qml/qmlcppcodegen/data/importsFromImportPath.qml b/tests/auto/qml/qmlcppcodegen/data/importsFromImportPath.qml
new file mode 100644
index 0000000000..18a6f281f5
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/importsFromImportPath.qml
@@ -0,0 +1,5 @@
+import Module
+
+NotCompiled {
+ property int bar: foo + 16
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/intEnumCompare.qml b/tests/auto/qml/qmlcppcodegen/data/intEnumCompare.qml
new file mode 100644
index 0000000000..e494d0d3e3
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/intEnumCompare.qml
@@ -0,0 +1,9 @@
+pragma Strict
+import QtQml
+
+QtObject {
+ property bool a: Component.Asynchronous == 1
+ property bool b: Component.Asynchronous != 1
+ property bool c: Qt.BottomEdge > 4
+ property bool d: Qt.BottomEdge < 2
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/intOverflow.qml b/tests/auto/qml/qmlcppcodegen/data/intOverflow.qml
new file mode 100644
index 0000000000..cbb5e2d4a7
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/intOverflow.qml
@@ -0,0 +1,7 @@
+pragma Strict
+import QtQml
+
+QtObject {
+ property real a: 1024 * 1024 * 1024 * 1024 + 5
+ property int b: 1024 * 1024 * 1024 * 1024 + 5
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/interactive.qml b/tests/auto/qml/qmlcppcodegen/data/interactive.qml
new file mode 100644
index 0000000000..5367ca103f
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/interactive.qml
@@ -0,0 +1,68 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+pragma Strict
+import QtQuick 2.9
+import QtQuick.Shapes 1.0
+
+Rectangle {
+ id: root
+
+ Row {
+ Slider {
+ id: widthSlider
+ }
+
+ property ShapePath linePath: ShapePath {
+ id: lineShapePath
+ strokeWidth: widthSlider.value
+ }
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/interceptor.qml b/tests/auto/qml/qmlcppcodegen/data/interceptor.qml
new file mode 100644
index 0000000000..5ee973933f
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/interceptor.qml
@@ -0,0 +1,53 @@
+/******************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt JavaScript to C++ compiler.
+**
+** $QT_BEGIN_LICENSE:COMM$
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+import QtQuick
+import TestTypes
+
+Item {
+ Behavior on width { PropertyAnimation {} }
+
+ CppBaseClass {
+ id: withBehavior
+ Behavior on cppProp { PropertyAnimation {} }
+ Component.onCompleted: cppProp = 200
+ }
+ height: withBehavior.cppProp
+
+ CppBaseClass {
+ id: withoutBehavior
+ Component.onCompleted: cppProp = 100
+ }
+ x: withoutBehavior.cppProp
+
+ Component.onCompleted: {
+ width = 200
+ y = 100
+ }
+
+ CppBaseClass {
+ id: qPropertyBinder
+ cppProp: withoutBehavior.cppProp + withBehavior.cppProp
+ }
+
+ property int qProperty1: qPropertyBinder.cppProp
+ property int qProperty2: withoutBehavior.cppProp + withBehavior.cppProp
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/jsMathObject.qml b/tests/auto/qml/qmlcppcodegen/data/jsMathObject.qml
new file mode 100644
index 0000000000..795e38b652
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/jsMathObject.qml
@@ -0,0 +1,64 @@
+/******************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt JavaScript to C++ compiler.
+**
+** $QT_BEGIN_LICENSE:COMM$
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+pragma Strict
+import QtQml
+
+QtObject {
+ property double a: 0
+ property double b: 0
+
+ property double abs: Math.abs(a)
+ property double acos: Math.acos(a)
+ property double acosh: Math.acosh(a)
+ property double asin: Math.asin(a)
+ property double asinh: Math.asinh(a)
+ property double atan: Math.atan(a)
+ property double atanh: Math.atanh(a)
+// property double atan2: Math.atan2(a)
+ property double cbrt: Math.cbrt(a)
+ property double ceil: Math.ceil(a)
+ property double clz32: Math.clz32(a)
+ property double cos: Math.cos(a)
+ property double cosh: Math.cosh(a)
+ property double exp: Math.exp(a)
+// property double expm1: Math.expm1(a)
+ property double floor: Math.floor(a)
+ property double fround: Math.fround(a)
+// property double hypot: Math.hypot(a)
+ property double imul: Math.imul(a, b)
+ property double log: Math.log(a)
+ property double log10: Math.log10(a)
+ property double log1p: Math.log1p(a)
+ property double log2: Math.log2(a)
+ property double max: Math.max(a, b)
+ property double min: Math.min(a, b)
+// property double pow: Math.pow(a, b)
+ property double random: Math.random()
+ property double round: Math.round(a)
+ property double sign: Math.sign(a)
+ property double sin: Math.sin(a)
+ property double sinh: Math.sinh(a)
+ property double sqrt: Math.sqrt(a)
+ property double tan: Math.tan(a)
+ property double tanh: Math.tanh(a)
+ property double trunc: Math.trunc(a)
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/jsimport.qml b/tests/auto/qml/qmlcppcodegen/data/jsimport.qml
new file mode 100644
index 0000000000..9c40878e60
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/jsimport.qml
@@ -0,0 +1,6 @@
+import QtQml 2.0
+import "script.js" as Script
+
+QtObject {
+ property int value: Script.getter()
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/jsmoduleimport.qml b/tests/auto/qml/qmlcppcodegen/data/jsmoduleimport.qml
new file mode 100644
index 0000000000..d9a4fc29fb
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/jsmoduleimport.qml
@@ -0,0 +1,7 @@
+import QtQml 2.0
+import "script.mjs" as Script
+
+QtObject {
+ property bool ok: Script.ok()
+ property var okFunc: Script.ok
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/layouts.qml b/tests/auto/qml/qmlcppcodegen/data/layouts.qml
new file mode 100644
index 0000000000..d3fe4efbd7
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/layouts.qml
@@ -0,0 +1,131 @@
+/******************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt JavaScript to C++ compiler.
+**
+** $QT_BEGIN_LICENSE:COMM$
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+import QtQuick
+import QtQuick.Layouts
+
+Rectangle {
+ id: appWindow
+
+ visible: false
+ objectName: "Basic layouts"
+ property int margin: 11
+
+ Component.onCompleted: {
+ width = mainLayout.implicitWidth + 2 * margin
+ height = mainLayout.implicitHeight + 2 * margin
+ }
+
+ width: mainLayout.Layout.minimumWidth + 2 * margin
+ height: mainLayout.Layout.minimumHeight + 2 * margin
+
+ ColumnLayout {
+ id: mainLayout
+ anchors.fill: parent
+ anchors.margins: appWindow.margin
+ Rectangle {
+ id: rowBox
+ objectName: "Row layout"
+ Layout.fillWidth: true
+ Layout.minimumWidth: rowLayout.Layout.minimumWidth + 30
+
+ RowLayout {
+ id: rowLayout
+ anchors.fill: parent
+ Item {
+ objectName: "This wants to grow horizontally"
+ Layout.fillWidth: true
+ }
+ Item {
+ property string text: "Button"
+ }
+ }
+ }
+
+ Rectangle {
+ id: gridBox
+ objectName: "Grid layout"
+ Layout.fillWidth: true
+ Layout.minimumWidth: gridLayout.Layout.minimumWidth + 30
+
+ GridLayout {
+ id: gridLayout
+ rows: 3
+ flow: GridLayout.TopToBottom
+ anchors.fill: parent
+
+ Item { property string text: "Line 1" }
+ Item { property string text: "Line 2" }
+ Item { property string text: "Line 3" }
+
+ Item { }
+ Item { }
+ Item { }
+
+ Item {
+ property string text: "This widget spans over three rows in the GridLayout.\n"
+ + "All items in the GridLayout are implicitly positioned from top to bottom."
+ property int wrapMode: Text.WordWrap
+ Layout.rowSpan: 3
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ Layout.minimumHeight: implicitHeight
+ Layout.minimumWidth: 100 // guesstimate, should be size of largest word
+ }
+ }
+ }
+ Item {
+ id: t3
+ property string text: "This fills the whole cell"
+ Layout.minimumHeight: 30
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ }
+ Rectangle {
+ id: stackBox
+ objectName: "Stack layout"
+ implicitWidth: 200
+ implicitHeight: 60
+ Layout.minimumHeight: 60
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ StackLayout {
+ id: stackLayout
+ anchors.fill: parent
+
+ function advance() { currentIndex = (currentIndex + 1) % count }
+
+ Repeater {
+ id: stackRepeater
+ model: 5
+ Rectangle {
+ required property int index
+ color: Qt.hsla((0.5 + index) / stackRepeater.count, 0.3, 0.7, 1)
+ MouseArea {
+ anchors.centerIn: parent
+ onClicked: stackLayout.advance()
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/library.js b/tests/auto/qml/qmlcppcodegen/data/library.js
new file mode 100644
index 0000000000..51fb41dc23
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/library.js
@@ -0,0 +1,4 @@
+
+function getter() {
+ return 42;
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/listIndices.qml b/tests/auto/qml/qmlcppcodegen/data/listIndices.qml
new file mode 100644
index 0000000000..b5fda4ef0d
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/listIndices.qml
@@ -0,0 +1,14 @@
+pragma Strict
+import QtQml
+
+QtObject {
+ id: self
+ property list<QtObject> items
+ property int numItems: items.length
+
+ Component.onCompleted: {
+ items.length = 3
+ for (var i = 0; i < 3; ++i)
+ items[i] = self
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/listlength.qml b/tests/auto/qml/qmlcppcodegen/data/listlength.qml
new file mode 100644
index 0000000000..2a5832aa03
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/listlength.qml
@@ -0,0 +1,8 @@
+import QtQuick
+
+Item {
+ property int l: children.length
+
+ Item {}
+ Item {}
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/math.qml b/tests/auto/qml/qmlcppcodegen/data/math.qml
new file mode 100644
index 0000000000..cc6cd3741a
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/math.qml
@@ -0,0 +1,6 @@
+import QML
+
+QtObject {
+ property int a: Math.max(5, 7, 9, -111)
+ property var b: 50 / 22
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/methods.qml b/tests/auto/qml/qmlcppcodegen/data/methods.qml
new file mode 100644
index 0000000000..3abd14c9c1
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/methods.qml
@@ -0,0 +1,56 @@
+import QtQuick
+import TestTypes
+
+BirthdayParty {
+ id: party
+ host: Person {
+ name: "Bob Jones"
+ shoeSize: 12
+ }
+ guests: [
+ Person { name: "Leo Hodges" },
+ Person { name: "Jack Smith" },
+ Person { name: "Anne Brown" }
+ ]
+
+ property var dresses: [0, 0, 0, 0]
+
+ property var foo: party
+ property var bar: console
+
+ property var numeric
+ property var stringly
+
+ Component.onCompleted: {
+ invite("William Green")
+ foo.invite("The Foo")
+ bar.log("The Bar")
+ numeric = 1;
+ stringly = "name";
+ }
+
+ function storeElement() {
+ ++host.shoeSize
+ party.dresses[2] = [1, 2, 3]
+ }
+
+ function stuff(sn) {
+ if (sn.substr(0, 2) === "on" && sn[2] === sn[2].toUpperCase())
+ return sn
+ return "on" + sn.substr(0, 1).toUpperCase() + sn.substr(1)
+ }
+
+ function retrieveVar() {
+ return guests[numeric][stringly];
+ }
+
+ function retrieveString() : string {
+ return guests[numeric][stringly];
+ }
+
+ property var n1: stuff("onGurk")
+ property var n2: stuff("semmeln")
+ property var n3: stuff(12)
+
+ property int enumValue: Item.TopRight
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/modulePrefix.qml b/tests/auto/qml/qmlcppcodegen/data/modulePrefix.qml
new file mode 100644
index 0000000000..eb12882dd7
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/modulePrefix.qml
@@ -0,0 +1,10 @@
+pragma Strict
+import QtQml
+import TestTypes as T
+
+QtObject {
+ id: self
+ property date foo: self.T.BirthdayParty.rsvp
+ property date bar: T.BirthdayParty.rsvp
+ property string baz: T.CppSingleton.objectName
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/noQQmlData.qml b/tests/auto/qml/qmlcppcodegen/data/noQQmlData.qml
new file mode 100644
index 0000000000..47e1d34183
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/noQQmlData.qml
@@ -0,0 +1,10 @@
+import QtQml
+import TestTypes
+
+BirthdayParty {
+ id: party
+ property string inDaHouse: " in da house!"
+ property string n: host.name + inDaHouse
+ function burn() { host = mrBurns.createObject(); }
+ property Component mrBurns: Person { name: "Mr Burns" }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/nonNotifyable.qml b/tests/auto/qml/qmlcppcodegen/data/nonNotifyable.qml
new file mode 100644
index 0000000000..13102ea638
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/nonNotifyable.qml
@@ -0,0 +1,14 @@
+import TestTypes
+import QtQml
+
+QtObject {
+ id: parent
+ property date dayz: BirthdayParty.rsvp
+ property QtObject o: QtObject {}
+
+ property date oParty: o.BirthdayParty.rsvp
+ Component.onCompleted: {
+ BirthdayParty.rsvp = new Date(2121, 0, 12);
+ o.BirthdayParty.rsvp = new Date(2111, 11, 11);
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/noscope.qml b/tests/auto/qml/qmlcppcodegen/data/noscope.qml
new file mode 100644
index 0000000000..298c00175c
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/noscope.qml
@@ -0,0 +1,10 @@
+import QtQuick
+
+Canvas {
+ onPaint: {
+ // Does not actually compile to C++ because getContext() is not properly specified
+ // However, it shouldn't crash
+ var ctx = getContext("2d");
+ ctx.clearRect(0, 0, width, height);
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/nullAccess.qml b/tests/auto/qml/qmlcppcodegen/data/nullAccess.qml
new file mode 100644
index 0000000000..ba2b59feb1
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/nullAccess.qml
@@ -0,0 +1,7 @@
+import QtQuick
+
+Item {
+ width: ListView.view.width+40
+ height: ListView.view.height
+ Component.onCompleted: ListView.view.height = 10
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/objectwithmethod.h b/tests/auto/qml/qmlcppcodegen/data/objectwithmethod.h
new file mode 100644
index 0000000000..6af7965628
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/objectwithmethod.h
@@ -0,0 +1,77 @@
+/******************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt JavaScript to C++ compiler.
+**
+** $QT_BEGIN_LICENSE:COMM$
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#ifndef OBJECTWITHMETOD_H
+#define OBJECTWITHMETOD_H
+
+#include <QtCore/qobject.h>
+#include <QtCore/qproperty.h>
+#include <QtQml/qqml.h>
+
+// Make objectName available. It doesn't exist on the builtin QtObject type
+struct QObjectForeignForObjectName {
+ Q_GADGET
+ QML_FOREIGN(QObject)
+ QML_ANONYMOUS
+};
+
+class ObjectWithMethod : public QObject
+{
+ Q_OBJECT
+ QML_ELEMENT
+ Q_PROPERTY(int fff MEMBER theThing BINDABLE theThingBindable FINAL)
+
+public:
+ ObjectWithMethod(QObject *parent = nullptr) : QObject(parent) { theThing = 5; }
+
+ Q_INVOKABLE int doThing() const { return theThing; }
+ QProperty<int> theThing;
+ QBindable<int> theThingBindable() { return QBindable<int>(&theThing); }
+};
+
+class OverriddenObjectName : public ObjectWithMethod
+{
+ Q_OBJECT
+ Q_PROPERTY(QString objectName READ objectName WRITE setObjectName BINDABLE objectNameBindable)
+
+ // This shouldn't work
+ Q_PROPERTY(int fff READ fff BINDABLE nothingBindable)
+
+public:
+ OverriddenObjectName(QObject *parent = nullptr) : ObjectWithMethod(parent)
+ {
+ m_objectName = u"borschtsch"_qs;
+ nothing = 77;
+ }
+
+ QString objectName() const { return m_objectName.value(); }
+ void setObjectName(const QString &objectName) { m_objectName.setValue(objectName); }
+ QBindable<QString> objectNameBindable() { return QBindable<QString>(&m_objectName); }
+ Q_INVOKABLE QString doThing() const { return u"7"_qs; }
+
+ int fff() const { return nothing.value(); }
+ QBindable<int> nothingBindable() { return QBindable<int>(&nothing); }
+private:
+ QProperty<int> nothing;
+ QProperty<QString> m_objectName;
+};
+
+#endif // OBJECTWITHMETHOD_H
diff --git a/tests/auto/qml/qmlcppcodegen/data/outOfBounds.qml b/tests/auto/qml/qmlcppcodegen/data/outOfBounds.qml
new file mode 100644
index 0000000000..14933a0792
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/outOfBounds.qml
@@ -0,0 +1,6 @@
+import QtQuick
+
+Item {
+ property var oob: children[12]
+ Component.onCompleted: console.log("oob", children[11])
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/overriddenMember.qml b/tests/auto/qml/qmlcppcodegen/data/overriddenMember.qml
new file mode 100644
index 0000000000..296c852e1e
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/overriddenMember.qml
@@ -0,0 +1,6 @@
+pragma Strict
+import QtQml
+
+BaseMember {
+ property int ppp: 16
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/ownProperty.qml b/tests/auto/qml/qmlcppcodegen/data/ownProperty.qml
new file mode 100644
index 0000000000..70e850f4eb
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/ownProperty.qml
@@ -0,0 +1,12 @@
+pragma Strict
+import QtQml
+
+QtObject {
+ property QtObject bar: QtObject {
+ id: inner
+ property string a: objectName
+ objectName: "foo"
+ }
+
+ objectName: inner.a
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/page.qml b/tests/auto/qml/qmlcppcodegen/data/page.qml
new file mode 100644
index 0000000000..ee79fba4c0
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/page.qml
@@ -0,0 +1,41 @@
+/******************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt JavaScript to C++ compiler.
+**
+** $QT_BEGIN_LICENSE:COMM$
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+import QtQuick
+import QtQuick.Controls.impl
+import QtQuick.Templates as T
+
+T.Page {
+ id: control
+
+ implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
+ contentWidth + leftPadding + rightPadding,
+ implicitHeaderWidth,
+ implicitFooterWidth)
+ implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
+ contentHeight + topPadding + bottomPadding
+ + (implicitHeaderHeight > 0 ? implicitHeaderHeight + spacing : 0)
+ + (implicitFooterHeight > 0 ? implicitFooterHeight + spacing : 0))
+
+ background: Rectangle {
+ color: control.palette.window
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/parentProp.qml b/tests/auto/qml/qmlcppcodegen/data/parentProp.qml
new file mode 100644
index 0000000000..fdfd96ba73
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/parentProp.qml
@@ -0,0 +1,25 @@
+import QtQuick
+
+Item {
+ implicitWidth: 12
+ property int a: 5
+ property int c: child.b + 2
+ property int i: child.i * 2
+
+ Item {
+ id: child
+ property int b: parent.a + 4
+ property int i: parent.implicitWidth - 1
+ }
+
+ Item {
+ id: sibling
+ implicitWidth: 29
+ }
+
+ Item {
+ id: evil
+ property string a: "599"
+ property string implicitWidth: "444"
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/person.cpp b/tests/auto/qml/qmlcppcodegen/data/person.cpp
new file mode 100644
index 0000000000..7989d3715b
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/person.cpp
@@ -0,0 +1,59 @@
+/******************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt JavaScript to C++ compiler.
+**
+** $QT_BEGIN_LICENSE:COMM$
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#include "person.h"
+
+Person::Person(QObject *parent)
+ : QObject(parent), m_name(u"Bart"_qs), m_shoeSize(0)
+{
+}
+
+QString Person::name() const
+{
+ return m_name;
+}
+
+void Person::setName(const QString &n)
+{
+ if (n != m_name) {
+ m_name = n;
+ emit nameChanged();
+ }
+}
+
+void Person::resetName()
+{
+ setName(u"Bart"_qs);
+}
+
+int Person::shoeSize() const
+{
+ return m_shoeSize;
+}
+
+void Person::setShoeSize(int s)
+{
+ if (s != m_shoeSize) {
+ m_shoeSize = s;
+ emit shoeSizeChanged();
+ }
+}
+
diff --git a/tests/auto/qml/qmlcppcodegen/data/person.h b/tests/auto/qml/qmlcppcodegen/data/person.h
new file mode 100644
index 0000000000..5d208396c8
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/person.h
@@ -0,0 +1,61 @@
+/******************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt JavaScript to C++ compiler.
+**
+** $QT_BEGIN_LICENSE:COMM$
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#ifndef PERSON_H
+#define PERSON_H
+
+#include <QObject>
+#include <QtQml/qqml.h>
+#include <QtQml/qqmlengine.h>
+
+class Person : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged RESET resetName FINAL)
+ Q_PROPERTY(int shoeSize READ shoeSize WRITE setShoeSize NOTIFY shoeSizeChanged FINAL)
+ Q_PROPERTY(int pain READ pain CONSTANT FINAL)
+ QML_ELEMENT
+public:
+ Person(QObject *parent = nullptr);
+ ~Person() { setShoeSize(-25); }
+
+ int pain() const {
+ qmlEngine(this)->throwError(QStringLiteral("ouch"));
+ return 92;
+ }
+
+ QString name() const;
+ void setName(const QString &);
+ void resetName();
+
+ int shoeSize() const;
+ void setShoeSize(int);
+
+signals:
+ void nameChanged();
+ void shoeSizeChanged();
+
+private:
+ QString m_name;
+ int m_shoeSize;
+};
+
+#endif // PERSON_H
diff --git a/tests/auto/qml/qmlcppcodegen/data/pressAndHoldButton.qml b/tests/auto/qml/qmlcppcodegen/data/pressAndHoldButton.qml
new file mode 100644
index 0000000000..9750bb3a1d
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/pressAndHoldButton.qml
@@ -0,0 +1,38 @@
+import QtQuick
+
+Image {
+ id: container
+
+ property int repeatDelay: 300
+ property int repeatDuration: 75
+ property bool pressed: false
+
+ signal clicked
+
+ scale: pressed ? 0.9 : 1
+
+ function press() {
+ autoRepeatClicks.start();
+ }
+
+ function release() {
+ autoRepeatClicks.stop()
+ container.pressed = false
+ }
+
+ ParallelAnimation on pressed {
+ id: autoRepeatClicks
+ running: false
+
+ PropertyAction { target: container; property: "pressed"; value: true }
+ ScriptAction { script: container.clicked() }
+ PauseAnimation { duration: container.repeatDelay }
+
+ ParallelAnimation {
+ loops: Animation.Infinite
+ ScriptAction { script: container.clicked() }
+ PauseAnimation { duration: container.repeatDuration }
+ }
+ }
+}
+
diff --git a/tests/auto/qml/qmlcppcodegen/data/propertyChanges.qml b/tests/auto/qml/qmlcppcodegen/data/propertyChanges.qml
new file mode 100644
index 0000000000..d0cc08f003
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/propertyChanges.qml
@@ -0,0 +1,9 @@
+pragma Strict
+import QtQuick
+
+Item {
+ id: outer
+ PropertyChanges {
+ outer.x: 15 + 12
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/registerelimination.qml b/tests/auto/qml/qmlcppcodegen/data/registerelimination.qml
new file mode 100644
index 0000000000..2ea98fe4ff
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/registerelimination.qml
@@ -0,0 +1,44 @@
+/******************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt JavaScript to C++ compiler.
+**
+** $QT_BEGIN_LICENSE:COMM$
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+import QtQuick
+
+Rectangle {
+ id: page
+ height: 480;
+
+ property int input: 10
+ property int output: ball.y
+
+ onInputChanged: ball.y = input
+
+ Rectangle {
+ id: ball
+
+ onYChanged: {
+ if (y <= 0) {
+ y = page.height - 21;
+ } else if (y >= page.height - 20) {
+ y = 0;
+ }
+ }
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/scopeVsObject.qml b/tests/auto/qml/qmlcppcodegen/data/scopeVsObject.qml
new file mode 100644
index 0000000000..631f8e2991
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/scopeVsObject.qml
@@ -0,0 +1,6 @@
+import QtQml
+
+QtObject {
+ objectName: "foo"
+ Component.onCompleted: objectName = objectName + "bar"
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/script.js b/tests/auto/qml/qmlcppcodegen/data/script.js
new file mode 100644
index 0000000000..fa55f9069e
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/script.js
@@ -0,0 +1,6 @@
+
+.import "library.js" as Library
+
+function getter() {
+ return Library.getter()
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/script.mjs b/tests/auto/qml/qmlcppcodegen/data/script.mjs
new file mode 100644
index 0000000000..459c336125
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/script.mjs
@@ -0,0 +1,4 @@
+
+export function ok() {
+ return true
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/shared/Slider.qml b/tests/auto/qml/qmlcppcodegen/data/shared/Slider.qml
new file mode 100644
index 0000000000..336edade29
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/shared/Slider.qml
@@ -0,0 +1,122 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtQuick module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** BSD License Usage
+** Alternatively, you may use this file under the terms of the BSD license
+** as follows:
+**
+** "Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+** * Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** * Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in
+** the documentation and/or other materials provided with the
+** distribution.
+** * Neither the name of The Qt Company Ltd nor the names of its
+** contributors may be used to endorse or promote products derived
+** from this software without specific prior written permission.
+**
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.12
+
+Item {
+ id: slider
+ height: 26
+ width: 320
+
+ property real min: 0
+ property real max: 1
+ property real value: min + (max - min) * dragHandler.value
+ property real init: min+(max-min)/2
+ property string name: "Slider"
+ property color color: "#0066cc"
+ property real minLabelWidth: 44
+
+ DragHandler {
+ id: dragHandler
+ target: handle
+ xAxis.minimum: Math.round(-handle.width / 2 + 3)
+ xAxis.maximum: Math.round(foo.width - handle.width/2 - 3)
+ property real value: (handle.x - xAxis.minimum) / (xAxis.maximum - xAxis.minimum)
+ }
+
+ Component.onCompleted: ()=> setValue(init)
+ function setValue(v) {
+ if (min < max)
+ handle.x = Math.round( v / (max - min) *
+ (dragHandler.xAxis.maximum - dragHandler.xAxis.minimum)
+ + dragHandler.xAxis.minimum);
+ }
+ Rectangle {
+ id:sliderName
+ anchors.left: parent.left
+ anchors.leftMargin: 16
+ height: childrenRect.height
+ width: Math.max(slider.minLabelWidth, childrenRect.width)
+ anchors.verticalCenter: parent.verticalCenter
+ Item {
+ objectName: slider.name + ":"
+ }
+ }
+
+ Rectangle{
+ id: foo
+ width: parent.width - 8 - sliderName.width
+ color: "#eee"
+ height: 7
+ radius: 3
+ antialiasing: true
+ border.color: Qt.darker(color, 1.2)
+ anchors.left: sliderName.right
+ anchors.right: parent.right
+ anchors.leftMargin: 10
+ anchors.rightMargin: 24
+ anchors.verticalCenter: parent.verticalCenter
+
+ Rectangle {
+ height: parent.height
+ anchors.left: parent.left
+ anchors.right: handle.horizontalCenter
+ color: slider.color
+ radius: 3
+ border.width: 1
+ border.color: Qt.darker(color, 1.3)
+ opacity: 0.8
+ }
+ Image {
+ id: handle
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/shifts.qml b/tests/auto/qml/qmlcppcodegen/data/shifts.qml
new file mode 100644
index 0000000000..009e010b2d
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/shifts.qml
@@ -0,0 +1,9 @@
+import QtQml
+
+QtObject {
+ property int a: (155 >> 2) << (2 << 2);
+ property int b: (a + 1) >> 1
+ property int c: (b - 2) << 2
+ property int d: (a + 3) << (b * 3)
+ property int e: (d - 4) >> (c / 2)
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/signal.qml b/tests/auto/qml/qmlcppcodegen/data/signal.qml
new file mode 100644
index 0000000000..a40020dff5
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/signal.qml
@@ -0,0 +1,6 @@
+import QtQml
+
+QtObject {
+ property int ff: 4
+ onObjectNameChanged: ff = 12
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/signalHandler.qml b/tests/auto/qml/qmlcppcodegen/data/signalHandler.qml
new file mode 100644
index 0000000000..621c218d02
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/signalHandler.qml
@@ -0,0 +1,10 @@
+pragma Strict
+import QtQml
+
+QtObject {
+ signal foo
+ signal bar(baz: string)
+
+ onFoo: ()=> console.log("foo")
+ onBar: (baz)=> console.log(baz)
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/specificParent.qml b/tests/auto/qml/qmlcppcodegen/data/specificParent.qml
new file mode 100644
index 0000000000..6d5e6cdc16
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/specificParent.qml
@@ -0,0 +1,11 @@
+import QtQuick
+
+Rectangle {
+ Item {
+ id: child
+ property real a: parent.radius
+ }
+
+ property real a: child.a
+ radius: 77
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/stringLength.qml b/tests/auto/qml/qmlcppcodegen/data/stringLength.qml
new file mode 100644
index 0000000000..df9bbb7f2d
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/stringLength.qml
@@ -0,0 +1,6 @@
+import QtQml
+
+QtObject {
+ objectName: "astringb"
+ property int stringLength: objectName.length
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/testlogger.js b/tests/auto/qml/qmlcppcodegen/data/testlogger.js
new file mode 100644
index 0000000000..77dded4e7b
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/testlogger.js
@@ -0,0 +1,76 @@
+/******************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt JavaScript to C++ compiler.
+**
+** $QT_BEGIN_LICENSE:COMM$
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+.pragma library
+
+var testResults = null;
+
+function log_init_results()
+{
+ if (!testResults) {
+ testResults = {
+ reportedStart: false,
+ nextId: 0,
+ testCases: []
+ }
+ }
+}
+
+function log_register_test(name)
+{
+ log_init_results()
+ var testId = testResults.nextId++
+ testResults.testCases.push(testId)
+ return testId
+}
+
+function log_optional_test(testId)
+{
+ log_init_results()
+ var index = testResults.testCases.indexOf(testId)
+ if (index >= 0)
+ testResults.testCases.splice(index, 1)
+}
+
+function log_mandatory_test(testId)
+{
+ log_init_results()
+ var index = testResults.testCases.indexOf(testId)
+ if (index == -1)
+ testResults.testCases.push(testId)
+}
+
+function log_start_test()
+{
+ log_init_results()
+ if (testResults.reportedStart)
+ return false
+ testResults.reportedStart = true
+ return true
+}
+
+function log_complete_test(testId)
+{
+ var index = testResults.testCases.indexOf(testId)
+ if (index >= 0)
+ testResults.testCases.splice(index, 1)
+ return testResults.testCases.length > 0
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/text.qml b/tests/auto/qml/qmlcppcodegen/data/text.qml
new file mode 100644
index 0000000000..5a30c7e3f4
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/text.qml
@@ -0,0 +1,20 @@
+import TestTypes
+import QtQml
+
+QtObject {
+ id: parent
+ property int a: 16
+ property date dayz: BirthdayParty.rsvp
+ property QtObject o: QtObject {
+ property date width: parent.dayz
+ }
+
+ property date oParty: o.BirthdayParty.rsvp
+
+ property BirthdayParty party: BirthdayParty {
+ eee: parent.a + 5
+ property int fff: eee + 12
+ }
+
+ property int ggg: party.eee + a
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/undefinedResets.qml b/tests/auto/qml/qmlcppcodegen/data/undefinedResets.qml
new file mode 100644
index 0000000000..fae040a1a5
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/undefinedResets.qml
@@ -0,0 +1,6 @@
+pragma Strict
+import TestTypes
+
+Person {
+ name: shoeSize === 11 ? undefined : "Marge"
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/unknownParameter.qml b/tests/auto/qml/qmlcppcodegen/data/unknownParameter.qml
new file mode 100644
index 0000000000..3b750c2a66
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/unknownParameter.qml
@@ -0,0 +1,7 @@
+pragma Strict
+import QtQml
+import TestTypes
+
+CppBaseClass {
+ Component.onCompleted: doCall(null);
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/unusedAttached.qml b/tests/auto/qml/qmlcppcodegen/data/unusedAttached.qml
new file mode 100644
index 0000000000..5bae1ef8c8
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/unusedAttached.qml
@@ -0,0 +1,5 @@
+import QtQuick
+
+Item {
+ KeyNavigation.priority: KeyNavigation.BeforeItem
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/urlString.qml b/tests/auto/qml/qmlcppcodegen/data/urlString.qml
new file mode 100644
index 0000000000..511c54532c
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/urlString.qml
@@ -0,0 +1,13 @@
+pragma Strict
+import QtQml
+
+QtObject {
+ id: self
+ property url c: "http://aabbcc.com"
+ property url d: "http://ccbbaa.com"
+ property url e: "http:" + "//a112233.de"
+ Component.onCompleted: {
+ c = "http://dddddd.com";
+ self.d = "http://aaaaaa.com";
+ }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/valueTypeProperty.qml b/tests/auto/qml/qmlcppcodegen/data/valueTypeProperty.qml
new file mode 100644
index 0000000000..1d1b74d9bc
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/valueTypeProperty.qml
@@ -0,0 +1,6 @@
+pragma Strict
+import QtQuick
+
+Text {
+ property string foo: font.family
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/voidfunction.qml b/tests/auto/qml/qmlcppcodegen/data/voidfunction.qml
new file mode 100644
index 0000000000..74313f900f
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/voidfunction.qml
@@ -0,0 +1,6 @@
+pragma Strict
+import QtQml
+
+QtObject {
+ function doesNotReturnValue() { objectName = "barbar" }
+}
diff --git a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp
new file mode 100644
index 0000000000..11c70f2c72
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp
@@ -0,0 +1,1623 @@
+/******************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt JavaScript to C++ compiler.
+**
+** $QT_BEGIN_LICENSE:COMM$
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+
+#include <data/birthdayparty.h>
+#include <data/cppbaseclass.h>
+#include <data/objectwithmethod.h>
+
+#include <QtTest>
+#include <QtQml>
+#include <QtGui/qcolor.h>
+
+#if QT_CONFIG(process)
+#include <QtCore/qprocess.h>
+#endif
+
+Q_IMPORT_QML_PLUGIN(TestTypesPlugin)
+
+class tst_QmlCppCodegen : public QObject
+{
+ Q_OBJECT
+private slots:
+ void simpleBinding();
+ void anchorsFill();
+ void signalHandler();
+ void idAccess();
+ void globals();
+ void multiLookup();
+ void enums();
+ void funcWithParams();
+ void intOverflow();
+ void stringLength();
+ void scopeVsObject();
+ void compositeTypeMethod();
+ void excessiveParameters();
+ void jsImport();
+ void jsmoduleImport();
+ void runInterpreted();
+ void methods();
+ void math();
+ void unknownParameter();
+ void array();
+ void equalsUndefined();
+ void conversions();
+ void interestingFiles_data();
+ void interestingFiles();
+ void extendedTypes();
+ void construct();
+ void contextParam();
+ void attachedType();
+ void componentReturnType();
+ void onAssignment();
+ void failures();
+ void enumScope();
+ void unusedAttached();
+ void attachedBaseEnum();
+ void nullAccess();
+ void interceptor();
+ void nonNotifyable();
+ void importsFromImportPath();
+ void aliasLookup();
+ void outOfBoundsArray();
+ void compositeSingleton();
+ void lotsOfRegisters();
+ void inPlaceDecrement();
+ void shifts();
+ void valueTypeProperty();
+ void propertyOfParent();
+ void accessModelMethodFromOutSide();
+ void functionArguments();
+ void bindingExpression();
+ void voidFunction();
+ void overriddenProperty();
+ void listLength();
+ void parentProperty();
+ void registerElimination();
+ void asCast();
+ void noQQmlData();
+ void scopeObjectDestruction();
+ void colorAsVariant();
+ void bindToValueType();
+ void undefinedResets();
+ void innerObjectNonShadowable();
+ void ownPropertiesNonShadowable();
+ void modulePrefix();
+ void colorString();
+ void urlString();
+ void callContextPropertyLookupResult();
+ void deadShoeSize();
+ void listIndices();
+ void jsMathObject();
+ void intEnumCompare();
+};
+
+void tst_QmlCppCodegen::simpleBinding()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/Test.qml"_qs));
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY2(!object.isNull(), component.errorString().toUtf8().constData());
+ QCOMPARE(object->property("foo").toInt(), int(3));
+
+ {
+ CppBaseClass *base = qobject_cast<CppBaseClass *>(object.data());
+ Q_ASSERT(base);
+ QVERIFY(!base->cppProp.hasBinding());
+ QCOMPARE(base->cppProp.value(), 7);
+ QVERIFY(base->cppProp2.hasBinding());
+ QCOMPARE(base->cppProp2.value(), 14);
+ base->cppProp.setValue(9);
+ QCOMPARE(base->cppProp.value(), 9);
+ QCOMPARE(base->cppProp2.value(), 18);
+ }
+}
+
+void tst_QmlCppCodegen::anchorsFill()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/anchorsFill.qml"_qs));
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY2(!object.isNull(), component.errorString().toUtf8().constData());
+
+ QCOMPARE(object->property("width").toInt(), 234);
+ QCOMPARE(object->children().length(), 2);
+
+ QObject *child = object->children().front();
+ QVERIFY(child);
+ QCOMPARE(child->property("width").toInt(), 234);
+
+ QObject *newParent = object->children().back();
+ QVERIFY(newParent);
+ QCOMPARE(newParent->property("width").toInt(), 47);
+
+ child->setProperty("parent", QVariant::fromValue(newParent));
+ QCOMPARE(child->property("width").toInt(), 47);
+}
+
+void tst_QmlCppCodegen::signalHandler()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/signal.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+ QCOMPARE(object->objectName(), QString());
+ QCOMPARE(object->property("ff").toInt(), 4);
+
+ object->setObjectName(u"foo"_qs);
+ QCOMPARE(object->property("ff").toInt(), 12);
+}
+
+void tst_QmlCppCodegen::idAccess()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/idAccess.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+
+ QVERIFY(object->property("y").toInt() != 48);
+ QCOMPARE(object->property("y").toInt(), 12);
+ object->setProperty("z", 13);
+ QCOMPARE(object->property("y").toInt(), 13);
+ object->setProperty("x", QVariant::fromValue(333));
+ QCOMPARE(object->property("y").toInt(), 48);
+
+ // The binding was broken by setting the property
+ object->setProperty("z", 14);
+ QCOMPARE(object->property("y").toInt(), 48);
+
+ QObject *ttt = qmlContext(object.data())->objectForName(u"ttt"_qs);
+ QFont f = qvariant_cast<QFont>(ttt->property("font"));
+ QCOMPARE(f.pointSize(), 22);
+}
+
+static QByteArray arg1()
+{
+ const QStringList args = QCoreApplication::instance()->arguments();
+ return args.length() > 1 ? args[1].toUtf8() : QByteArray("undefined");
+}
+
+void tst_QmlCppCodegen::globals()
+{
+ QQmlEngine engine;
+ int exitCode = -1;
+ QObject::connect(&engine, &QQmlEngine::exit, [&](int code) { exitCode = code; });
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/globals.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+
+ const QByteArray message = QByteArray("Start 2 ") + arg1();
+ QTest::ignoreMessage(QtDebugMsg, message.constData());
+
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+ QTRY_COMPARE(exitCode, 0);
+
+ QObject *application = qvariant_cast<QObject *>(object->property("application"));
+ QVERIFY(application);
+ QCOMPARE(QString::fromUtf8(application->metaObject()->className()),
+ u"QQuickApplication"_qs);
+
+ QTest::ignoreMessage(QtDebugMsg, "End");
+ QMetaObject::invokeMethod(application, "aboutToQuit");
+
+ const QVariant somewhere = object->property("somewhere");
+ QCOMPARE(somewhere.userType(), QMetaType::QUrl);
+ QCOMPARE(qvariant_cast<QUrl>(somewhere).toString(), u"qrc:/somewhere/else.qml"_qs);
+
+ const QVariant somewhereString = object->property("somewhereString");
+ QCOMPARE(somewhereString.userType(), QMetaType::QString);
+ QCOMPARE(somewhereString.toString(), u"qrc:/somewhere/else.qml"_qs);
+
+ const QVariant plain = object->property("plain");
+ QCOMPARE(plain.userType(), QMetaType::QUrl);
+ QCOMPARE(qvariant_cast<QUrl>(plain).toString(), u"/not/here.qml"_qs);
+}
+
+void tst_QmlCppCodegen::multiLookup()
+{
+ // Multiple lookups of singletons (Qt in this case) don't clash with one another.
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/immediateQuit.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+
+ const QByteArray message = QByteArray("End: ") + arg1();
+ QTest::ignoreMessage(QtDebugMsg, message.constData());
+
+ QSignalSpy quitSpy(&engine, &QQmlEngine::quit);
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+ QCOMPARE(quitSpy.count(), 1);
+}
+
+void tst_QmlCppCodegen::enums()
+{
+ QQmlEngine engine;
+ {
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/Enums.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+
+ QTest::ignoreMessage(QtWarningMsg, "qrc:/TestTypes/Enums.qml:4:1: "
+ "QML Enums: Layout must be attached to Item elements");
+ QScopedPointer<QObject> object(component.create());
+
+ QVERIFY(!object.isNull());
+ bool ok = false;
+ QCOMPARE(object->property("appState").toInt(&ok), 2);
+ QVERIFY(ok);
+ QCOMPARE(object->property("color").toString(), u"blue"_qs);
+
+ QTRY_COMPARE(object->property("appState").toInt(&ok), 1);
+ QVERIFY(ok);
+ QCOMPARE(object->property("color").toString(), u"green"_qs);
+
+ const auto func = qmlAttachedPropertiesFunction(
+ object.data(), QMetaType::fromName("QQuickLayout*").metaObject());
+
+ QTest::ignoreMessage(QtWarningMsg, "qrc:/TestTypes/enumsInOtherObject.qml:4:25: "
+ "QML Enums: Layout must be attached to Item elements");
+ QObject *attached = qmlAttachedPropertiesObject(object.data(), func);
+
+ const QVariant prop = attached->property("alignment");
+ QVERIFY(prop.isValid());
+ QCOMPARE(qvariant_cast<Qt::Alignment>(prop), Qt::AlignCenter);
+ }
+ {
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/enumsInOtherObject.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+ QCOMPARE(object->property("color").toString(), u"blue"_qs);
+ QTRY_COMPARE(object->property("color").toString(), u"green"_qs);
+ }
+}
+
+void tst_QmlCppCodegen::funcWithParams()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/funcWithParams.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+ QCOMPARE(object->property("bar").toInt(), 30);
+}
+
+void tst_QmlCppCodegen::intOverflow()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/intOverflow.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+ QCOMPARE(object->property("a").toDouble(), 1.09951162778e+12);
+ QCOMPARE(object->property("b").toInt(), 5);
+}
+
+void tst_QmlCppCodegen::stringLength()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/stringLength.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+ QCOMPARE(object->property("stringLength").toInt(), 8);
+}
+
+void tst_QmlCppCodegen::scopeVsObject()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/scopeVsObject.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+ QCOMPARE(object->property("objectName").toString(), u"foobar"_qs);
+}
+
+void tst_QmlCppCodegen::compositeTypeMethod()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/compositeTypeMethod.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+ QSignalSpy spy(object.data(), SIGNAL(foo()));
+ QTRY_VERIFY(spy.count() > 0);
+}
+
+void tst_QmlCppCodegen::excessiveParameters()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/excessiveParameters.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+ QSignalSpy spy(object.data(), SIGNAL(foo()));
+ QTRY_VERIFY(spy.count() > 0);
+}
+
+void tst_QmlCppCodegen::jsImport()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/jsimport.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+ QCOMPARE(object->property("value").toInt(), 42);
+}
+
+void tst_QmlCppCodegen::jsmoduleImport()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/jsmoduleimport.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+ QCOMPARE(object->property("ok").toBool(), true);
+ QVariant okFunc = object->property("okFunc");
+ QCOMPARE(okFunc.metaType(), QMetaType::fromType<QJSValue>());
+ QJSValue val = engine.toScriptValue(okFunc);
+ QJSValue result = val.call();
+ QVERIFY(result.isBool());
+ QVERIFY(result.toBool());
+}
+
+void tst_QmlCppCodegen::methods()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/methods.qml"_qs));
+ QVERIFY(component.isReady());
+
+ QTest::ignoreMessage(QtDebugMsg, "The Bar");
+ QTest::ignoreMessage(QtWarningMsg, QRegularExpression(u"TypeError: .* is not a function"_qs));
+ QScopedPointer<QObject> obj(component.create());
+ QVERIFY(obj);
+ BirthdayParty *party(qobject_cast<BirthdayParty *>(obj.data()));
+
+ QVERIFY(party && party->host());
+ QCOMPARE(party->guestCount(), 5);
+
+ bool foundGreen = false;
+ bool foundFoo = false;
+ for (int ii = 0; ii < party->guestCount(); ++ii) {
+ if (party->guest(ii)->name() == u"William Green"_qs)
+ foundGreen = true;
+ if (party->guest(ii)->name() == u"The Foo"_qs)
+ foundFoo = true;
+ }
+
+ QVERIFY(foundGreen);
+ QVERIFY(foundFoo);
+
+ QCOMPARE(obj->property("n1").toString(), u"onGurk"_qs);
+ QCOMPARE(obj->property("n2").toString(), u"onSemmeln"_qs);
+ QCOMPARE(obj->property("n3"), QVariant());
+
+ {
+ QVariant ret;
+ obj->metaObject()->invokeMethod(obj.data(), "retrieveVar", Q_RETURN_ARG(QVariant, ret));
+ QCOMPARE(ret.typeId(), QMetaType::QString);
+ QCOMPARE(ret.toString(), u"Jack Smith"_qs);
+ }
+
+ {
+ QString ret;
+ obj->metaObject()->invokeMethod(obj.data(), "retrieveString", Q_RETURN_ARG(QString, ret));
+ QCOMPARE(ret, u"Jack Smith"_qs);
+ }
+
+ QCOMPARE(party->host()->shoeSize(), 12);
+ obj->metaObject()->invokeMethod(obj.data(), "storeElement");
+ QCOMPARE(party->host()->shoeSize(), 13);
+ QJSManagedValue v = engine.toManagedValue(obj->property("dresses"));
+ QVERIFY(v.isArray());
+
+ QJSManagedValue inner(v.property(2), &engine);
+ QVERIFY(inner.isArray());
+ QCOMPARE(inner.property(0).toInt(), 1);
+ QCOMPARE(inner.property(1).toInt(), 2);
+ QCOMPARE(inner.property(2).toInt(), 3);
+
+ QCOMPARE(obj->property("enumValue").toInt(), 2);
+}
+
+void tst_QmlCppCodegen::math()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/math.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+ QCOMPARE(object->property("a").toInt(), 9);
+ QCOMPARE(object->property("b").toDouble(), 50.0 / 22.0);
+}
+
+void tst_QmlCppCodegen::unknownParameter()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/unknownParameter.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+ QCOMPARE(object->property("cppProp").toInt(), 18);
+}
+
+void tst_QmlCppCodegen::array()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/array.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+ const QJSValue value1 = object->property("values1").value<QJSValue>();
+ QVERIFY(value1.isArray());
+ QCOMPARE(value1.property(u"length"_qs).toInt(), 3);
+ QCOMPARE(value1.property(0).toInt(), 1);
+ QCOMPARE(value1.property(1).toInt(), 2);
+ QCOMPARE(value1.property(2).toInt(), 3);
+
+ const QJSValue value2 = object->property("values2").value<QJSValue>();
+ QVERIFY(value2.isArray());
+ QCOMPARE(value2.property(u"length"_qs).toInt(), 0);
+}
+
+void tst_QmlCppCodegen::equalsUndefined()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/equalsUndefined.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+
+ QCOMPARE(object->property("a").toInt(), 50);
+ QCOMPARE(object->property("b").toInt(), 5000);
+}
+
+void tst_QmlCppCodegen::conversions()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/conversions.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+
+ QTest::ignoreMessage(QtWarningMsg, "qrc:/TestTypes/conversions.qml:42: TypeError: Type error");
+
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+
+ QVERIFY(object->property("nullIsNull").toBool());
+ QVERIFY(!object->property("intIsNull").toBool());
+ QVERIFY(!object->property("zeroIsNull").toBool());
+ QVERIFY(!object->property("arrayIsNull").toBool());
+ QVERIFY(!object->property("objectIsNull").toBool());
+ QVERIFY(!object->property("nullIsNotNull").toBool());
+ QVERIFY(object->property("intIsNotNull").toBool());
+ QVERIFY(object->property("zeroIsNotNull").toBool());
+ QVERIFY(object->property("arrayIsNotNull").toBool());
+ QVERIFY(object->property("objectIsNotNull").toBool());
+ QVERIFY(!object->property("neNull").toBool());
+ QVERIFY(object->property("eqNull").toBool());
+
+ QVERIFY(!object->property("boolEqualsBool").toBool());
+ QVERIFY(object->property("boolNotEqualsBool").toBool());
+
+ QCOMPARE(object->property("cmpEqInt").toInt(), 17);
+
+ QCOMPARE(object->property("undefinedType").toString(), u"undefined"_qs);
+ QCOMPARE(object->property("booleanType").toString(), u"boolean"_qs);
+ QCOMPARE(object->property("numberType").toString(), u"number"_qs);
+ QCOMPARE(object->property("stringType").toString(), u"string"_qs);
+ QCOMPARE(object->property("objectType").toString(), u"object"_qs);
+ QCOMPARE(object->property("symbolType").toString(), u"symbol"_qs);
+
+ QJSManagedValue obj = engine.toManagedValue(object->property("anObject"));
+ QCOMPARE(obj.property(u"a"_qs).toInt(), 12);
+ QCOMPARE(obj.property(u"b"_qs).toInt(), 14);
+ QCOMPARE(obj.property(u"c"_qs).toString(), u"somestring"_qs);
+
+ QVERIFY(object->property("aInObject").toBool());
+ QVERIFY(object->property("lengthInArray").toBool());
+ QVERIFY(!object->property("fooInNumber").isValid());
+ QVERIFY(!object->property("numberInObject").toBool());
+ QVERIFY(object->property("numberInArray").toBool());
+
+ QCOMPARE(object->property("varPlusVar").toDouble(), 2.0);
+ QVERIFY(qIsNaN(object->property("varMinusVar").toDouble()));
+ QVERIFY(qIsNaN(object->property("varTimesVar").toDouble()));
+ QCOMPARE(object->property("varDivVar").toDouble(), 0.0);
+
+ const QVariant stringPlusString = object->property("stringPlusString");
+ QCOMPARE(stringPlusString.typeId(), QMetaType::QString);
+ QCOMPARE(stringPlusString.toString(), u"1220"_qs);
+
+ const QVariant stringMinusString = object->property("stringMinusString");
+ QCOMPARE(stringMinusString.typeId(), QMetaType::Double);
+ QCOMPARE(stringMinusString.toDouble(), -8.0);
+
+ const QVariant stringTimesString = object->property("stringTimesString");
+ QCOMPARE(stringTimesString.typeId(), QMetaType::Double);
+ QCOMPARE(stringTimesString.toDouble(), 240.0);
+
+ const QVariant stringDivString = object->property("stringDivString");
+ QCOMPARE(stringDivString.typeId(), QMetaType::Double);
+ QCOMPARE(stringDivString.toDouble(), 0.6);
+
+ QCOMPARE(object->property("uglyString").toString(),
+ u"with\nnewlinewith\"quotwith\\slashes"_qs);
+
+ QCOMPARE(qvariant_cast<QObject *>(object->property("nullObject1")), nullptr);
+ QCOMPARE(qvariant_cast<QObject *>(object->property("nullObject2")), object.data());
+
+ QCOMPARE(object->property("passObjectLiteral").toInt(), 11);
+ QCOMPARE(object->property("passArrayLiteral").toInt(), 4);
+
+ QCOMPARE(object->property("doneStuff").toInt(), 19);
+
+ QVariantList modulos = object->property("modulos").toList();
+ QCOMPARE(modulos.length(), 7);
+
+ QCOMPARE(modulos[0].userType(), QMetaType::Double);
+ QCOMPARE(modulos[0].toDouble(), 0.0);
+
+ QCOMPARE(modulos[1].userType(), QMetaType::Int);
+ QCOMPARE(modulos[1].toInt(), 0);
+
+ QCOMPARE(modulos[2].userType(), QMetaType::Double);
+ QCOMPARE(modulos[2].toDouble(), 2.4);
+
+ QCOMPARE(modulos[3].userType(), QMetaType::Double);
+ QCOMPARE(modulos[3].toDouble(), -0.4);
+
+ QCOMPARE(modulos[4].userType(), QMetaType::Double);
+ QCOMPARE(modulos[4].toDouble(), 0.0);
+
+ QCOMPARE(modulos[5].userType(), QMetaType::Double);
+ QVERIFY(qIsNaN(modulos[5].toDouble()));
+
+ QCOMPARE(modulos[6].userType(), QMetaType::Double);
+ QVERIFY(qIsNaN(modulos[6].toDouble()));
+
+ QVariantList unaryOps = object->property("unaryOps").toList();
+ QCOMPARE(unaryOps.length(), 6);
+
+ QCOMPARE(unaryOps[0].userType(), QMetaType::Double);
+ QCOMPARE(unaryOps[0].toDouble(), 1221);
+
+ QCOMPARE(unaryOps[1].userType(), QMetaType::Double);
+ QCOMPARE(unaryOps[1].toDouble(), 1219);
+
+ QCOMPARE(unaryOps[2].userType(), QMetaType::Double);
+ QCOMPARE(unaryOps[2].toDouble(), 1220);
+
+ QCOMPARE(unaryOps[3].userType(), QMetaType::Double);
+ QCOMPARE(unaryOps[3].toDouble(), -1220);
+
+ QCOMPARE(unaryOps[4].userType(), QMetaType::Double);
+ QCOMPARE(unaryOps[4].toDouble(), 1220);
+
+ QCOMPARE(unaryOps[5].userType(), QMetaType::Double);
+ QCOMPARE(unaryOps[5].toDouble(), 1220);
+
+ QVariant undef;
+ QVERIFY(object->metaObject()->invokeMethod(object.data(), "retUndefined",
+ Q_RETURN_ARG(QVariant, undef)));
+ QVERIFY(!undef.isValid());
+}
+
+void tst_QmlCppCodegen::interestingFiles_data()
+{
+ QTest::addColumn<QString>("file");
+ QTest::addRow("conversions2") << u"conversions2.qml"_qs;
+ QTest::addRow("TestCase") << u"TestCase.qml"_qs;
+ QTest::addRow("layouts") << u"layouts.qml"_qs;
+ QTest::addRow("interactive") << u"interactive.qml"_qs;
+ QTest::addRow("Panel") << u"Panel.qml"_qs;
+ QTest::addRow("ProgressBar") << u"ProgressBar/ProgressBar.ui.qml"_qs;
+ QTest::addRow("Root") << u"ProgressBar/Root.qml"_qs;
+ QTest::addRow("noscope") << u"noscope.qml"_qs;
+ QTest::addRow("dynamicscene") << u"dynamicscene.qml"_qs;
+ QTest::addRow("curlygrouped") << u"curlygrouped.qml"_qs;
+}
+
+void tst_QmlCppCodegen::interestingFiles()
+{
+ QFETCH(QString, file);
+
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/%1"_qs.arg(file)));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+}
+
+void tst_QmlCppCodegen::extendedTypes()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/extendedTypes.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+
+ QTest::ignoreMessage(QtDebugMsg, "6 QSizeF(10, 20) 30");
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+
+ QCOMPARE(object->property("a").toInt(), 6);
+ QCOMPARE(qvariant_cast<QSizeF>(object->property("b")), QSizeF(10, 20));
+ QCOMPARE(object->property("c").toInt(), 30);
+ QCOMPARE(object->property("d").toString(), u"QSizeF(10, 20)"_qs);
+
+ QCOMPARE(object->property("e").toInt(), 2);
+}
+
+void tst_QmlCppCodegen::construct()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/construct.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+
+ const QJSManagedValue v = engine.toManagedValue(object->property("foo"));
+ QVERIFY(v.isError());
+ QCOMPARE(v.toString(), u"Error: bar"_qs);
+
+ QCOMPARE(object->property("aaa").toInt(), 12);
+ QTest::ignoreMessage(QtWarningMsg, "qrc:/TestTypes/construct.qml:9: Error: ouch");
+ object->metaObject()->invokeMethod(object.data(), "ouch");
+ QCOMPARE(object->property("aaa").toInt(), 13);
+}
+
+void tst_QmlCppCodegen::contextParam()
+{
+ // The compiler cannot resolve context parameters.
+ // Make sure the binding is interpreted.
+
+ QQmlEngine engine;
+
+ QVariantMap m;
+ m.insert(u"foo"_qs, 10);
+ engine.rootContext()->setContextProperty(u"contextParam"_qs, m);
+
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/contextParam.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+
+ QCOMPARE(object->property("foo").toInt(), 10);
+}
+
+void tst_QmlCppCodegen::attachedType()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/text.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+ QCOMPARE(object->property("dayz").toDateTime(), QDateTime(QDate(1911, 3, 4), QTime()));
+ QCOMPARE(object->property("oParty").toDateTime(), QDateTime(QDate(1911, 3, 4), QTime()));
+
+ QObject *party = qvariant_cast<QObject *>(object->property("party"));
+ QVERIFY(party);
+ QCOMPARE(party->property("eee").toInt(), 21);
+ QCOMPARE(party->property("fff").toInt(), 33);
+ QCOMPARE(object->property("ggg").toInt(), 37);
+}
+
+void tst_QmlCppCodegen::componentReturnType()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/componentReturnType.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+
+ QCOMPARE(object->property("count").toInt(), 10);
+ QCOMPARE(QQmlListReference(object.data(), "children", &engine).count(), 11);
+}
+
+void tst_QmlCppCodegen::onAssignment()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/pressAndHoldButton.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+
+ QCOMPARE(object->property("pressed").toBool(), false);
+ QCOMPARE(object->property("scale").toDouble(), 1.0);
+
+ object->metaObject()->invokeMethod(object.data(), "press");
+ QTRY_COMPARE(object->property("pressed").toBool(), true);
+ QCOMPARE(object->property("scale").toDouble(), 0.9);
+
+ object->metaObject()->invokeMethod(object.data(), "release");
+ QCOMPARE(object->property("pressed").toBool(), false);
+ QCOMPARE(object->property("scale").toDouble(), 1.0);
+}
+
+namespace QmlCacheGeneratedCode {
+namespace _0x5f_TestTypes_failures_qml {
+extern const QQmlPrivate::AOTCompiledFunction aotBuiltFunctions[];
+}
+}
+
+void tst_QmlCppCodegen::failures()
+{
+ const auto &aotFailure
+ = QmlCacheGeneratedCode::_0x5f_TestTypes_failures_qml::aotBuiltFunctions[0];
+ QVERIFY(aotFailure.argumentTypes.isEmpty());
+ QVERIFY(!aotFailure.functionPtr);
+ QCOMPARE(aotFailure.index, 0);
+}
+
+void tst_QmlCppCodegen::enumScope()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/enumScope.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QCOMPARE(object->property("flow").toInt(), 1);
+}
+
+void tst_QmlCppCodegen::unusedAttached()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/unusedAttached.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+
+ const auto func = qmlAttachedPropertiesFunction(
+ object.data(), QMetaType::fromName("QQuickKeyNavigationAttached*").metaObject());
+ QObject *attached = qmlAttachedPropertiesObject(object.data(), func);
+ const QVariant prop = attached->property("priority");
+ QVERIFY(prop.isValid());
+ QCOMPARE(QByteArray(prop.metaType().name()), "QQuickKeyNavigationAttached::Priority");
+ bool ok = false;
+ QCOMPARE(prop.toInt(&ok), 0);
+ QVERIFY(ok);
+}
+
+void tst_QmlCppCodegen::attachedBaseEnum()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/attachedBaseEnum.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+
+ QObject *drag = qvariant_cast<QObject *>(object->property("drag"));
+ QVERIFY(drag);
+
+ // Drag.YAxis is 2, but we cannot #include it here.
+ bool ok = false;
+ QCOMPARE(drag->property("axis").toInt(&ok), 2);
+ QVERIFY(ok);
+}
+
+void tst_QmlCppCodegen::nullAccess()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/nullAccess.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+
+ QTest::ignoreMessage(QtWarningMsg,
+ "qrc:/TestTypes/nullAccess.qml:4:5: TypeError: "
+ "Cannot read property 'width' of null");
+ QTest::ignoreMessage(QtWarningMsg,
+ "qrc:/TestTypes/nullAccess.qml:5:5: TypeError: "
+ "Cannot read property 'height' of null");
+ QTest::ignoreMessage(QtWarningMsg,
+ "qrc:/TestTypes/nullAccess.qml:6: TypeError: Value is null and "
+ "could not be converted to an object");
+ QScopedPointer<QObject> object(component.create());
+
+ QCOMPARE(object->property("width").toDouble(), 0.0);
+ QCOMPARE(object->property("height").toDouble(), 0.0);
+}
+
+void tst_QmlCppCodegen::interceptor()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/interceptor.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+
+ QCOMPARE(object->property("x").toInt(), 100);
+ QCOMPARE(object->property("y").toInt(), 100);
+
+ QVERIFY(object->property("width").toInt() != 200);
+ QVERIFY(object->property("height").toInt() != 200);
+ QVERIFY(object->property("qProperty1").toInt() != 300);
+ QVERIFY(object->property("qProperty2").toInt() != 300);
+ QTRY_COMPARE(object->property("width").toInt(), 200);
+ QTRY_COMPARE(object->property("height").toInt(), 200);
+ QTRY_COMPARE(object->property("qProperty1").toInt(), 300);
+ QTRY_COMPARE(object->property("qProperty2").toInt(), 300);
+}
+
+void tst_QmlCppCodegen::nonNotifyable()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/nonNotifyable.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+
+ QCOMPARE(qvariant_cast<QDateTime>(object->property("dayz")),
+ QDateTime(QDate(2121, 1, 12), QTime()));
+ QCOMPARE(qvariant_cast<QDateTime>(object->property("oParty")),
+ QDateTime(QDate(2111, 12, 11), QTime()));
+}
+
+void tst_QmlCppCodegen::importsFromImportPath()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/importsFromImportPath.qml"_qs));
+
+ // We might propagate the import path, eventually, but for now instantiating is not important.
+ // If the compiler accepts the file, it's probably fine.
+ QVERIFY(component.isError());
+ QCOMPARE(component.errorString(),
+ u"qrc:/TestTypes/importsFromImportPath.qml:1 module \"Module\" is not installed\n"_qs);
+}
+
+void tst_QmlCppCodegen::aliasLookup()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/aliasLookup.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+
+ const QVariant t = object->property("t");
+ QCOMPARE(t.metaType(), QMetaType::fromType<QString>());
+ QCOMPARE(t.toString(), u"12"_qs);
+}
+
+void tst_QmlCppCodegen::outOfBoundsArray()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/outOfBounds.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+
+ QTest::ignoreMessage(QtDebugMsg, "oob undefined");
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+ QVERIFY(object->metaObject()->indexOfProperty("oob") > 0);
+ QVERIFY(!object->property("oob").isValid());
+}
+
+void tst_QmlCppCodegen::compositeSingleton()
+{
+ QQmlEngine engine;
+ engine.addImportPath(u":/TestTypes/imports/"_qs);
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/compositesingleton.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> o(component.create());
+ QCOMPARE(o->property("x").toDouble(), 4.5);
+ QCOMPARE(o->property("y").toDouble(), 10.0);
+ QCOMPARE(o->property("smooth").toBool(), true);
+}
+
+void tst_QmlCppCodegen::lotsOfRegisters()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/page.qml"_qs));
+ QVERIFY2(!component.isError(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+
+ const auto compare = [&]() {
+ const qreal implicitBackgroundWidth = object->property("implicitBackgroundWidth").toDouble();
+ const qreal leftInset = object->property("leftInset").toDouble();
+ const qreal rightInset = object->property("rightInset").toDouble();
+ const qreal contentWidth = object->property("contentWidth").toDouble();
+ const qreal leftPadding = object->property("leftPadding").toDouble();
+ const qreal rightPadding = object->property("rightPadding").toDouble();
+ const qreal implicitFooterWidth = object->property("implicitFooterWidth").toDouble();
+ const qreal implicitHeaderWidth = object->property("implicitHeaderWidth").toDouble();
+
+ const qreal implicitWidth = object->property("implicitWidth").toDouble();
+ QCOMPARE(implicitWidth, qMax(qMax(implicitBackgroundWidth + leftInset + rightInset,
+ contentWidth + leftPadding + rightPadding),
+ qMax(implicitHeaderWidth, implicitFooterWidth)));
+ };
+
+ compare();
+
+ const QList<const char *> props = {
+ "leftInset", "rightInset", "contentWidth", "leftPadding", "rightPadding"
+ };
+
+ for (int i = 0; i < 100; ++i) {
+ QVERIFY(object->setProperty(props[i % props.length()], (i * 17) % 512));
+ compare();
+ }
+}
+
+void tst_QmlCppCodegen::inPlaceDecrement()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/dialog.qml"_qs));
+ QVERIFY2(component.isReady(), qPrintable(component.errorString()));
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+ QObject *header = qvariant_cast<QObject *>(object->property("header"));
+ QVERIFY(header);
+ QObject *background = qvariant_cast<QObject *>(header->property("background"));
+ QObject *parent = qvariant_cast<QObject *>(background->property("parent"));
+
+ QCOMPARE(background->property("width").toInt(), parent->property("width").toInt() + 1);
+ QCOMPARE(background->property("height").toInt(), parent->property("height").toInt() - 1);
+
+ QVERIFY(object->setProperty("width", QVariant::fromValue(17)));
+ QVERIFY(parent->property("width").toInt() > 0);
+ QVERIFY(object->setProperty("height", QVariant::fromValue(53)));
+ QVERIFY(parent->property("height").toInt() > 0);
+
+ QCOMPARE(background->property("width").toInt(), parent->property("width").toInt() + 1);
+ QCOMPARE(background->property("height").toInt(), parent->property("height").toInt() - 1);
+
+ QCOMPARE(object->property("a").toInt(), 1024);
+}
+
+void tst_QmlCppCodegen::shifts()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/shifts.qml"_qs));
+ QVERIFY2(component.isReady(), qPrintable(component.errorString()));
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+
+ QCOMPARE(object->property("a").toInt(), 9728);
+ QCOMPARE(object->property("b").toInt(), 4864);
+ QCOMPARE(object->property("c").toInt(), 19448);
+ QCOMPARE(object->property("d").toInt(), 9731);
+ QCOMPARE(object->property("e").toInt(), 0);
+}
+
+void tst_QmlCppCodegen::valueTypeProperty()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/valueTypeProperty.qml"_qs));
+ QVERIFY2(component.isReady(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+
+ QFont font = qvariant_cast<QFont>(object->property("font"));
+ QCOMPARE(object->property("foo").toString(), font.family());
+ font.setFamily(u"Bar"_qs);
+ object->setProperty("font", QVariant::fromValue(font));
+ QCOMPARE(object->property("foo").toString(), u"Bar"_qs);
+}
+
+void tst_QmlCppCodegen::propertyOfParent()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/RootWithoutId.qml"_qs));
+ QVERIFY2(component.isReady(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+
+ QObject *child = qmlContext(object.data())->objectForName(u"item"_qs);
+
+ bool expected = false;
+
+ for (int i = 0; i < 3; ++i) {
+ const QVariant foo = object->property("foo");
+ QCOMPARE(foo.metaType(), QMetaType::fromType<bool>());
+ QCOMPARE(foo.toBool(), expected);
+
+ const QVariant bar = object->property("bar");
+ QCOMPARE(bar.metaType(), QMetaType::fromType<bool>());
+ QCOMPARE(bar.toBool(), expected);
+
+ const QVariant visible = child->property("visible");
+ QCOMPARE(visible.metaType(), QMetaType::fromType<bool>());
+ QCOMPARE(visible.toBool(), expected);
+
+ expected = !expected;
+ object->setProperty("foo", expected);
+ }
+}
+
+void tst_QmlCppCodegen::accessModelMethodFromOutSide()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/AccessModelMethodsFromOutside.qml"_qs));
+ QVERIFY2(component.isReady(), component.errorString().toUtf8());
+
+ QTest::ignoreMessage(QtDebugMsg, "3");
+ QTest::ignoreMessage(QtDebugMsg, "Apple");
+ QScopedPointer<QObject> object(component.create());
+
+ QCOMPARE(object->property("cost1").toDouble(), 3);
+ QCOMPARE(object->property("name1").toString(), u"Orange"_qs);
+ QCOMPARE(object->property("cost2").toDouble(), 1.95);
+ QCOMPARE(object->property("name2").toString(), u"Banana"_qs);
+}
+
+void tst_QmlCppCodegen::functionArguments()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/Dummy.qml"_qs));
+ QVERIFY2(component.isReady(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+
+ const QMetaObject *metaObject = object->metaObject();
+
+ int result;
+ int a = 1;
+ bool b = false;
+ QObject *c = nullptr;
+ double d = -1.2;
+ int e = 3;
+
+ const QByteArray className = QByteArray(metaObject->className()) + '*';
+ metaObject->invokeMethod(
+ object.data(), "someFunction", Q_RETURN_ARG(int, result),
+ Q_ARG(int, a), Q_ARG(bool, b), QGenericArgument(className, &c),
+ Q_ARG(double, d), Q_ARG(int, e));
+ QCOMPARE(result, 42);
+
+ QString astr = u"foo"_qs;
+ QString bstr = u"bar"_qs;
+ QString concatenated;
+ metaObject->invokeMethod(
+ object.data(), "concat", Q_RETURN_ARG(QString, concatenated),
+ Q_ARG(QString, astr), Q_ARG(QString, bstr));
+ QCOMPARE(concatenated, u"foobar"_qs);
+}
+
+void tst_QmlCppCodegen::bindingExpression()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/BindingExpression.qml"_qs));
+ QVERIFY2(component.isReady(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+
+ QObject *child = qmlContext(object.data())->objectForName(u"child"_qs);
+
+ double width = 200;
+ double y = 10;
+ for (int i = 0; i < 10; ++i) {
+ QCOMPARE(object->property("width").toDouble(), width);
+ QCOMPARE(object->property("height").toDouble(), width);
+ QCOMPARE(object->property("y").toDouble(), y);
+
+ const double childY = y + (width - 100) / 2;
+ QCOMPARE(child->property("y").toDouble(), childY);
+ QCOMPARE(object->property("mass"), childY > 100 ? u"heavy"_qs : u"light"_qs);
+ QCOMPARE(object->property("test_division").toDouble(), width / 1000 + 50);
+ QCOMPARE(object->property("test_ternary").toDouble(), 2.2);
+
+ const int test_switch = object->property("test_switch").toInt();
+ switch (int(width) % 3) {
+ case 0:
+ QCOMPARE(test_switch, 130);
+ break;
+ case 1:
+ QCOMPARE(test_switch, 380);
+ break;
+ case 2:
+ QCOMPARE(test_switch, 630);
+ break;
+ }
+
+ width = 200 * i;
+ y = 10 + i;
+ object->setProperty("width", width);
+ object->setProperty("y", y);
+ }
+}
+
+void tst_QmlCppCodegen::voidFunction()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/voidfunction.qml"_qs));
+ QVERIFY2(component.isReady(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+ QVERIFY(object->objectName().isEmpty());
+ object->metaObject()->invokeMethod(object.data(), "doesNotReturnValue");
+ QCOMPARE(object->objectName(), u"barbar"_qs);
+}
+
+void tst_QmlCppCodegen::overriddenProperty()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/childobject.qml"_qs));
+ QVERIFY2(component.isReady(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+ QCOMPARE(object->objectName(), u"kraut"_qs);
+ QCOMPARE(object->property("doneThing").toInt(), 5);
+ QCOMPARE(object->property("usingFinal").toInt(), 5);
+
+ auto checkAssignment = [&]() {
+ const QString newName = u"worscht"_qs;
+ QMetaObject::invokeMethod(object.data(), "setChildObjectName", Q_ARG(QString, newName));
+ QCOMPARE(object->objectName(), newName);
+ };
+ checkAssignment();
+
+ ObjectWithMethod *benign = new ObjectWithMethod(object.data());
+ benign->theThing = 10;
+ benign->setObjectName(u"cabbage"_qs);
+ object->setProperty("child", QVariant::fromValue(benign));
+ QCOMPARE(object->objectName(), u"cabbage"_qs);
+ checkAssignment();
+ QCOMPARE(object->property("doneThing").toInt(), 10);
+ QCOMPARE(object->property("usingFinal").toInt(), 10);
+
+ OverriddenObjectName *evil = new OverriddenObjectName(object.data());
+ QTest::ignoreMessage(QtWarningMsg,
+ "Final member fff is overridden in class OverriddenObjectName. "
+ "The override won't be used.");
+ object->setProperty("child", QVariant::fromValue(evil));
+
+ QCOMPARE(object->objectName(), u"borschtsch"_qs);
+
+ checkAssignment();
+ QCOMPARE(object->property("doneThing").toInt(), 7);
+ QCOMPARE(object->property("usingFinal").toInt(), 5);
+}
+
+void tst_QmlCppCodegen::listLength()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/listlength.qml"_qs));
+ QVERIFY2(component.isReady(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+ QCOMPARE(object->property("l").toInt(), 2);
+}
+
+void tst_QmlCppCodegen::parentProperty()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/parentProp.qml"_qs));
+ QVERIFY2(component.isReady(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+ QCOMPARE(object->property("c").toInt(), 11);
+ QCOMPARE(object->property("i").toInt(), 22);
+ object->setProperty("a", QVariant::fromValue(22));
+ QCOMPARE(object->property("c").toInt(), 28);
+ object->setProperty("implicitWidth", QVariant::fromValue(14));
+ QCOMPARE(object->property("i").toInt(), 26);
+
+ QObject *child = qmlContext(object.data())->objectForName(u"child"_qs);
+ QObject *sibling = qmlContext(object.data())->objectForName(u"sibling"_qs);
+ QObject *evil = qmlContext(object.data())->objectForName(u"evil"_qs);
+
+ child->setProperty("parent", QVariant::fromValue(sibling));
+
+ QCOMPARE(child->property("b").toInt(), 0);
+ QCOMPARE(child->property("i").toInt(), 28);
+ QCOMPARE(object->property("i").toInt(), 56);
+
+ child->setProperty("parent", QVariant::fromValue(evil));
+
+ QCOMPARE(child->property("b").toInt(), 5994);
+ QCOMPARE(object->property("c").toInt(), 5996);
+
+ QCOMPARE(child->property("i").toInt(), 443);
+ QCOMPARE(object->property("i").toInt(), 886);
+
+ {
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/specificParent.qml"_qs));
+
+ QVERIFY2(component.isReady(), qPrintable(component.errorString()));
+ QScopedPointer<QObject> rootObject(component.create());
+ QVERIFY(rootObject);
+
+ QCOMPARE(rootObject->property("a").toReal(), 77.0);
+ }
+}
+
+void tst_QmlCppCodegen::registerElimination()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/registerelimination.qml"_qs));
+ QVERIFY2(component.isReady(), component.errorString().toUtf8());
+ QScopedPointer<QObject> object(component.create());
+ QVERIFY(!object.isNull());
+
+ // Increment of 23 hits both 0 and 460
+ for (int input = -23; input < 700; input += 23) {
+ object->setProperty("input", input);
+ if (input <= 0 || input >= 460)
+ QCOMPARE(object->property("output").toInt(), 459);
+ else
+ QCOMPARE(object->property("output").toInt(), input);
+ }
+}
+
+void tst_QmlCppCodegen::asCast()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/asCast.qml"_qs));
+ QVERIFY2(component.isReady(), component.errorString().toUtf8());
+ QScopedPointer<QObject> root(component.create());
+ QVERIFY(!root.isNull());
+
+ QQmlContext *context = qmlContext(root.data());
+ const QObject *object = context->objectForName(u"object"_qs);
+ const QObject *item = context->objectForName(u"item"_qs);
+ const QObject *rectangle = context->objectForName(u"rectangle"_qs);
+ const QObject *dummy = context->objectForName(u"dummy"_qs);
+
+ QCOMPARE(qvariant_cast<QObject *>(root->property("objectAsObject")), object);
+ QCOMPARE(qvariant_cast<QObject *>(root->property("objectAsItem")), nullptr);
+ QCOMPARE(qvariant_cast<QObject *>(root->property("objectAsRectangle")), nullptr);
+ QCOMPARE(qvariant_cast<QObject *>(root->property("objectAsDummy")), nullptr);
+
+ QCOMPARE(qvariant_cast<QObject *>(root->property("itemAsObject")), item);
+ QCOMPARE(qvariant_cast<QObject *>(root->property("itemAsItem")), item);
+ QCOMPARE(qvariant_cast<QObject *>(root->property("itemAsRectangle")), nullptr);
+ QCOMPARE(qvariant_cast<QObject *>(root->property("itemAsDummy")), nullptr);
+
+ QCOMPARE(qvariant_cast<QObject *>(root->property("rectangleAsObject")), rectangle);
+ QCOMPARE(qvariant_cast<QObject *>(root->property("rectangleAsItem")), rectangle);
+ QCOMPARE(qvariant_cast<QObject *>(root->property("rectangleAsRectangle")), rectangle);
+ QCOMPARE(qvariant_cast<QObject *>(root->property("rectangleAsDummy")), nullptr);
+
+ QCOMPARE(qvariant_cast<QObject *>(root->property("dummyAsObject")), dummy);
+ QCOMPARE(qvariant_cast<QObject *>(root->property("dummyAsItem")), dummy);
+ QCOMPARE(qvariant_cast<QObject *>(root->property("dummyAsRectangle")), nullptr);
+ QCOMPARE(qvariant_cast<QObject *>(root->property("dummyAsDummy")), dummy);
+}
+
+void tst_QmlCppCodegen::noQQmlData()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/noQQmlData.qml"_qs));
+ QVERIFY2(component.isReady(), component.errorString().toUtf8());
+
+ QTest::ignoreMessage(QtWarningMsg, "qrc:/TestTypes/noQQmlData.qml:7: TypeError: "
+ "Cannot read property 'name' of null");
+ QScopedPointer<QObject> root(component.create());
+ QVERIFY(!root.isNull());
+
+ BirthdayParty *party = qobject_cast<BirthdayParty *>(root.data());
+ QVERIFY(party != nullptr);
+
+ QCOMPARE(party->host(), nullptr);
+ QCOMPARE(party->property("n").toString(), QString());
+
+ Person *host1 = new Person(party);
+ party->setHost(host1);
+ QCOMPARE(party->property("n").toString(), u"Bart in da house!"_qs);
+ host1->setName(u"Marge"_qs);
+ QCOMPARE(party->property("n").toString(), u"Marge in da house!"_qs);
+
+ QTest::ignoreMessage(QtWarningMsg, "qrc:/TestTypes/noQQmlData.qml:7: TypeError: "
+ "Cannot read property 'name' of null");
+
+ // Doesn't crash
+ party->setHost(nullptr);
+
+ // Lookups are initialized now, and we introduce an object without QQmlData
+ Person *host2 = new Person(party);
+ party->setHost(host2);
+ QCOMPARE(party->property("n").toString(), u"Bart in da house!"_qs);
+ host2->setName(u"Homer"_qs);
+ QCOMPARE(party->property("n").toString(), u"Homer in da house!"_qs);
+
+ QMetaObject::invokeMethod(party, "burn");
+ engine.collectGarbage();
+
+ // Does not crash
+ party->setProperty("inDaHouse", u" burns!"_qs);
+
+ // Mr Burns may or may not burn, depending on whether we use lookups.
+ // If using lookups, the binding is aborted when we find the isQueuedForDeletion flag.
+ // If reading the property directly, we don't have to care about it.
+ QVERIFY(party->property("n").toString().startsWith(u"Mr Burns"_qs));
+}
+
+void tst_QmlCppCodegen::scopeObjectDestruction()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/fileDialog.qml"_qs));
+
+ QVERIFY2(component.isReady(), qPrintable(component.errorString()));
+ QScopedPointer<QObject> rootObject(component.create());
+ QVERIFY(rootObject);
+
+ QObject *dialog = rootObject->property("dialog").value<QObject *>();
+ QVERIFY(dialog);
+
+ // We cannot check the warning messages. The AOT compiled code complains about reading the
+ // "parent" property of an object scheduled for deletion. The runtime silently returns undefined
+ // at that point and then complains about not being able to read a property on undefined.
+
+ // Doesn't crash, even though it triggers bindings on scope objects scheduled for deletion.
+ QMetaObject::invokeMethod(dialog, "open");
+}
+
+static void checkColorProperties(QQmlComponent *component)
+{
+ QVERIFY2(component->isReady(), qPrintable(component->errorString()));
+ QScopedPointer<QObject> rootObject(component->create());
+ QVERIFY(rootObject);
+
+ const QMetaObject *mo = QMetaType::fromName("QQuickIcon").metaObject();
+ QVERIFY(mo != nullptr);
+
+ const QMetaProperty prop = mo->property(mo->indexOfProperty("color"));
+ QVERIFY(prop.isValid());
+
+ const QVariant a = rootObject->property("a");
+ QVERIFY(a.isValid());
+
+ const QVariant iconColor = prop.readOnGadget(rootObject->property("icon").data());
+ QVERIFY(iconColor.isValid());
+
+ const QMetaType colorType = QMetaType::fromName("QColor");
+ QVERIFY(colorType.isValid());
+
+ QCOMPARE(a.metaType(), colorType);
+ QCOMPARE(iconColor.metaType(), colorType);
+
+ QCOMPARE(iconColor, a);
+}
+
+void tst_QmlCppCodegen::colorAsVariant()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/colorAsVariant.qml"_qs));
+ checkColorProperties(&component);
+}
+
+void tst_QmlCppCodegen::bindToValueType()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/bindToValueType.qml"_qs));
+ checkColorProperties(&component);
+}
+
+void tst_QmlCppCodegen::undefinedResets()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/undefinedResets.qml"_qs));
+
+ QVERIFY2(component.isReady(), qPrintable(component.errorString()));
+ QScopedPointer<QObject> rootObject(component.create());
+ QVERIFY(rootObject);
+
+ Person *person = qobject_cast<Person *>(rootObject.data());
+ QVERIFY(person);
+ QCOMPARE(person->shoeSize(), 0);
+ QCOMPARE(person->name(), u"Marge"_qs);
+
+ person->setShoeSize(11);
+
+ QCOMPARE(person->shoeSize(), 11);
+ QCOMPARE(person->name(), u"Bart"_qs);
+
+ person->setShoeSize(10);
+ QCOMPARE(person->shoeSize(), 10);
+ QCOMPARE(person->name(), u"Marge"_qs);
+}
+
+void tst_QmlCppCodegen::innerObjectNonShadowable()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/ownProperty.qml"_qs));
+
+ QVERIFY2(component.isReady(), qPrintable(component.errorString()));
+ QScopedPointer<QObject> rootObject(component.create());
+ QVERIFY(rootObject);
+
+ QCOMPARE(rootObject->objectName(), u"foo"_qs);
+}
+
+void tst_QmlCppCodegen::ownPropertiesNonShadowable()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/overriddenMember.qml"_qs));
+
+ QVERIFY2(component.isReady(), qPrintable(component.errorString()));
+ QScopedPointer<QObject> rootObject(component.create());
+ QVERIFY(rootObject);
+
+ QCOMPARE(rootObject->property("ppp").toInt(), 16);
+ QCOMPARE(rootObject->property("ppp2").toInt(), 9);
+ QCOMPARE(rootObject->property("ppp3").toInt(), 12);
+}
+
+void tst_QmlCppCodegen::modulePrefix()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/modulePrefix.qml"_qs));
+
+ QVERIFY2(component.isReady(), qPrintable(component.errorString()));
+ QScopedPointer<QObject> rootObject(component.create());
+ QVERIFY(rootObject);
+
+ QCOMPARE(rootObject->property("foo").toDateTime(), QDateTime(QDate(1911, 3, 4), QTime()));
+ QCOMPARE(rootObject->property("bar").toDateTime(), QDateTime(QDate(1911, 3, 4), QTime()));
+ QCOMPARE(rootObject->property("baz").toString(), QStringLiteral("ItIsTheSingleton"));
+}
+
+void tst_QmlCppCodegen::colorString()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/colorString.qml"_qs));
+
+ QVERIFY2(component.isReady(), qPrintable(component.errorString()));
+ QScopedPointer<QObject> rootObject(component.create());
+ QVERIFY(rootObject);
+
+ QCOMPARE(qvariant_cast<QColor>(rootObject->property("c")), QColor::fromRgb(0xdd, 0xdd, 0xdd));
+ QCOMPARE(qvariant_cast<QColor>(rootObject->property("d")), QColor::fromRgb(0xaa, 0xaa, 0xaa));
+ QCOMPARE(qvariant_cast<QColor>(rootObject->property("e")), QColor::fromRgb(0x11, 0x22, 0x33));
+}
+
+void tst_QmlCppCodegen::urlString()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine, QUrl(u"qrc:/TestTypes/urlString.qml"_qs));
+
+ QVERIFY2(component.isReady(), qPrintable(component.errorString()));
+ QScopedPointer<QObject> rootObject(component.create());
+ QVERIFY(rootObject);
+
+ QCOMPARE(qvariant_cast<QUrl>(rootObject->property("c")), QUrl(u"http://dddddd.com"_qs));
+ QCOMPARE(qvariant_cast<QUrl>(rootObject->property("d")), QUrl(u"http://aaaaaa.com"_qs));
+ QCOMPARE(qvariant_cast<QUrl>(rootObject->property("e")), QUrl(u"http://a112233.de"_qs));
+}
+
+
+void tst_QmlCppCodegen::callContextPropertyLookupResult()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine, QUrl(u"qrc:/TestTypes/callContextPropertyLookupResult.qml"_qs));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(o);
+
+ QVERIFY(qvariant_cast<QQmlComponent *>(o->property("c")) != nullptr);
+}
+
+void tst_QmlCppCodegen::deadShoeSize()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine, QUrl(u"qrc:/TestTypes/deadShoeSize.qml"_qs));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QTest::ignoreMessage(QtWarningMsg, "qrc:/TestTypes/deadShoeSize.qml:5: Error: ouch");
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(o);
+ QCOMPARE(o->property("shoeSize").toInt(), 0);
+}
+
+void tst_QmlCppCodegen::listIndices()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine, QUrl(u"qrc:/TestTypes/listIndices.qml"_qs));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(o);
+
+ QQmlListReference list(o.data(), "items");
+ QCOMPARE(list.count(), 3);
+ for (int i = 0; i < 3; ++i)
+ QCOMPARE(list.at(i), o.data());
+ QCOMPARE(o->property("numItems").toInt(), 3);
+}
+
+void tst_QmlCppCodegen::jsMathObject()
+{
+ QQmlEngine engine;
+ QQmlComponent c(&engine, QUrl(u"qrc:/TestTypes/jsMathObject.qml"_qs));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(o);
+
+ const double inputs[] = {
+ qQNaN(), -qInf(),
+ std::numeric_limits<double>::min(),
+ std::numeric_limits<float>::min(),
+ std::numeric_limits<qint32>::min(),
+ -1000.2, -100, -2, -1.333, -1, -0.84, -0.5,
+ std::copysign(0.0, -1), 0.0,
+ 0.5, 0.77, 1, 1.4545, 2, 199, 2002.13,
+ std::numeric_limits<qint32>::max(),
+ std::numeric_limits<quint32>::max(),
+ std::numeric_limits<float>::max(),
+ std::numeric_limits<double>::max(),
+ };
+
+ QJSManagedValue math(engine.globalObject().property(QStringLiteral("Math")), &engine);
+
+ const QMetaObject *metaObject = o->metaObject();
+
+ QString name;
+ for (double a : inputs) {
+ for (double b : inputs) {
+ o->setProperty("a", a);
+ o->setProperty("b", b);
+ for (int i = 0, end = metaObject->propertyCount(); i != end; ++i) {
+ const QMetaProperty prop = metaObject->property(i);
+ name = QString::fromUtf8(prop.name());
+
+ if (!math.hasProperty(name))
+ continue;
+
+ const double result = prop.read(o.data()).toDouble();
+ if (name == QStringLiteral("random")) {
+ QVERIFY(result >= 0.0 && result < 1.0);
+ continue;
+ }
+
+ QJSManagedValue jsMethod(math.property(name), &engine);
+ const double expected = jsMethod.call(QJSValueList {a, b}).toNumber();
+ QCOMPARE(result, expected);
+ }
+ }
+ }
+
+ if (QTest::currentTestFailed())
+ qDebug() << name << "failed.";
+}
+
+void tst_QmlCppCodegen::intEnumCompare()
+{
+ QQmlEngine engine;
+ {
+ QQmlComponent c(&engine, QUrl(u"qrc:/TestTypes/intEnumCompare.qml"_qs));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(o);
+ QCOMPARE(o->property("a").toBool(), true);
+ QCOMPARE(o->property("b").toBool(), false);
+ QCOMPARE(o->property("c").toBool(), true);
+ QCOMPARE(o->property("d").toBool(), false);
+ }
+
+ {
+ // We cannot use Qt.red in QML because it's lower case.
+ QQmlComponent c(&engine, QUrl(u"qrc:/TestTypes/enumInvalid.qml"_qs));
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+ QScopedPointer<QObject> o(c.create());
+ QVERIFY(o);
+ QCOMPARE(o->property("c").toBool(), false);
+ QCOMPARE(o->property("d").toBool(), false);
+ }
+}
+
+void tst_QmlCppCodegen::runInterpreted()
+{
+ if (qEnvironmentVariableIsSet("QV4_FORCE_INTERPRETER"))
+ QSKIP("Already running in interpreted mode");
+
+#if QT_CONFIG(process)
+ QProcess process;
+ process.setProgram(QCoreApplication::applicationFilePath());
+ process.setEnvironment(QProcess::systemEnvironment()
+ + QStringList(u"QV4_FORCE_INTERPRETER=1"_qs));
+ process.start();
+ QVERIFY(process.waitForFinished());
+ QCOMPARE(process.exitStatus(), QProcess::NormalExit);
+ QCOMPARE(process.exitCode(), 0);
+#else
+ QSKIP("Test needs QProcess");
+#endif
+}
+
+QTEST_MAIN(tst_QmlCppCodegen)
+
+#include "tst_qmlcppcodegen.moc"
diff --git a/tools/qmlcachegen/qmlcachegen.cpp b/tools/qmlcachegen/qmlcachegen.cpp
index 29fe609469..3648a10f95 100644
--- a/tools/qmlcachegen/qmlcachegen.cpp
+++ b/tools/qmlcachegen/qmlcachegen.cpp
@@ -36,6 +36,8 @@
#include <QSaveFile>
#include <QScopedPointer>
#include <QScopeGuard>
+#include <QLibraryInfo>
+#include <QLoggingCategory>
#include <private/qqmlirbuilder_p.h>
#include <private/qqmljsparser_p.h>
@@ -47,7 +49,9 @@
#include <algorithm>
-using namespace QQmlJS;
+QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcAotCompiler);
+QT_END_NAMESPACE
static bool argumentsFromCommandLineAndFile(QStringList& allArguments, const QStringList &arguments)
{
@@ -106,12 +110,21 @@ int main(int argc, char **argv)
QCommandLineOption directCallsOption(QStringLiteral("direct-calls"), QCoreApplication::translate("main", "This option is ignored."));
directCallsOption.setFlags(QCommandLineOption::HiddenFromHelp);
parser.addOption(directCallsOption);
- QCommandLineOption includesOption(QStringLiteral("i"), QCoreApplication::translate("main", "This option is ignored."), QCoreApplication::translate("main", "ignored file"));
- includesOption.setFlags(QCommandLineOption::HiddenFromHelp);
- parser.addOption(includesOption);
- QCommandLineOption importPathOption(QStringLiteral("I"), QCoreApplication::translate("main", "This option is ignored."), QCoreApplication::translate("main", "ignored path"));
- importPathOption.setFlags(QCommandLineOption::HiddenFromHelp);
+ QCommandLineOption importsOption(
+ QStringLiteral("i"),
+ QCoreApplication::translate("main", "Import extra qmltypes"),
+ QCoreApplication::translate("main", "qmltypes file"));
+ parser.addOption(importsOption);
+ QCommandLineOption importPathOption(
+ QStringLiteral("I"),
+ QCoreApplication::translate("main", "Look for QML modules in specified directory"),
+ QCoreApplication::translate("main", "import directory"));
parser.addOption(importPathOption);
+ QCommandLineOption onlyBytecode(
+ QStringLiteral("only-bytecode"),
+ QCoreApplication::translate(
+ "main", "Generate only byte code for bindings and functions, no C++ code"));
+ parser.addOption(onlyBytecode);
QCommandLineOption outputFileOption(QStringLiteral("o"), QCoreApplication::translate("main", "Output file name"), QCoreApplication::translate("main", "file name"));
parser.addOption(outputFileOption);
@@ -188,35 +201,34 @@ int main(int argc, char **argv)
QString inputFileUrl = inputFile;
QQmlJSSaveFunction saveFunction;
- if (target == GenerateCpp) {
- QQmlJSResourceFileMapper fileMapper(parser.values(resourceOption));
- QString inputResourcePath = parser.value(resourcePathOption);
-
- // If the user didn't specify the resource path corresponding to the file on disk being
- // compiled, try to determine it from the resource file, if one was supplied.
- if (inputResourcePath.isEmpty()) {
- const QStringList resourcePaths = fileMapper.resourcePaths(
- QQmlJSResourceFileMapper::localFileFilter(inputFile));
- if (resourcePaths.isEmpty()) {
- fprintf(stderr, "No resource path for file: %s\n", qPrintable(inputFile));
- return EXIT_FAILURE;
- }
-
- if (resourcePaths.size() != 1) {
- fprintf(stderr, "Multiple resource paths for file %s. "
- "Use the --%s option to disambiguate:\n",
- qPrintable(inputFile),
- qPrintable(resourcePathOption.names().first()));
- for (const QString &resourcePath: resourcePaths)
- fprintf(stderr, "\t%s\n", qPrintable(resourcePath));
- return EXIT_FAILURE;
- }
+ QQmlJSResourceFileMapper fileMapper(parser.values(resourceOption));
+ QString inputResourcePath = parser.value(resourcePathOption);
+
+ // If the user didn't specify the resource path corresponding to the file on disk being
+ // compiled, try to determine it from the resource file, if one was supplied.
+ if (inputResourcePath.isEmpty()) {
+ const QStringList resourcePaths = fileMapper.resourcePaths(
+ QQmlJSResourceFileMapper::localFileFilter(inputFile));
+ if (target == GenerateCpp && resourcePaths.isEmpty()) {
+ fprintf(stderr, "No resource path for file: %s\n", qPrintable(inputFile));
+ return EXIT_FAILURE;
+ }
+ if (resourcePaths.size() == 1) {
inputResourcePath = resourcePaths.first();
+ } else if (target == GenerateCpp) {
+ fprintf(stderr, "Multiple resource paths for file %s. "
+ "Use the --%s option to disambiguate:\n",
+ qPrintable(inputFile),
+ qPrintable(resourcePathOption.names().first()));
+ for (const QString &resourcePath: resourcePaths)
+ fprintf(stderr, "\t%s\n", qPrintable(resourcePath));
+ return EXIT_FAILURE;
}
+ }
+ if (target == GenerateCpp) {
inputFileUrl = QStringLiteral("qrc://") + inputResourcePath;
-
saveFunction = [inputResourcePath, outputFileName](
const QV4::CompiledData::SaveableUnitPointer &unit,
const QQmlJSAotFunctionMap &aotFunctions,
@@ -240,9 +252,47 @@ int main(int argc, char **argv)
if (inputFile.endsWith(QLatin1String(".qml"))) {
QQmlJSCompileError error;
- if (!qCompileQmlFile(inputFile, saveFunction, nullptr, &error)) {
- error.augment(QLatin1String("Error compiling qml file: ")).print();
- return EXIT_FAILURE;
+ if (target != GenerateCpp || inputResourcePath.isEmpty() || parser.isSet(onlyBytecode)) {
+ if (!qCompileQmlFile(inputFile, saveFunction, nullptr, &error,
+ /* storeSourceLocation */ false)) {
+ error.augment(QStringLiteral("Error compiling qml file: ")).print();
+ return EXIT_FAILURE;
+ }
+ } else {
+ QStringList importPaths;
+ if (parser.isSet(importPathOption))
+ importPaths = parser.values(importPathOption);
+
+ importPaths.append(QLibraryInfo::path(QLibraryInfo::QmlImportsPath));
+
+ QQmlJSImporter importer(
+ importPaths, parser.isSet(resourceOption) ? &fileMapper : nullptr);
+ QQmlJSLogger logger;
+
+ // Always trigger the qFatal() on "pragma Strict" violations.
+ logger.setCategoryError(Log_Compiler, true);
+
+ // By default, we're completely silent,
+ // as the lcAotCompiler category default is QtFatalMsg
+ if (lcAotCompiler().isDebugEnabled())
+ logger.setCategoryLevel(Log_Compiler, QtDebugMsg);
+ else if (lcAotCompiler().isInfoEnabled())
+ logger.setCategoryLevel(Log_Compiler, QtInfoMsg);
+ else if (lcAotCompiler().isWarningEnabled())
+ logger.setCategoryLevel(Log_Compiler, QtWarningMsg);
+ else if (lcAotCompiler().isCriticalEnabled())
+ logger.setCategoryLevel(Log_Compiler, QtCriticalMsg);
+ else
+ logger.setSilent(true);
+
+ QQmlJSAotCompiler cppCodeGen(
+ &importer, u':' + inputResourcePath, parser.values(importsOption), &logger);
+
+ if (!qCompileQmlFile(inputFile, saveFunction, &cppCodeGen, &error,
+ /* storeSourceLocation */ true)) {
+ error.augment(QStringLiteral("Error compiling qml file: ")).print();
+ return EXIT_FAILURE;
+ }
}
} else if (inputFile.endsWith(QLatin1String(".js")) || inputFile.endsWith(QLatin1String(".mjs"))) {
QQmlJSCompileError error;