diff options
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 Binary files differnew file mode 100644 index 0000000000..75ec0fa080 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/ProgressBar/built-with-Qt_Large.png 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>(¬hing); } +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; |