diff options
Diffstat (limited to 'tests/auto/quickcontrols2/controls')
79 files changed, 31071 insertions, 0 deletions
diff --git a/tests/auto/quickcontrols2/controls/CMakeLists.txt b/tests/auto/quickcontrols2/controls/CMakeLists.txt new file mode 100644 index 0000000000..9e80b76580 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/CMakeLists.txt @@ -0,0 +1,7 @@ +# Generated from controls.pro. + +add_subdirectory(basic) +add_subdirectory(fusion) +add_subdirectory(imagine) +add_subdirectory(material) +add_subdirectory(universal) diff --git a/tests/auto/quickcontrols2/controls/basic/BLACKLIST b/tests/auto/quickcontrols2/controls/basic/BLACKLIST new file mode 100644 index 0000000000..476340700a --- /dev/null +++ b/tests/auto/quickcontrols2/controls/basic/BLACKLIST @@ -0,0 +1,5 @@ +# See qtbase/src/testlib/qtestblacklist.cpp for format + +# QTBUG-70597 +[Tumbler::test_itemsCorrectlyPositioned] +* diff --git a/tests/auto/quickcontrols2/controls/basic/CMakeLists.txt b/tests/auto/quickcontrols2/controls/basic/CMakeLists.txt new file mode 100644 index 0000000000..63a3f1a4ff --- /dev/null +++ b/tests/auto/quickcontrols2/controls/basic/CMakeLists.txt @@ -0,0 +1,28 @@ +# Generated from basic.pro. + +##################################################################### +## tst_basic Test: +##################################################################### + +# Collect test data +file(GLOB_RECURSE test_data_glob + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../data/tst_*) +list(APPEND test_data ${test_data_glob}) + +qt_internal_add_test(tst_basic + GUI + QMLTEST + SOURCES + tst_basic.cpp + DEFINES + TST_CONTROLS_DATA=\\\"${CMAKE_CURRENT_SOURCE_DIR}/../data\\\" + PUBLIC_LIBRARIES + Qt::Gui + Qt::QuickControls2 + TESTDATA ${test_data} +) + +#### Keys ignored in scope 1:.:.:basic.pro:<TRUE>: +# OTHER_FILES = "$$PWD/../data/*.qml" +# TEMPLATE = "app" diff --git a/tests/auto/quickcontrols2/controls/basic/dependencies.qml b/tests/auto/quickcontrols2/controls/basic/dependencies.qml new file mode 100644 index 0000000000..f7d87422b5 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/basic/dependencies.qml @@ -0,0 +1,5 @@ +import QtTest +import QtQuick +import QtQuick.Controls + +TestCase { } diff --git a/tests/auto/quickcontrols2/controls/basic/tst_basic.cpp b/tests/auto/quickcontrols2/controls/basic/tst_basic.cpp new file mode 100644 index 0000000000..95d7e36a38 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/basic/tst_basic.cpp @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtQuickTest/quicktest.h> +#include <QtQuickControls2/qquickstyle.h> + +int main(int argc, char *argv[]) +{ + QTEST_SET_MAIN_SOURCE_PATH + qputenv("QML_NO_TOUCH_COMPRESSION", "1"); + QQuickStyle::setStyle("Basic"); + return quick_test_main(argc, argv, "tst_controls::Basic", TST_CONTROLS_DATA); +} diff --git a/tests/auto/quickcontrols2/controls/data/SignalSequenceSpy.qml b/tests/auto/quickcontrols2/controls/data/SignalSequenceSpy.qml new file mode 100644 index 0000000000..40718c0910 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/SignalSequenceSpy.qml @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 + +QtObject { + property QtObject target: null + // We could just listen to all signals (try { signal.connect(/*...*/) } catch (e)) + // if it weren't for the fact the spy is often declared as a property of the control, + // which creates a "spyChanged" signal, which leads to an unexpected spyChanged signal + // emission. However, we don't know what the property will be called, so the signals + // have to be listed explicitly. + property var signals: [] + property var expectedSequence: [] + property int sequenceIndex: 0 + property bool sequenceFailure: false + readonly property bool success: !sequenceFailure && sequenceIndex === expectedSequence.length + + function reset() { + sequenceIndex = 0 + sequenceFailure = false + } + + property QtObject __oldTarget: null + property var __connections: [] + + onExpectedSequenceChanged: reset() + + // We may call __setup from onTargetChanged and as we would read the signals property + // inside __setup, we may be initializing the binding for signals for the first time, which + // will write the value to the property and trigger onSignalsChanged and call __setup + // again. One easy way to protect against it is to evaluate those two dependencies upfront + onTargetChanged: __setup(target, signals) + onSignalsChanged: __setup(target, signals) + + function __setup(target, signals) { + if (__oldTarget) { + __connections.forEach(function (cx) { + __oldTarget[cx.name].disconnect(cx.method) + }) + __oldTarget = null + } + + __connections = [] + + if (!!target && !!signals && signals.length > 0) { + signals.forEach(function (signalName) { + var handlerName = "on" + signalName.substr(0, 1).toUpperCase() + signalName.substr(1) + var method = function() { __checkSignal(signalName, arguments) } + target[handlerName].connect(method) + __connections.push({ "name": handlerName, "method": method }) + }) + __oldTarget = target + } + } + + function __checkSignal(signalName, signalArgs) { + if (sequenceFailure) + return; + + if (sequenceIndex >= expectedSequence.length) { + console.warn("SignalSequenceSpy: Received unexpected signal '" + signalName + "' (none expected).") + sequenceFailure = true + return + } + + var expectedSignal = expectedSequence[sequenceIndex] + if (typeof expectedSignal === "string") { + if (expectedSignal === signalName) { + sequenceIndex++ + return + } + } else if (typeof expectedSignal === "object") { + var expectedSignalData = expectedSignal + expectedSignal = expectedSignalData[0] + if (expectedSignal === signalName) { + var expectedValues = expectedSignalData[1] + for (var p in expectedValues) { + if (target[p] != expectedValues[p]) { + console.warn("SignalSequenceSpy: Value mismatch for property '" + p + "' after '" + signalName + "' signal." + + __mismatchValuesFormat(target[p], expectedValues[p])) + sequenceFailure = true + return + } + } + sequenceIndex++ + return + } + } + console.warn("SignalSequenceSpy: Received unexpected signal (is \"" + expectedSignal + "\" listed in the signals array?)" + + __mismatchValuesFormat(signalName, expectedSignal)) + sequenceFailure = true + } + + function __mismatchValuesFormat(actual, expected) { + return "\n Actual : " + actual + + "\n Expected : " + expected + + "\n Sequence index: " + sequenceIndex + } +} diff --git a/tests/auto/quickcontrols2/controls/data/TestItem.qml b/tests/auto/quickcontrols2/controls/data/TestItem.qml new file mode 100644 index 0000000000..df81269ce1 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/TestItem.qml @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 + +Item { + id: item + property var createdCallback + property var destroyedCallback + Component.onCompleted: if (createdCallback) createdCallback(item) + Component.onDestruction: if (destroyedCallback) destroyedCallback(item) +} diff --git a/tests/auto/quickcontrols2/controls/data/TumblerDatePicker.qml b/tests/auto/quickcontrols2/controls/data/TumblerDatePicker.qml new file mode 100644 index 0000000000..c11b504b75 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/TumblerDatePicker.qml @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 + +Row { + id: datePicker + + readonly property var days: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + + property alias dayTumbler: dayTumbler + property alias monthTumbler: monthTumbler + property alias yearTumbler: yearTumbler + + Tumbler { + id: dayTumbler + objectName: "dayTumbler" + + Component.onCompleted: updateModel() + + function updateModel() { + var previousIndex = dayTumbler.currentIndex; + var array = []; + var newDays = datePicker.days[monthTumbler.currentIndex]; + for (var i = 0; i < newDays; ++i) { + array.push(i + 1); + } + dayTumbler.model = array; + dayTumbler.currentIndex = Math.min(newDays - 1, previousIndex); + } + } + Tumbler { + id: monthTumbler + objectName: "monthTumbler" + model: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + onCurrentIndexChanged: dayTumbler.updateModel() + } + Tumbler { + id: yearTumbler + objectName: "yearTumbler" + model: ListModel { + objectName: "yearTumblerListModel" + Component.onCompleted: { + for (var i = 2000; i < 2100; ++i) { + append({value: i.toString()}); + } + } + } + } +} diff --git a/tests/auto/quickcontrols2/controls/data/TumblerListView.qml b/tests/auto/quickcontrols2/controls/data/TumblerListView.qml new file mode 100644 index 0000000000..a581c4327d --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/TumblerListView.qml @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 + +ListView { + implicitWidth: 60 + implicitHeight: 200 + snapMode: ListView.SnapToItem + highlightRangeMode: ListView.StrictlyEnforceRange + preferredHighlightBegin: height / 2 - (height / parent.visibleItemCount / 2) + preferredHighlightEnd: height / 2 + (height / parent.visibleItemCount / 2) + clip: true + model: parent.model + delegate: Text { + objectName: text + text: "Custom" + modelData + opacity: 1.0 - Math.abs(Tumbler.displacement) / (Tumbler.tumbler.visibleItemCount / 2) + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } +} diff --git a/tests/auto/quickcontrols2/controls/data/TumblerPathView.qml b/tests/auto/quickcontrols2/controls/data/TumblerPathView.qml new file mode 100644 index 0000000000..0f9be0e929 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/TumblerPathView.qml @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 + +PathView { + id: pathView + implicitWidth: 60 + implicitHeight: 200 + clip: true + pathItemCount: parent.visibleItemCount + 1 + preferredHighlightBegin: 0.5 + preferredHighlightEnd: 0.5 + dragMargin: width / 2 + model: parent.model + delegate: Text { + objectName: text + text: "Custom" + modelData + opacity: 1.0 - Math.abs(Tumbler.displacement) / (Tumbler.tumbler.visibleItemCount / 2) + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + path: Path { + startX: pathView.width / 2 + startY: -pathView.delegateHeight / 2 + PathLine { + x: pathView.width / 2 + y: pathView.pathItemCount * pathView.delegateHeight - pathView.delegateHeight / 2 + } + } + + property real delegateHeight: parent.availableHeight / parent.visibleItemCount +} diff --git a/tests/auto/quickcontrols2/controls/data/splitview/fillItemInMiddle.qml b/tests/auto/quickcontrols2/controls/data/splitview/fillItemInMiddle.qml new file mode 100644 index 0000000000..0acac9eb22 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/splitview/fillItemInMiddle.qml @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 + +SplitView { + anchors.fill: parent + + Rectangle { + objectName: "salmon" + color: objectName + implicitWidth: 25 + implicitHeight: 25 + } + Rectangle { + objectName: "navajowhite" + color: objectName + implicitWidth: 100 + implicitHeight: 100 + + SplitView.fillWidth: true + } + Rectangle { + objectName: "steelblue" + color: objectName + implicitWidth: 200 + implicitHeight: 200 + } +} diff --git a/tests/auto/quickcontrols2/controls/data/splitview/fillItemOnLeft.qml b/tests/auto/quickcontrols2/controls/data/splitview/fillItemOnLeft.qml new file mode 100644 index 0000000000..6f9c6e0b67 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/splitview/fillItemOnLeft.qml @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 + +SplitView { + anchors.fill: parent + + Rectangle { + objectName: "salmon" + color: objectName + implicitWidth: 25 + implicitHeight: 25 + + SplitView.fillWidth: true + } + Rectangle { + objectName: "navajowhite" + color: objectName + implicitWidth: 200 + implicitHeight: 200 + } + Rectangle { + objectName: "steelblue" + color: objectName + implicitWidth: 200 + implicitHeight: 200 + } +} diff --git a/tests/auto/quickcontrols2/controls/data/splitview/fillItemOnTop.qml b/tests/auto/quickcontrols2/controls/data/splitview/fillItemOnTop.qml new file mode 100644 index 0000000000..2955aa61eb --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/splitview/fillItemOnTop.qml @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 + +SplitView { + anchors.fill: parent + orientation: Qt.Vertical + + Rectangle { + objectName: "salmon" + color: objectName + implicitWidth: 25 + implicitHeight: 25 + + SplitView.fillHeight: true + } + Rectangle { + objectName: "navajowhite" + color: objectName + implicitWidth: 200 + implicitHeight: 200 + } + Rectangle { + objectName: "steelblue" + color: objectName + implicitWidth: 200 + implicitHeight: 200 + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_abstractbutton.qml b/tests/auto/quickcontrols2/controls/data/tst_abstractbutton.qml new file mode 100644 index 0000000000..6e59d3a688 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_abstractbutton.qml @@ -0,0 +1,956 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 200 + height: 200 + visible: true + when: windowShown + name: "AbstractButton" + + Component { + id: button + AbstractButton { + width: 100 + height: 50 + } + } + + Component { + id: item + Item { } + } + + Component { + id: action + Action { } + } + + Component { + id: signalSpy + SignalSpy { } + } + + function test_text() { + var control = createTemporaryObject(button, testCase); + verify(control); + + compare(control.text, ""); + control.text = "Button"; + compare(control.text, "Button"); + control.text = ""; + compare(control.text, ""); + } + + function test_baseline() { + var control = createTemporaryObject(button, testCase, {padding: 6}) + verify(control) + compare(control.baselineOffset, 0) + control.contentItem = item.createObject(control, {baselineOffset: 12}) + compare(control.baselineOffset, 18) + } + + function test_implicitSize() { + var control = createTemporaryObject(button, testCase) + verify(control) + + compare(control.implicitWidth, 0) + compare(control.implicitHeight, 0) + + control.contentItem = item.createObject(control, {implicitWidth: 10, implicitHeight: 20}) + compare(control.implicitWidth, 10) + compare(control.implicitHeight, 20) + + control.background = item.createObject(control, {implicitWidth: 20, implicitHeight: 30}) + compare(control.implicitWidth, 20) + compare(control.implicitHeight, 30) + + control.padding = 100 + compare(control.implicitWidth, 210) + compare(control.implicitHeight, 220) + } + + function test_pressPoint_data() { + return [ + { tag: "mouse", mouse: true }, + { tag: "touch", touch: true } + ] + } + + function test_pressPoint(data) { + var control = createTemporaryObject(button, testCase, {width: 100, height: 40}) + verify(control) + + var pressXChanges = 0 + var pressYChanges = 0 + + var pressXSpy = signalSpy.createObject(control, {target: control, signalName: "pressXChanged"}) + verify(pressXSpy.valid) + + var pressYSpy = signalSpy.createObject(control, {target: control, signalName: "pressYChanged"}) + verify(pressYSpy.valid) + + compare(control.pressX, 0) + compare(control.pressY, 0) + + var touch = data.touch ? touchEvent(control) : null + + if (data.touch) + touch.press(0, control, control.width / 2, control.height / 2).commit() + else + mousePress(control, control.width / 2, control.height / 2) + compare(control.pressX, control.width / 2) + compare(control.pressY, control.height / 2) + compare(pressXSpy.count, ++pressXChanges) + compare(pressYSpy.count, ++pressYChanges) + + if (data.touch) + touch.move(0, control, control.width / 2, control.height / 2).commit() + else + mouseMove(control, control.width / 2, control.height / 2) + compare(control.pressX, control.width / 2) + compare(control.pressY, control.height / 2) + compare(pressXSpy.count, pressXChanges) + compare(pressYSpy.count, pressYChanges) + + if (data.touch) + touch.move(0, control, control.width / 4, control.height / 4).commit() + else + mouseMove(control, control.width / 4, control.height / 4) + compare(control.pressX, control.width / 4) + compare(control.pressY, control.height / 4) + compare(pressXSpy.count, ++pressXChanges) + compare(pressYSpy.count, ++pressYChanges) + + if (data.touch) + touch.move(0, control, 0, 0).commit() + else + mouseMove(control, 0, 0) + compare(control.pressX, 0) + compare(control.pressY, 0) + compare(pressXSpy.count, ++pressXChanges) + compare(pressYSpy.count, ++pressYChanges) + + if (data.touch) + touch.move(0, control, -control.width / 2, -control.height / 2).commit() + else + mouseMove(control, -control.width / 2, -control.height / 2) + compare(control.pressX, -control.width / 2) + compare(control.pressY, -control.height / 2) + compare(pressXSpy.count, ++pressXChanges) + compare(pressYSpy.count, ++pressYChanges) + + if (data.touch) + touch.release(0, control, -control.width / 2, -control.height / 2).commit() + else + mouseRelease(control, -control.width / 2, -control.height / 2) + compare(control.pressX, -control.width / 2) + compare(control.pressY, -control.height / 2) + compare(pressXSpy.count, pressXChanges) + compare(pressYSpy.count, pressYChanges) + + if (data.touch) + touch.press(0, control, control.width - 1, control.height - 1).commit() + else + mousePress(control, control.width - 1, control.height - 1) + compare(control.pressX, control.width - 1) + compare(control.pressY, control.height - 1) + compare(pressXSpy.count, ++pressXChanges) + compare(pressYSpy.count, ++pressYChanges) + + if (data.touch) + touch.move(0, control, control.width + 1, control.height + 1).commit() + else + mouseMove(control, control.width + 1, control.height + 1) + compare(control.pressX, control.width + 1) + compare(control.pressY, control.height + 1) + compare(pressXSpy.count, ++pressXChanges) + compare(pressYSpy.count, ++pressYChanges) + + if (data.touch) + touch.release(0, control, control.width + 2, control.height + 2).commit() + else + mouseRelease(control, control.width + 2, control.height + 2) + compare(control.pressX, control.width + 2) + compare(control.pressY, control.height + 2) + compare(pressXSpy.count, ++pressXChanges) + compare(pressYSpy.count, ++pressYChanges) + } + + function test_pressAndHold() { + var control = createTemporaryObject(button, testCase, {checkable: true}) + verify(control) + + var pressAndHoldSpy = signalSpy.createObject(control, {target: control, signalName: "pressAndHold"}) + verify(pressAndHoldSpy.valid) + + mousePress(control) + pressAndHoldSpy.wait() + compare(control.checked, false) + + mouseRelease(control) + compare(control.checked, false) + } + + Component { + id: keyCatcher + Item { + property int lastKeyPress: -1 + property int lastKeyRelease: -1 + Keys.onPressed: lastKeyPress = event.key + Keys.onReleased: lastKeyRelease = event.key + } + } + + function test_keyEvents_data() { + return [ + { tag: "space", key: Qt.Key_Space, result: -1 }, + { tag: "backspace", key: Qt.Key_Backspace, result: Qt.Key_Backspace } + ] + } + + function test_keyEvents(data) { + var container = createTemporaryObject(keyCatcher, testCase) + verify(container) + + var control = button.createObject(container) + verify(control) + + control.forceActiveFocus() + verify(control.activeFocus) + + keyPress(data.key) + compare(container.lastKeyPress, data.result) + + keyRelease(data.key) + compare(container.lastKeyRelease, data.result) + } + + function test_icon() { + var control = createTemporaryObject(button, testCase) + verify(control) + compare(control.icon.name, "") + compare(control.icon.source, "") + compare(control.icon.width, 0) + compare(control.icon.height, 0) + compare(control.icon.color, "#00000000") + + var iconSpy = signalSpy.createObject(control, { target: control, signalName: "iconChanged"} ) + verify(iconSpy.valid) + + control.icon.name = "test-name" + compare(control.icon.name, "test-name") + compare(iconSpy.count, 1) + + control.icon.source = "qrc:/test-source" + compare(control.icon.source, "qrc:/test-source") + compare(iconSpy.count, 2) + + control.icon.width = 32 + compare(control.icon.width, 32) + compare(iconSpy.count, 3) + + control.icon.height = 32 + compare(control.icon.height, 32) + compare(iconSpy.count, 4) + + control.icon.color = "#ff0000" + compare(control.icon.color, "#ff0000") + compare(iconSpy.count, 5) + } + + function test_action_data() { + return [ + { tag: "implicit text", property: "text", + initButton: undefined, initAction: "Action", + assignExpected: "Action", assignChanged: true, + resetExpected: "", resetChanged: true }, + { tag: "explicit text", property: "text", + initButton: "Button", initAction: "Action", + assignExpected: "Button", assignChanged: false, + resetExpected: "Button", resetChanged: false }, + { tag: "empty button text", property: "text", + initButton: "", initAction: "Action", + assignExpected: "", assignChanged: false, + resetExpected: "", resetChanged: false }, + { tag: "empty action text", property: "text", + initButton: "Button", initAction: "", + assignExpected: "Button", assignChanged: false, + resetExpected: "Button", resetChanged: false }, + { tag: "empty both text", property: "text", + initButton: undefined, initAction: "", + assignExpected: "", assignChanged: false, + resetExpected: "", resetChanged: false }, + + { tag: "modify button text", property: "text", + initButton: undefined, initAction: "Action", + assignExpected: "Action", assignChanged: true, + modifyButton: "Button2", + modifyButtonExpected: "Button2", modifyButtonChanged: true, + resetExpected: "Button2", resetChanged: false }, + { tag: "modify implicit action text", property: "text", + initButton: undefined, initAction: "Action", + assignExpected: "Action", assignChanged: true, + modifyAction: "Action2", + modifyActionExpected: "Action2", modifyActionChanged: true, + resetExpected: "", resetChanged: true }, + { tag: "modify explicit action text", property: "text", + initButton: "Button", initAction: "Action", + assignExpected: "Button", assignChanged: false, + modifyAction: "Action2", + modifyActionExpected: "Button", modifyActionChanged: false, + resetExpected: "Button", resetChanged: false }, + ] + } + + function test_action(data) { + var control = createTemporaryObject(button, testCase) + verify(control) + control[data.property] = data.initButton + + var act = action.createObject(control) + act[data.property] = data.initAction + + var spy = signalSpy.createObject(control, {target: control, signalName: data.property + "Changed"}) + verify(spy.valid) + + // assign action + spy.clear() + control.action = act + compare(control[data.property], data.assignExpected) + compare(spy.count, data.assignChanged ? 1 : 0) + + // modify button + if (data.hasOwnProperty("modifyButton")) { + spy.clear() + control[data.property] = data.modifyButton + compare(control[data.property], data.modifyButtonExpected) + compare(spy.count, data.modifyButtonChanged ? 1 : 0) + } + + // modify action + if (data.hasOwnProperty("modifyAction")) { + spy.clear() + act[data.property] = data.modifyAction + compare(control[data.property], data.modifyActionExpected) + compare(spy.count, data.modifyActionChanged ? 1 : 0) + } + + // reset action + spy.clear() + control.action = null + compare(control[data.property], data.resetExpected) + compare(spy.count, data.resetChanged ? 1 : 0) + } + + function test_actionIcon_data() { + var data = [] + + // Save duplicating the rows by reusing them with different properties of the same type. + // This means that the first loop will test icon.name and the second one will test icon.source. + var stringPropertyValueSuffixes = [ + { propertyName: "name", valueSuffix: "IconName" }, + { propertyName: "source", valueSuffix: "IconSource" } + ] + + for (var i = 0; i < stringPropertyValueSuffixes.length; ++i) { + var propertyName = stringPropertyValueSuffixes[i].propertyName + var valueSuffix = stringPropertyValueSuffixes[i].valueSuffix + + var buttonPropertyValue = "Button" + valueSuffix + var buttonPropertyValue2 = "Button" + valueSuffix + "2" + var actionPropertyValue = "Action" + valueSuffix + var actionPropertyValue2 = "Action" + valueSuffix + "2" + + data.push({ tag: "implicit " + propertyName, property: propertyName, + initButton: undefined, initAction: actionPropertyValue, + assignExpected: actionPropertyValue, assignChanged: true, + resetExpected: "", resetChanged: true }) + data.push({ tag: "explicit " + propertyName, property: propertyName, + initButton: buttonPropertyValue, initAction: actionPropertyValue, + assignExpected: buttonPropertyValue, assignChanged: false, + resetExpected: buttonPropertyValue, resetChanged: false }) + data.push({ tag: "empty button " + propertyName, property: propertyName, + initButton: "", initAction: actionPropertyValue, + assignExpected: "", assignChanged: false, + resetExpected: "", resetChanged: false }) + data.push({ tag: "empty action " + propertyName, property: propertyName, + initButton: buttonPropertyValue, initAction: "", + assignExpected: buttonPropertyValue, assignChanged: false, + resetExpected: buttonPropertyValue, resetChanged: false }) + data.push({ tag: "empty both " + propertyName, property: propertyName, + initButton: undefined, initAction: "", + assignExpected: "", assignChanged: false, + resetExpected: "", resetChanged: false }) + data.push({ tag: "modify button " + propertyName, property: propertyName, + initButton: undefined, initAction: actionPropertyValue, + assignExpected: actionPropertyValue, assignChanged: true, + modifyButton: buttonPropertyValue2, + modifyButtonExpected: buttonPropertyValue2, modifyButtonChanged: true, + resetExpected: buttonPropertyValue2, resetChanged: false }) + data.push({ tag: "modify implicit action " + propertyName, property: propertyName, + initButton: undefined, initAction: actionPropertyValue, + assignExpected: actionPropertyValue, assignChanged: true, + modifyAction: actionPropertyValue2, + modifyActionExpected: actionPropertyValue2, modifyActionChanged: true, + resetExpected: "", resetChanged: true }) + data.push({ tag: "modify explicit action " + propertyName, property: propertyName, + initButton: buttonPropertyValue, initAction: actionPropertyValue, + assignExpected: buttonPropertyValue, assignChanged: false, + modifyAction: actionPropertyValue2, + modifyActionExpected: buttonPropertyValue, modifyActionChanged: false, + resetExpected: buttonPropertyValue, resetChanged: false }) + } + + var intPropertyNames = [ + "width", + "height", + ] + + for (i = 0; i < intPropertyNames.length; ++i) { + propertyName = intPropertyNames[i] + + buttonPropertyValue = 20 + buttonPropertyValue2 = 21 + actionPropertyValue = 40 + actionPropertyValue2 = 41 + var defaultValue = 0 + + data.push({ tag: "implicit " + propertyName, property: propertyName, + initButton: undefined, initAction: actionPropertyValue, + assignExpected: actionPropertyValue, assignChanged: true, + resetExpected: defaultValue, resetChanged: true }) + data.push({ tag: "explicit " + propertyName, property: propertyName, + initButton: buttonPropertyValue, initAction: actionPropertyValue, + assignExpected: buttonPropertyValue, assignChanged: false, + resetExpected: buttonPropertyValue, resetChanged: false }) + data.push({ tag: "default button " + propertyName, property: propertyName, + initButton: defaultValue, initAction: actionPropertyValue, + assignExpected: defaultValue, assignChanged: false, + resetExpected: defaultValue, resetChanged: false }) + data.push({ tag: "default action " + propertyName, property: propertyName, + initButton: buttonPropertyValue, initAction: defaultValue, + assignExpected: buttonPropertyValue, assignChanged: false, + resetExpected: buttonPropertyValue, resetChanged: false }) + data.push({ tag: "default both " + propertyName, property: propertyName, + initButton: undefined, initAction: defaultValue, + assignExpected: defaultValue, assignChanged: false, + resetExpected: defaultValue, resetChanged: false }) + data.push({ tag: "modify button " + propertyName, property: propertyName, + initButton: undefined, initAction: actionPropertyValue, + assignExpected: actionPropertyValue, assignChanged: true, + modifyButton: buttonPropertyValue2, + modifyButtonExpected: buttonPropertyValue2, modifyButtonChanged: true, + resetExpected: buttonPropertyValue2, resetChanged: false }) + data.push({ tag: "modify implicit action " + propertyName, property: propertyName, + initButton: undefined, initAction: actionPropertyValue, + assignExpected: actionPropertyValue, assignChanged: true, + modifyAction: actionPropertyValue2, + modifyActionExpected: actionPropertyValue2, modifyActionChanged: true, + resetExpected: defaultValue, resetChanged: true }) + data.push({ tag: "modify explicit action " + propertyName, property: propertyName, + initButton: buttonPropertyValue, initAction: actionPropertyValue, + assignExpected: buttonPropertyValue, assignChanged: false, + modifyAction: actionPropertyValue2, + modifyActionExpected: buttonPropertyValue, modifyActionChanged: false, + resetExpected: buttonPropertyValue, resetChanged: false }) + } + + propertyName = "color" + buttonPropertyValue = "#aa0000" + buttonPropertyValue2 = "#ff0000" + actionPropertyValue = "#0000aa" + actionPropertyValue2 = "#0000ff" + defaultValue = "#00000000" + + data.push({ tag: "implicit " + propertyName, property: propertyName, + initButton: undefined, initAction: actionPropertyValue, + assignExpected: actionPropertyValue, assignChanged: true, + resetExpected: defaultValue, resetChanged: true }) + data.push({ tag: "explicit " + propertyName, property: propertyName, + initButton: buttonPropertyValue, initAction: actionPropertyValue, + assignExpected: buttonPropertyValue, assignChanged: false, + resetExpected: buttonPropertyValue, resetChanged: false }) + data.push({ tag: "default button " + propertyName, property: propertyName, + initButton: defaultValue, initAction: actionPropertyValue, + assignExpected: defaultValue, assignChanged: false, + resetExpected: defaultValue, resetChanged: false }) + data.push({ tag: "default action " + propertyName, property: propertyName, + initButton: buttonPropertyValue, initAction: defaultValue, + assignExpected: buttonPropertyValue, assignChanged: false, + resetExpected: buttonPropertyValue, resetChanged: false }) + data.push({ tag: "default both " + propertyName, property: propertyName, + initButton: undefined, initAction: defaultValue, + assignExpected: defaultValue, assignChanged: false, + resetExpected: defaultValue, resetChanged: false }) + data.push({ tag: "modify button " + propertyName, property: propertyName, + initButton: undefined, initAction: actionPropertyValue, + assignExpected: actionPropertyValue, assignChanged: true, + modifyButton: buttonPropertyValue2, + modifyButtonExpected: buttonPropertyValue2, modifyButtonChanged: true, + resetExpected: buttonPropertyValue2, resetChanged: false }) + data.push({ tag: "modify implicit action " + propertyName, property: propertyName, + initButton: undefined, initAction: actionPropertyValue, + assignExpected: actionPropertyValue, assignChanged: true, + modifyAction: actionPropertyValue2, + modifyActionExpected: actionPropertyValue2, modifyActionChanged: true, + resetExpected: defaultValue, resetChanged: true }) + data.push({ tag: "modify explicit action " + propertyName, property: propertyName, + initButton: buttonPropertyValue, initAction: actionPropertyValue, + assignExpected: buttonPropertyValue, assignChanged: false, + modifyAction: actionPropertyValue2, + modifyActionExpected: buttonPropertyValue, modifyActionChanged: false, + resetExpected: buttonPropertyValue, resetChanged: false }) + + return data; + } + + function test_actionIcon(data) { + var control = createTemporaryObject(button, testCase) + verify(control) + control.icon[data.property] = data.initButton + + var act = action.createObject(control) + act.icon[data.property] = data.initAction + + var spy = signalSpy.createObject(control, {target: control, signalName: "iconChanged"}) + verify(spy.valid) + + // assign action + spy.clear() + control.action = act + compare(control.icon[data.property], data.assignExpected) + compare(spy.count, data.assignChanged ? 1 : 0) + + // modify button + if (data.hasOwnProperty("modifyButton")) { + spy.clear() + control.icon[data.property] = data.modifyButton + compare(control.icon[data.property], data.modifyButtonExpected) + compare(spy.count, data.modifyButtonChanged ? 1 : 0) + } + + // modify action + if (data.hasOwnProperty("modifyAction")) { + spy.clear() + act.icon[data.property] = data.modifyAction + compare(control.icon[data.property], data.modifyActionExpected) + compare(spy.count, data.modifyActionChanged ? 1 : 0) + } + + // reset action + spy.clear() + control.action = null + compare(control.icon[data.property], data.resetExpected) + compare(spy.count, data.resetChanged ? 1 : 0) + } + + Component { + id: actionButton + AbstractButton { + width: 100 + height: 50 + action: Action { + text: "Default" + icon.name: checked ? "checked" : "unchecked" + icon.source: "qrc:/icons/default.png" + checkable: true + checked: true + enabled: false + } + } + } + + function test_actionButton() { + var control = createTemporaryObject(actionButton, testCase) + verify(control) + + // initial values + compare(control.text, "Default") + compare(control.checkable, true) + compare(control.checked, true) + compare(control.enabled, false) + compare(control.icon.name, "checked") + + var textSpy = signalSpy.createObject(control, { target: control, signalName: "textChanged" }) + verify(textSpy.valid) + + // changes via action + control.action.text = "Action" + control.action.checkable = false + control.action.checked = false + control.action.enabled = true + compare(control.text, "Action") // propagates + compare(control.checkable, false) // propagates + compare(control.checked, false) // propagates + compare(control.enabled, true) // propagates + compare(control.icon.name, "unchecked") // propagates + compare(textSpy.count, 1) + + // changes via button + control.text = "Button" + control.checkable = true + control.checked = true + control.enabled = false + control.icon.name = "default" + compare(control.text, "Button") + compare(control.checkable, true) + compare(control.checked, true) + compare(control.enabled, false) + compare(control.icon.name, "default") + compare(control.action.text, "Action") // does NOT propagate + compare(control.action.checkable, true) // propagates + compare(control.action.checked, true) // propagates + compare(control.action.enabled, true) // does NOT propagate + compare(control.action.icon.name, control.action.checked ? "checked" : "unchecked") // does NOT propagate + compare(textSpy.count, 2) + + // remove the action so that only the button's properties are left + control.action = null + compare(control.text, "Button") + compare(control.icon.name, "default") + compare(textSpy.count, 2) + + // setting an action while button has a particular property set + // shouldn't cause a change in the button's effective property value + var secondAction = createTemporaryObject(action, testCase) + verify(secondAction) + secondAction.text = "SecondAction" + control.action = secondAction + compare(control.text, "Button") + compare(textSpy.count, 2) + + // test setting an action whose properties aren't set + var thirdAction = createTemporaryObject(action, testCase) + verify(thirdAction) + control.action = thirdAction + compare(control.text, "Button") + compare(textSpy.count, 2) + } + + Component { + id: checkableButton + AbstractButton { + width: 100 + height: 50 + checkable: true + action: Action {} + } + } + + function test_checkable_button() { + var control = createTemporaryObject(checkableButton, testCase) + verify(control) + control.checked = false + control.forceActiveFocus() + verify(control.activeFocus) + verify(!control.checked) + verify(!control.action.checked) + + keyPress(Qt.Key_Space) + keyRelease(Qt.Key_Space) + + compare(control.action.checked, true) + compare(control.checked, true) + + keyPress(Qt.Key_Space) + + compare(control.action.checked, true) + compare(control.checked, true) + + keyRelease(Qt.Key_Space) + + compare(control.action.checked, false) + compare(control.checked, false) + + var checkedSpy = signalSpy.createObject(control, {target: control.action, signalName: "checkedChanged"}) + var toggledSpy = signalSpy.createObject(control, {target: control, signalName: "toggled"}) + var actionToggledSpy = signalSpy.createObject(control, {target: control.action, signalName: "toggled"}) + + verify(checkedSpy.valid) + verify(toggledSpy.valid) + verify(actionToggledSpy.valid) + + mousePress(control) + + compare(control.action.checked, false) + compare(control.checked, false) + + mouseRelease(control) + + checkedSpy.wait() + compare(checkedSpy.count, 1) + compare(actionToggledSpy.count, 1) + compare(toggledSpy.count, 1) + + compare(control.action.checked, true) + compare(control.checked, true) + + mousePress(control) + mouseRelease(control) + + compare(control.checked, false) + compare(control.action.checked, false) + } + + function test_trigger_data() { + return [ + {tag: "click", click: true, button: true, action: true, clicked: true, triggered: true}, + {tag: "click disabled button", click: true, button: false, action: true, clicked: false, triggered: false}, + {tag: "click disabled action", click: true, button: true, action: false, clicked: true, triggered: false}, + {tag: "trigger", trigger: true, button: true, action: true, clicked: true, triggered: true}, + {tag: "trigger disabled button", trigger: true, button: false, action: true, clicked: false, triggered: true}, + {tag: "trigger disabled action", trigger: true, button: true, action: false, clicked: false, triggered: false} + ] + } + + function test_trigger(data) { + var control = createTemporaryObject(actionButton, testCase, {"action.enabled": data.action, "enabled": data.button}) + verify(control) + + compare(control.enabled, data.button) + compare(control.action.enabled, data.action) + + var buttonSpy = signalSpy.createObject(control, {target: control, signalName: "clicked"}) + verify(buttonSpy.valid) + + var actionSpy = signalSpy.createObject(control, {target: control.action, signalName: "triggered"}) + verify(actionSpy.valid) + + if (data.click) + mouseClick(control) + else if (data.trigger) + control.action.trigger() + + compare(buttonSpy.count, data.clicked ? 1 : 0) + compare(actionSpy.count, data.triggered ? 1 : 0) + } + + function test_mnemonic() { + if (Qt.platform.os === "osx" || Qt.platform.os === "macos") + skip("Mnemonics are not used on macOS") + + var control = createTemporaryObject(button, testCase) + verify(control) + + control.text = "&Hello" + compare(control.text, "&Hello") + + var clickSpy = signalSpy.createObject(control, {target: control, signalName: "clicked"}) + verify(clickSpy.valid) + + keyClick(Qt.Key_H, Qt.AltModifier) + compare(clickSpy.count, 1) + + control.visible = false + keyClick(Qt.Key_H, Qt.AltModifier) + compare(clickSpy.count, 1) + + control.visible = true + keyClick(Qt.Key_H, Qt.AltModifier) + compare(clickSpy.count, 2) + + control.text = "Te&st" + compare(control.text, "Te&st") + + keyClick(Qt.Key_H, Qt.AltModifier) + compare(clickSpy.count, 2) + + keyClick(Qt.Key_S, Qt.AltModifier) + compare(clickSpy.count, 3) + + control.visible = false + control.text = "&Hidden" + keyClick(Qt.Key_H, Qt.AltModifier) + compare(clickSpy.count, 3) + + control.visible = true + keyClick(Qt.Key_H, Qt.AltModifier) + compare(clickSpy.count, 4) + + control.text = undefined + control.action = action.createObject(control, {text: "&Action"}) + + var actionSpy = signalSpy.createObject(control, {target: control.action, signalName: "triggered"}) + verify(actionSpy.valid) + + keyClick(Qt.Key_A, Qt.AltModifier) + compare(actionSpy.count, 1) + compare(clickSpy.count, 5) + + // ungrab on destruction (don't crash) + control.Component.onDestruction.connect(function() { control = null }) + control.destroy() + wait(0) + verify(!control) + keyClick(Qt.Key_H, Qt.AltModifier) + } + + Component { + id: actionGroup + ActionGroup { + Action { id: action1; checkable: true; checked: true } + Action { id: action2; checkable: true } + Action { id: action3; checkable: true } + } + } + + function test_actionGroup() { + var group = createTemporaryObject(actionGroup, testCase) + verify(group) + + var button1 = createTemporaryObject(button, testCase, {action: group.actions[0], width: 10, height: 10}) + var button2 = createTemporaryObject(button, testCase, {action: group.actions[1], width: 10, height: 10, y: 10}) + var button3 = createTemporaryObject(button, testCase, {action: group.actions[2], width: 10, height: 10, y: 20}) + + verify(button1) + compare(button1.checked, true) + compare(button1.action.checked, true) + + verify(button2) + compare(button2.checked, false) + compare(button2.action.checked, false) + + verify(button3) + compare(button3.checked, false) + compare(button3.action.checked, false) + + mouseClick(button2) + + compare(button1.checked, false) + compare(button1.action.checked, false) + + compare(button2.checked, true) + compare(button2.action.checked, true) + + compare(button3.checked, false) + compare(button3.action.checked, false) + } + + function test_clickedAfterLongPress() { + var control = createTemporaryObject(button, testCase, { text: "Hello" }) + verify(control) + + var clickedSpy = signalSpy.createObject(control, { target: control, signalName: "clicked" }) + verify(clickedSpy.valid) + + mousePress(control) + // Ensure that clicked is emitted when no handler is defined for the pressAndHold() signal. + // Note that even though signal spies aren't considered in QObject::isSignalConnected(), + // we can't use one here to check for pressAndHold(), because otherwise clicked() won't be emitted. + wait(Qt.styleHints.mousePressAndHoldInterval + 100) + mouseRelease(control) + compare(clickedSpy.count, 1) + } + + function test_doubleClick() { + let control = createTemporaryObject(button, testCase, { text: "Hello" }) + verify(control) + + let pressedSpy = signalSpy.createObject(control, { target: control, signalName: "pressed" }) + verify(pressedSpy.valid) + + let releasedSpy = signalSpy.createObject(control, { target: control, signalName: "released" }) + verify(releasedSpy.valid) + + let clickedSpy = signalSpy.createObject(control, { target: control, signalName: "clicked" }) + verify(clickedSpy.valid) + + let doubleClickedSpy = signalSpy.createObject(control, { target: control, signalName: "doubleClicked" }) + verify(doubleClickedSpy.valid) + + mouseDoubleClickSequence(control) + compare(pressedSpy.count, 2) + compare(releasedSpy.count, 2) + compare(clickedSpy.count, 1) + compare(doubleClickedSpy.count, 1) + + let touch = touchEvent(control) + touch.press(0, control) + touch.commit() + compare(pressedSpy.count, 3) + compare(releasedSpy.count, 2) + compare(clickedSpy.count, 1) + compare(doubleClickedSpy.count, 1) + + touch.release(0, control) + touch.commit() + compare(pressedSpy.count, 3) + compare(releasedSpy.count, 3) + compare(clickedSpy.count, 2) + compare(doubleClickedSpy.count, 1) + + touch.press(0, control) + touch.commit() + compare(pressedSpy.count, 4) + compare(releasedSpy.count, 3) + compare(clickedSpy.count, 2) + compare(doubleClickedSpy.count, 1) + + touch.release(0, control) + touch.commit() + compare(pressedSpy.count, 4) + compare(releasedSpy.count, 4) + compare(clickedSpy.count, 2) + compare(doubleClickedSpy.count, 2) + } + + function test_checkedShouldNotSetCheckable() { + let control = createTemporaryObject(button, testCase, { checked: true }) + verify(control) + + verify(!control.checkable) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_action.qml b/tests/auto/quickcontrols2/controls/data/tst_action.qml new file mode 100644 index 0000000000..eded64b119 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_action.qml @@ -0,0 +1,197 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls +import QtQuick.Templates as T + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "Action" + + Component { + id: component + Action { } + } + + Component { + id: signalSpy + SignalSpy { } + } + + function test_enabled() { + var action = createTemporaryObject(component, testCase) + verify(action) + + var spy = createTemporaryObject(signalSpy, testCase, {target: action, signalName: "triggered"}) + verify(spy.valid) + + action.trigger() + compare(spy.count, 1) + + action.enabled = false + action.trigger() + compare(spy.count, 1) + + action.enabled = undefined // reset + action.trigger() + compare(spy.count, 2) + } + + Component { + id: buttonAndMenu + Item { + property alias button: button + property alias menu: menu + property alias menuItem: menuItem + property alias action: sharedAction + property var lastSource + Action { + id: sharedAction + text: "Shared" + shortcut: "Ctrl+B" + onTriggered: (source) => lastSource = source + } + Button { + id: button + action: sharedAction + Menu { + id: menu + MenuItem { + id: menuItem + action: sharedAction + } + } + } + } + } + + function test_shared() { + var container = createTemporaryObject(buttonAndMenu, testCase) + verify(container) + + keyClick(Qt.Key_B, Qt.ControlModifier) + compare(container.lastSource, container.button) + + container.menu.open() + keyClick(Qt.Key_B, Qt.ControlModifier) + compare(container.lastSource, container.menuItem) + + tryVerify(function() { return !container.menu.visible }) + keyClick(Qt.Key_B, Qt.ControlModifier) + compare(container.lastSource, container.button) + + container.button.visible = false + keyClick(Qt.Key_B, Qt.ControlModifier) + compare(container.lastSource, container.action) + } + + Component { + id: actionAndRepeater + Item { + property alias action: testAction + Action { + id: testAction + shortcut: "Ctrl+A" + } + Repeater { + model: 1 + Button { + action: testAction + } + } + } + } + + function test_repeater() { + var container = createTemporaryObject(actionAndRepeater, testCase) + verify(container) + + var spy = signalSpy.createObject(container, {target: container.action, signalName: "triggered"}) + verify(spy.valid) + + keyClick(Qt.Key_A, Qt.ControlModifier) + compare(spy.count, 1) + } + + Component { + id: shortcutBinding + Item { + Action { + id: action + shortcut: StandardKey.Copy + } + + Shortcut { + id: indirectShortcut + sequences: [ action.shortcut ] + } + + Shortcut { + id: directShortcut + sequences: [ StandardKey.Copy ] + } + + property alias indirect: indirectShortcut; + property alias direct: directShortcut + } + } + + function test_shortcutBinding() { + var container = createTemporaryObject(shortcutBinding, testCase); + verify(container) + compare(container.indirect.nativeText, container.direct.nativeText); + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_actiongroup.qml b/tests/auto/quickcontrols2/controls/data/tst_actiongroup.qml new file mode 100644 index 0000000000..e4ea5679da --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_actiongroup.qml @@ -0,0 +1,400 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 200 + height: 200 + visible: true + when: windowShown + name: "ActionGroup" + + Component { + id: actionGroup + ActionGroup { } + } + + Component { + id: nonExclusiveGroup + ActionGroup { exclusive: false } + } + + Component { + id: declarativeGroup + ActionGroup { + Action { text: "First" } + Action { text: "Second" } + Action { text: "Third" } + } + } + + Component { + id: signalSpy + SignalSpy { } + } + + function test_null() { + var group = createTemporaryObject(actionGroup, testCase) + verify(group) + + group.addAction(null) + group.removeAction(null) + } + + Component { + id: action + Action { } + } + + function test_defaults() { + var group = createTemporaryObject(actionGroup, testCase) + verify(group) + compare(group.actions.length, 0) + compare(group.checkedAction, null) + compare(group.exclusive, true) + } + + function test_current() { + var group = createTemporaryObject(actionGroup, testCase) + verify(group) + + var checkedActionSpy = createTemporaryObject(signalSpy, testCase, {target: group, signalName: "checkedActionChanged"}) + verify(checkedActionSpy.valid) + verify(!group.checkedAction) + + var action1 = createTemporaryObject(action, testCase, {checked: true}) + var action2 = createTemporaryObject(action, testCase, {checked: false}) + var action3 = createTemporaryObject(action, testCase, {checked: true, objectName: "3"}) + + // add checked + group.addAction(action1) + compare(group.checkedAction, action1) + compare(action1.checked, true) + compare(action2.checked, false) + compare(action3.checked, true) + compare(checkedActionSpy.count, 1) + + // add non-checked + group.addAction(action2) + compare(group.checkedAction, action1) + compare(action1.checked, true) + compare(action2.checked, false) + compare(action3.checked, true) + compare(checkedActionSpy.count, 1) + + // add checked + group.addAction(action3) + compare(group.checkedAction, action3) + compare(action1.checked, false) + compare(action2.checked, false) + compare(action3.checked, true) + compare(checkedActionSpy.count, 2) + + // change current + group.checkedAction = action2 + compare(group.checkedAction, action2) + compare(action1.checked, false) + compare(action2.checked, true) + compare(action3.checked, false) + compare(checkedActionSpy.count, 3) + + // check + action1.checked = true + compare(group.checkedAction, action1) + compare(action1.checked, true) + compare(action2.checked, false) + compare(action3.checked, false) + compare(checkedActionSpy.count, 4) + + // remove non-checked + group.removeAction(action2) + compare(group.checkedAction, action1) + compare(action1.checked, true) + compare(action2.checked, false) + compare(action3.checked, false) + compare(checkedActionSpy.count, 4) + + // remove checked + group.removeAction(action1) + verify(!group.checkedAction) + compare(action1.checked, false) + compare(action2.checked, false) + compare(action3.checked, false) + compare(checkedActionSpy.count, 5) + } + + function test_actions() { + var group = createTemporaryObject(actionGroup, testCase) + verify(group) + + var actionsSpy = createTemporaryObject(signalSpy, testCase, {target: group, signalName: "actionsChanged"}) + verify(actionsSpy.valid) + + compare(group.actions.length, 0) + compare(group.checkedAction, null) + + var action1 = createTemporaryObject(action, testCase, {checked: true}) + var action2 = createTemporaryObject(action, testCase, {checked: false}) + + group.actions = [action1, action2] + compare(group.actions.length, 2) + compare(group.actions[0], action1) + compare(group.actions[1], action2) + compare(group.checkedAction, action1) + compare(actionsSpy.count, 2) + + var action3 = createTemporaryObject(action, testCase, {checked: true}) + + group.addAction(action3) + compare(group.actions.length, 3) + compare(group.actions[0], action1) + compare(group.actions[1], action2) + compare(group.actions[2], action3) + compare(group.checkedAction, action3) + compare(actionsSpy.count, 3) + + group.removeAction(action1) + compare(group.actions.length, 2) + compare(group.actions[0], action2) + compare(group.actions[1], action3) + compare(group.checkedAction, action3) + compare(actionsSpy.count, 4) + + group.actions = [] + compare(group.actions.length, 0) + tryCompare(group, "checkedAction", null) + compare(actionsSpy.count, 5) + } + + function test_declarative() { + var group = createTemporaryObject(declarativeGroup, testCase) + verify(group) + + compare(group.actions.length, 3) + compare(group.actions[0].text, "First") + compare(group.actions[1].text, "Second") + compare(group.actions[2].text, "Third") + } + + function test_triggered_data() { + return [ + {tag: "exclusive", exclusive: true}, + {tag: "non-exclusive", exclusive: false} + ] + } + + function test_triggered(data) { + var group = createTemporaryObject(actionGroup, testCase, {exclusive: data.exclusive}) + verify(group) + + var triggeredSpy = createTemporaryObject(signalSpy, testCase, {target: group, signalName: "triggered"}) + verify(triggeredSpy.valid) + + var action1 = createTemporaryObject(action, testCase) + var action2 = createTemporaryObject(action, testCase) + + group.addAction(action1) + group.addAction(action2) + + action1.triggered() + compare(triggeredSpy.count, 1) + compare(triggeredSpy.signalArguments[0][0], action1) + + action2.triggered() + compare(triggeredSpy.count, 2) + compare(triggeredSpy.signalArguments[1][0], action2) + } + + Component { + id: attachedGroup + Item { + property ActionGroup group: ActionGroup { id: group } + property Action action1: Action { ActionGroup.group: group } + property Action action2: Action { ActionGroup.group: group } + property Action action3: Action { ActionGroup.group: group } + } + } + + function test_attached() { + var container = createTemporaryObject(attachedGroup, testCase) + verify(container) + + verify(!container.group.checkedAction) + + container.action1.checked = true + compare(container.group.checkedAction, container.action1) + compare(container.action1.checked, true) + compare(container.action2.checked, false) + compare(container.action3.checked, false) + + container.action2.checked = true + compare(container.group.checkedAction, container.action2) + compare(container.action1.checked, false) + compare(container.action2.checked, true) + compare(container.action3.checked, false) + + container.action3.checked = true + compare(container.group.checkedAction, container.action3) + compare(container.action1.checked, false) + compare(container.action2.checked, false) + compare(container.action3.checked, true) + } + + function test_actionDestroyed() { + var group = createTemporaryObject(actionGroup, testCase) + verify(group) + + var actionsSpy = createTemporaryObject(signalSpy, testCase, {target: group, signalName: "actionsChanged"}) + verify(actionsSpy.valid) + + var action1 = createTemporaryObject(action, testCase, {objectName: "action1", checked: true}) + + group.addAction(action1) + compare(group.actions.length, 1) + compare(group.actions[0], action1) + compare(group.checkedAction, action1) + compare(actionsSpy.count, 1) + + action1.destroy() + wait(0) + compare(group.actions.length, 0) + compare(group.checkedAction, null) + compare(actionsSpy.count, 2) + } + + function test_nonExclusive() { + var group = createTemporaryObject(nonExclusiveGroup, testCase) + verify(group) + + var action1 = createTemporaryObject(action, testCase, {checked: true}) + group.addAction(action1) + compare(action1.checked, true) + compare(group.checkedAction, null) + + var action2 = createTemporaryObject(action, testCase, {checked: true}) + group.addAction(action2) + compare(action1.checked, true) + compare(action2.checked, true) + compare(group.checkedAction, null) + + action1.checked = false + compare(action1.checked, false) + compare(action2.checked, true) + compare(group.checkedAction, null) + + action2.checked = false + compare(action1.checked, false) + compare(action2.checked, false) + compare(group.checkedAction, null) + + action1.checked = true + compare(action1.checked, true) + compare(action2.checked, false) + compare(group.checkedAction, null) + + action2.checked = true + compare(action1.checked, true) + compare(action2.checked, true) + compare(group.checkedAction, null) + } + + function test_enabled() { + var group = createTemporaryObject(actionGroup, testCase) + verify(group) + + compare(group.enabled, true) + + var action1 = createTemporaryObject(action, testCase) + var action2 = createTemporaryObject(action, testCase) + compare(action1.enabled, true) + compare(action2.enabled, true) + + var action1Spy = createTemporaryObject(signalSpy, testCase, {target: action1, signalName: "enabledChanged"}) + var action2Spy = createTemporaryObject(signalSpy, testCase, {target: action2, signalName: "enabledChanged"}) + verify(action1Spy.valid && action2Spy.valid) + + group.addAction(action1) + compare(action1.enabled, true) + compare(action2.enabled, true) + compare(action1Spy.count, 0) + compare(action2Spy.count, 0) + + group.enabled = false + compare(action1.enabled, false) + compare(action2.enabled, true) + compare(action1Spy.count, 1) + compare(action1Spy.signalArguments[0][0], false) + compare(action2Spy.count, 0) + + group.addAction(action2) + compare(action1.enabled, false) + compare(action2.enabled, false) + compare(action1Spy.count, 1) + compare(action2Spy.count, 1) + compare(action2Spy.signalArguments[0][0], false) + + action1.enabled = false + compare(action1.enabled, false) + compare(action1Spy.count, 2) + compare(action1Spy.signalArguments[1][0], false) + compare(action2Spy.count, 1) + + group.enabled = true + compare(action1.enabled, false) + compare(action2.enabled, true) + compare(action1Spy.count, 2) + compare(action2Spy.count, 2) + compare(action2Spy.signalArguments[1][0], true) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_busyindicator.qml b/tests/auto/quickcontrols2/controls/data/tst_busyindicator.qml new file mode 100644 index 0000000000..9353c33828 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_busyindicator.qml @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "BusyIndicator" + + Component { + id: busyIndicator + BusyIndicator { } + } + + Component { + id: mouseArea + MouseArea { } + } + + function test_running() { + var control = createTemporaryObject(busyIndicator, testCase) + verify(control) + + compare(control.running, true) + control.running = false + compare(control.running, false) + } + + // QTBUG-61785 + function test_mouseArea() { + var ma = createTemporaryObject(mouseArea, testCase, {width: testCase.width, height: testCase.height}) + verify(ma) + + var control = busyIndicator.createObject(ma, {width: testCase.width, height: testCase.height}) + verify(control) + + mousePress(control) + verify(ma.pressed) + + mouseRelease(control) + verify(!ma.pressed) + + var touch = touchEvent(control) + touch.press(0, control).commit() + verify(ma.pressed) + + touch.release(0, control).commit() + verify(!ma.pressed) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_button.qml b/tests/auto/quickcontrols2/controls/data/tst_button.qml new file mode 100644 index 0000000000..a6ec4faad9 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_button.qml @@ -0,0 +1,511 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 200 + height: 200 + visible: true + when: windowShown + name: "Button" + + Component { + id: button + Button { } + } + + Component { + id: signalSequenceSpy + SignalSequenceSpy { + signals: ["pressed", "released", "canceled", "clicked", "toggled", "doubleClicked", "pressedChanged", "downChanged", "checkedChanged"] + } + } + + Component { + id: signalSpy + SignalSpy { } + } + + function test_text() { + var control = createTemporaryObject(button, testCase) + verify(control) + + compare(control.text, "") + control.text = "Button" + compare(control.text, "Button") + control.text = "" + compare(control.text, "") + } + + function test_mouse() { + var control = createTemporaryObject(button, testCase) + verify(control) + + var sequenceSpy = signalSequenceSpy.createObject(control, {target: control}) + + // click + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], + ["downChanged", { "down": true }], + "pressed"] + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.pressed, true) + verify(sequenceSpy.success) + + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false }], + ["downChanged", { "down": false }], + "released", + "clicked"] + mouseRelease(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.pressed, false) + verify(sequenceSpy.success) + + // release outside + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], + ["downChanged", { "down": true }], + "pressed"] + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.pressed, true) + verify(sequenceSpy.success) + + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false }], + ["downChanged", { "down": false }]] + mouseMove(control, control.width * 2, control.height * 2, 0) + compare(control.pressed, false) + verify(sequenceSpy.success) + + sequenceSpy.expectedSequence = [["canceled", { "pressed": false }]] + mouseRelease(control, control.width * 2, control.height * 2, Qt.LeftButton) + compare(control.pressed, false) + verify(sequenceSpy.success) + + // right button + sequenceSpy.expectedSequence = [] + mousePress(control, control.width / 2, control.height / 2, Qt.RightButton) + compare(control.pressed, false) + + mouseRelease(control, control.width / 2, control.height / 2, Qt.RightButton) + compare(control.pressed, false) + verify(sequenceSpy.success) + + // double click + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], + ["downChanged", { "down": true }], + "pressed", + ["pressedChanged", { "pressed": false }], + ["downChanged", { "down": false }], + "released", + "clicked", + ["pressedChanged", { "pressed": true }], + ["downChanged", { "down": true }], + "pressed", + "doubleClicked", + ["pressedChanged", { "pressed": false }], + ["downChanged", { "down": false }], + "released"] + mouseDoubleClickSequence(control, control.width / 2, control.height / 2, Qt.LeftButton) + verify(sequenceSpy.success) + } + + function test_touch() { + var control = createTemporaryObject(button, testCase) + verify(control) + + var touch = touchEvent(control) + var sequenceSpy = signalSequenceSpy.createObject(control, {target: control}) + + // click + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], + ["downChanged", { "down": true }], + "pressed"] + touch.press(0, control, control.width / 2, control.height / 2).commit() + compare(control.pressed, true) + verify(sequenceSpy.success) + + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false }], + ["downChanged", { "down": false }], + "released", + "clicked"] + touch.release(0, control, control.width / 2, control.height / 2).commit() + compare(control.pressed, false) + verify(sequenceSpy.success) + + // release outside + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], + ["downChanged", { "down": true }], + "pressed"] + touch.press(0, control, control.width / 2, control.height / 2).commit() + compare(control.pressed, true) + verify(sequenceSpy.success) + + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false }], + ["downChanged", { "down": false }]] + touch.move(0, control, control.width * 2, control.height * 2).commit() + compare(control.pressed, false) + verify(sequenceSpy.success) + + sequenceSpy.expectedSequence = [["canceled", { "pressed": false }]] + touch.release(0, control, control.width * 2, control.height * 2).commit() + compare(control.pressed, false) + verify(sequenceSpy.success) + } + + function test_multiTouch() { + var control1 = createTemporaryObject(button, testCase) + verify(control1) + + var pressedCount1 = 0 + + var pressedSpy1 = signalSpy.createObject(control1, {target: control1, signalName: "pressedChanged"}) + verify(pressedSpy1.valid) + + var touch = touchEvent(control1) + touch.press(0, control1, 0, 0).commit().move(0, control1, control1.width - 1, control1.height - 1).commit() + + compare(pressedSpy1.count, ++pressedCount1) + compare(control1.pressed, true) + + // second touch point on the same control is ignored + touch.stationary(0).press(1, control1, 0, 0).commit() + touch.stationary(0).move(1, control1).commit() + touch.stationary(0).release(1).commit() + + compare(pressedSpy1.count, pressedCount1) + compare(control1.pressed, true) + + var control2 = createTemporaryObject(button, testCase, {y: control1.height}) + verify(control2) + + var pressedCount2 = 0 + + var pressedSpy2 = signalSpy.createObject(control2, {target: control2, signalName: "pressedChanged"}) + verify(pressedSpy2.valid) + + // press the second button + touch.stationary(0).press(2, control2, 0, 0).commit() + + compare(pressedSpy2.count, ++pressedCount2) + compare(control2.pressed, true) + + compare(pressedSpy1.count, pressedCount1) + compare(control1.pressed, true) + + // release both buttons + touch.release(0, control1).release(2, control2).commit() + + compare(pressedSpy2.count, ++pressedCount2) + compare(control2.pressed, false) + + compare(pressedSpy1.count, ++pressedCount1) + compare(control1.pressed, false) + } + + function test_keys() { + var control = createTemporaryObject(button, testCase) + verify(control) + + control.forceActiveFocus() + verify(control.activeFocus) + + var sequenceSpy = signalSequenceSpy.createObject(control, {target: control}) + + // click + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], + ["downChanged", { "down": true }], + "pressed", + ["pressedChanged", { "pressed": false }], + ["downChanged", { "down": false }], + "released", + "clicked"] + keyClick(Qt.Key_Space) + verify(sequenceSpy.success) + + // no change + sequenceSpy.expectedSequence = [] + var keys = [Qt.Key_Enter, Qt.Key_Return, Qt.Key_Escape, Qt.Key_Tab] + for (var i = 0; i < keys.length; ++i) { + sequenceSpy.reset() + keyClick(keys[i]) + verify(sequenceSpy.success) + } + } + + function eventErrorMessage(actual, expected) { + return "actual event:" + JSON.stringify(actual) + ", expected event:" + JSON.stringify(expected) + } + + function test_autoRepeat() { + var control = createTemporaryObject(button, testCase) + verify(control) + + compare(control.autoRepeat, false) + control.autoRepeat = true + compare(control.autoRepeat, true) + + control.forceActiveFocus() + verify(control.activeFocus) + + var clickSpy = signalSpy.createObject(control, {target: control, signalName: "clicked"}) + verify(clickSpy.valid) + var pressSpy = signalSpy.createObject(control, {target: control, signalName: "pressed"}) + verify(pressSpy.valid) + var releaseSpy = signalSpy.createObject(control, {target: control, signalName: "released"}) + verify(releaseSpy.valid) + + // auto-repeat mouse click + mousePress(control) + compare(control.pressed, true) + clickSpy.wait() + clickSpy.wait() + compare(pressSpy.count, clickSpy.count + 1) + compare(releaseSpy.count, clickSpy.count) + mouseRelease(control) + compare(control.pressed, false) + compare(clickSpy.count, pressSpy.count) + compare(releaseSpy.count, pressSpy.count) + + clickSpy.clear() + pressSpy.clear() + releaseSpy.clear() + + // auto-repeat key click + keyPress(Qt.Key_Space) + compare(control.pressed, true) + clickSpy.wait() + clickSpy.wait() + compare(pressSpy.count, clickSpy.count + 1) + compare(releaseSpy.count, clickSpy.count) + keyRelease(Qt.Key_Space) + compare(control.pressed, false) + compare(clickSpy.count, pressSpy.count) + compare(releaseSpy.count, pressSpy.count) + + clickSpy.clear() + pressSpy.clear() + releaseSpy.clear() + + mousePress(control) + compare(control.pressed, true) + clickSpy.wait() + compare(pressSpy.count, clickSpy.count + 1) + compare(releaseSpy.count, clickSpy.count) + + // move inside during repeat -> continue repeat + mouseMove(control, control.width / 4, control.height / 4) + clickSpy.wait() + compare(pressSpy.count, clickSpy.count + 1) + compare(releaseSpy.count, clickSpy.count) + + clickSpy.clear() + pressSpy.clear() + releaseSpy.clear() + + // move outside during repeat -> stop repeat + mouseMove(control, -1, -1) + // NOTE: The following wait() is NOT a reliable way to test that the + // auto-repeat timer is not running, but there's no way dig into the + // private APIs from QML. If this test ever fails in the future, it + // indicates that the auto-repeat timer logic is broken. + wait(125) + compare(clickSpy.count, 0) + compare(pressSpy.count, 0) + compare(releaseSpy.count, 0) + + mouseRelease(control, -1, -1) + compare(control.pressed, false) + compare(clickSpy.count, 0) + compare(pressSpy.count, 0) + compare(releaseSpy.count, 0) + } + + function test_baseline() { + var control = createTemporaryObject(button, testCase) + verify(control) + compare(control.baselineOffset, control.contentItem.y + control.contentItem.baselineOffset) + } + + function test_checkable() { + var control = createTemporaryObject(button, testCase) + verify(control) + verify(control.hasOwnProperty("checkable")) + verify(!control.checkable) + + var sequenceSpy = signalSequenceSpy.createObject(control, {target: control}) + + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], + ["downChanged", { "down": true }], + "pressed", + ["pressedChanged", { "pressed": false }], + ["downChanged", { "down": false }], + "released", + "clicked"] + mouseClick(control) + verify(!control.checked) + verify(sequenceSpy.success) + + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], + ["downChanged", { "down": true }], + "pressed", + ["pressedChanged", { "pressed": false }], + ["downChanged", { "down": false }], + ["checkedChanged", { "checked": true }], + "toggled", + "released", + "clicked"] + control.checkable = true + mouseClick(control) + verify(control.checked) + verify(sequenceSpy.success) + + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], + ["downChanged", { "down": true }], + "pressed", + ["pressedChanged", { "pressed": false }], + ["downChanged", { "down": false }], + ["checkedChanged", { "checked": false }], + "toggled", + "released", + "clicked"] + mouseClick(control) + verify(!control.checked) + verify(sequenceSpy.success) + } + + function test_highlighted() { + var control = createTemporaryObject(button, testCase) + verify(control) + verify(!control.highlighted) + + control.highlighted = true + verify(control.highlighted) + } + + function test_spacing() { + var control = createTemporaryObject(button, testCase, { text: "Some long, long, long text" }) + verify(control) + verify(control.contentItem.implicitWidth + control.leftPadding + control.rightPadding > control.background.implicitWidth) + + var textLabel = findChild(control.contentItem, "label") + verify(textLabel) + + // The implicitWidth of the IconLabel that all buttons use as their contentItem + // should be equal to the implicitWidth of the Text while no icon is set. + compare(control.contentItem.implicitWidth, textLabel.implicitWidth) + + // That means that spacing shouldn't affect it. + control.spacing += 100 + compare(control.contentItem.implicitWidth, textLabel.implicitWidth) + + // The implicitWidth of the Button itself should, therefore, also never include spacing while no icon is set. + compare(control.implicitWidth, textLabel.implicitWidth + control.leftPadding + control.rightPadding) + } + + function test_display_data() { + return [ + { "tag": "IconOnly", display: Button.IconOnly }, + { "tag": "TextOnly", display: Button.TextOnly }, + { "tag": "TextUnderIcon", display: Button.TextUnderIcon }, + { "tag": "TextBesideIcon", display: Button.TextBesideIcon }, + { "tag": "IconOnly, mirrored", display: Button.IconOnly, mirrored: true }, + { "tag": "TextOnly, mirrored", display: Button.TextOnly, mirrored: true }, + { "tag": "TextUnderIcon, mirrored", display: Button.TextUnderIcon, mirrored: true }, + { "tag": "TextBesideIcon, mirrored", display: Button.TextBesideIcon, mirrored: true } + ] + } + + function test_display(data) { + var control = createTemporaryObject(button, testCase, { + text: "Button", + display: data.display, + "icon.source": "qrc:/qt-project.org/imports/QtQuick/Controls/Basic/images/check.png", + "LayoutMirroring.enabled": !!data.mirrored + }) + verify(control) + compare(control.icon.source, "qrc:/qt-project.org/imports/QtQuick/Controls/Basic/images/check.png") + + var iconImage = findChild(control.contentItem, "image") + var textLabel = findChild(control.contentItem, "label") + + switch (control.display) { + case Button.IconOnly: + verify(iconImage) + verify(!textLabel) + compare(iconImage.x, (control.availableWidth - iconImage.width) / 2) + compare(iconImage.y, (control.availableHeight - iconImage.height) / 2) + break; + case Button.TextOnly: + verify(!iconImage) + verify(textLabel) + compare(textLabel.x, (control.availableWidth - textLabel.width) / 2) + compare(textLabel.y, (control.availableHeight - textLabel.height) / 2) + break; + case Button.TextUnderIcon: + verify(iconImage) + verify(textLabel) + compare(iconImage.x, (control.availableWidth - iconImage.width) / 2) + compare(textLabel.x, (control.availableWidth - textLabel.width) / 2) + verify(iconImage.y < textLabel.y) + break; + case Button.TextBesideIcon: + verify(iconImage) + verify(textLabel) + if (control.mirrored) + verify(textLabel.x < iconImage.x) + else + verify(iconImage.x < textLabel.x) + compare(iconImage.y, (control.availableHeight - iconImage.height) / 2) + compare(textLabel.y, (control.availableHeight - textLabel.height) / 2) + break; + } + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_buttongroup.qml b/tests/auto/quickcontrols2/controls/data/tst_buttongroup.qml new file mode 100644 index 0000000000..138c0e2441 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_buttongroup.qml @@ -0,0 +1,465 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 200 + height: 200 + visible: true + when: windowShown + name: "ButtonGroup" + + Component { + id: buttonGroup + ButtonGroup { } + } + + Component { + id: nonExclusiveGroup + ButtonGroup { exclusive: false } + } + + Component { + id: signalSpy + SignalSpy { } + } + + function test_null() { + var group = createTemporaryObject(buttonGroup, testCase) + verify(group) + + group.addButton(null) + group.removeButton(null) + } + + Component { + id: button + Button { } + } + + Component { + id: nonCheckable + QtObject { } + } + + function test_defaults() { + var group = createTemporaryObject(buttonGroup, testCase) + verify(group) + compare(group.buttons.length, 0) + compare(group.checkedButton, null) + compare(group.exclusive, true) + compare(group.checkState, Qt.Unchecked) + } + + function test_current() { + var group = createTemporaryObject(buttonGroup, testCase) + verify(group) + + var checkedButtonSpy = createTemporaryObject(signalSpy, testCase, {target: group, signalName: "checkedButtonChanged"}) + verify(checkedButtonSpy.valid) + verify(!group.checkedButton) + + var button1 = createTemporaryObject(button, testCase, {checked: true}) + var button2 = createTemporaryObject(button, testCase, {checked: false}) + var button3 = createTemporaryObject(button, testCase, {checked: true, objectName: "3"}) + + // add checked + group.addButton(button1) + compare(group.checkedButton, button1) + compare(button1.checked, true) + compare(button2.checked, false) + compare(button3.checked, true) + compare(checkedButtonSpy.count, 1) + + // add non-checked + group.addButton(button2) + compare(group.checkedButton, button1) + compare(button1.checked, true) + compare(button2.checked, false) + compare(button3.checked, true) + compare(checkedButtonSpy.count, 1) + + // add checked + group.addButton(button3) + compare(group.checkedButton, button3) + compare(button1.checked, false) + compare(button2.checked, false) + compare(button3.checked, true) + compare(checkedButtonSpy.count, 2) + + // change current + group.checkedButton = button2 + compare(group.checkedButton, button2) + compare(button1.checked, false) + compare(button2.checked, true) + compare(button3.checked, false) + compare(checkedButtonSpy.count, 3) + + // check + button1.checked = true + compare(group.checkedButton, button1) + compare(button1.checked, true) + compare(button2.checked, false) + compare(button3.checked, false) + compare(checkedButtonSpy.count, 4) + + // remove non-checked + group.removeButton(button2) + compare(group.checkedButton, button1) + compare(button1.checked, true) + compare(button2.checked, false) + compare(button3.checked, false) + compare(checkedButtonSpy.count, 4) + + // remove checked + group.removeButton(button1) + verify(!group.checkedButton) + compare(button1.checked, false) + compare(button2.checked, false) + compare(button3.checked, false) + compare(checkedButtonSpy.count, 5) + } + + function test_buttons() { + var group = createTemporaryObject(buttonGroup, testCase) + verify(group) + + var buttonsSpy = createTemporaryObject(signalSpy, testCase, {target: group, signalName: "buttonsChanged"}) + verify(buttonsSpy.valid) + + compare(group.buttons.length, 0) + compare(group.checkedButton, null) + + var button1 = createTemporaryObject(button, testCase, {checked: true}) + var button2 = createTemporaryObject(button, testCase, {checked: false}) + + group.buttons = [button1, button2] + compare(group.buttons.length, 2) + compare(group.buttons[0], button1) + compare(group.buttons[1], button2) + compare(group.checkedButton, button1) + compare(buttonsSpy.count, 2) + + var button3 = createTemporaryObject(button, testCase, {checked: true}) + + group.addButton(button3) + compare(group.buttons.length, 3) + compare(group.buttons[0], button1) + compare(group.buttons[1], button2) + compare(group.buttons[2], button3) + compare(group.checkedButton, button3) + compare(buttonsSpy.count, 3) + + group.removeButton(button1) + compare(group.buttons.length, 2) + compare(group.buttons[0], button2) + compare(group.buttons[1], button3) + compare(group.checkedButton, button3) + compare(buttonsSpy.count, 4) + + group.buttons = [] + compare(group.buttons.length, 0) + tryCompare(group, "checkedButton", null) + compare(buttonsSpy.count, 5) + } + + function test_clicked_data() { + return [ + {tag: "exclusive", exclusive: true}, + {tag: "non-exclusive", exclusive: false} + ] + } + + function test_clicked(data) { + var group = createTemporaryObject(buttonGroup, testCase, {exclusive: data.exclusive}) + verify(group) + + var clickedSpy = createTemporaryObject(signalSpy, testCase, {target: group, signalName: "clicked"}) + verify(clickedSpy.valid) + + var button1 = createTemporaryObject(button, testCase) + var button2 = createTemporaryObject(button, testCase) + + group.addButton(button1) + group.addButton(button2) + + button1.clicked() + compare(clickedSpy.count, 1) + compare(clickedSpy.signalArguments[0][0], button1) + + button2.clicked() + compare(clickedSpy.count, 2) + compare(clickedSpy.signalArguments[1][0], button2) + } + + Component { + id: checkBoxes + Item { + property ButtonGroup group: ButtonGroup { id: group } + property CheckBox control1: CheckBox { ButtonGroup.group: group } + property CheckBox control2: CheckBox { ButtonGroup.group: group } + property CheckBox control3: CheckBox { ButtonGroup.group: group } + } + } + + Component { + id: radioButtons + Item { + property ButtonGroup group: ButtonGroup { id: group } + property RadioButton control1: RadioButton { ButtonGroup.group: group } + property RadioButton control2: RadioButton { ButtonGroup.group: group } + property RadioButton control3: RadioButton { ButtonGroup.group: group } + } + } + + Component { + id: switches + Item { + property ButtonGroup group: ButtonGroup { id: group } + property Switch control1: Switch { ButtonGroup.group: group } + property Switch control2: Switch { ButtonGroup.group: group } + property Switch control3: Switch { ButtonGroup.group: group } + } + } + + Component { + id: childControls + Item { + id: container + property ButtonGroup group: ButtonGroup { id: group; buttons: container.children } + property alias control1: control1 + property alias control2: control2 + property alias control3: control3 + CheckBox { id: control1 } + RadioButton { id: control2 } + Switch { id: control3 } + } + } + + function test_controls_data() { + return [ + { tag: "CheckBox", component: checkBoxes }, + { tag: "RadioButton", component: radioButtons }, + { tag: "Switch", component: switches }, + { tag: "Children", component: childControls } + ] + } + + function test_controls(data) { + var container = createTemporaryObject(data.component, testCase) + verify(container) + + verify(!container.group.checkedButton) + + container.control1.checked = true + compare(container.group.checkedButton, container.control1) + compare(container.control1.checked, true) + compare(container.control2.checked, false) + compare(container.control3.checked, false) + + container.control2.checked = true + compare(container.group.checkedButton, container.control2) + compare(container.control1.checked, false) + compare(container.control2.checked, true) + compare(container.control3.checked, false) + + container.control3.checked = true + compare(container.group.checkedButton, container.control3) + compare(container.control1.checked, false) + compare(container.control2.checked, false) + compare(container.control3.checked, true) + } + + function test_buttonDestroyed() { + var group = createTemporaryObject(buttonGroup, testCase) + verify(group) + + var buttonsSpy = createTemporaryObject(signalSpy, testCase, {target: group, signalName: "buttonsChanged"}) + verify(buttonsSpy.valid) + + var button1 = createTemporaryObject(button, testCase, {objectName: "button1", checked: true}) + + group.addButton(button1) + compare(group.buttons.length, 1) + compare(group.buttons[0], button1) + compare(group.checkedButton, button1) + compare(buttonsSpy.count, 1) + + button1.destroy() + wait(0) + compare(group.buttons.length, 0) + compare(group.checkedButton, null) + compare(buttonsSpy.count, 2) + } + + Component { + id: repeater + Column { + id: column + property ButtonGroup group: ButtonGroup { buttons: column.children } + property alias repeater: r + Repeater { + id: r + model: 3 + delegate: RadioDelegate { + checked: index == 0 + objectName: index + } + } + } + } + + function test_repeater() { + var container = createTemporaryObject(repeater, testCase) + verify(container) + + verify(container.group.checkedButton) + compare(container.group.checkedButton.objectName, "0") + } + + function test_nonExclusive() { + var group = createTemporaryObject(nonExclusiveGroup, testCase) + verify(group) + + compare(group.checkState, Qt.Unchecked) + + var button1 = createTemporaryObject(button, testCase, {checked: true}) + group.addButton(button1) + compare(button1.checked, true) + compare(group.checkedButton, null) + compare(group.checkState, Qt.Checked) + + var button2 = createTemporaryObject(button, testCase, {checked: true}) + group.addButton(button2) + compare(button1.checked, true) + compare(button2.checked, true) + compare(group.checkedButton, null) + compare(group.checkState, Qt.Checked) + + var button3 = createTemporaryObject(button, testCase, {checked: false}) + group.addButton(button3) + compare(button1.checked, true) + compare(button2.checked, true) + compare(button3.checked, false) + compare(group.checkedButton, null) + compare(group.checkState, Qt.PartiallyChecked) + + button1.checked = false + compare(button1.checked, false) + compare(button2.checked, true) + compare(button3.checked, false) + compare(group.checkedButton, null) + compare(group.checkState, Qt.PartiallyChecked) + + button2.checked = false + compare(button1.checked, false) + compare(button2.checked, false) + compare(button3.checked, false) + compare(group.checkedButton, null) + compare(group.checkState, Qt.Unchecked) + + button1.checked = true + compare(button1.checked, true) + compare(button2.checked, false) + compare(button3.checked, false) + compare(group.checkedButton, null) + compare(group.checkState, Qt.PartiallyChecked) + + button2.checked = true + compare(button1.checked, true) + compare(button2.checked, true) + compare(button3.checked, false) + compare(group.checkedButton, null) + compare(group.checkState, Qt.PartiallyChecked) + + button3.checked = true + compare(button1.checked, true) + compare(button2.checked, true) + compare(button3.checked, true) + compare(group.checkedButton, null) + compare(group.checkState, Qt.Checked) + } + + Component { + id: checkedButtonColumn + Column { + id: column + ButtonGroup { buttons: column.children } + Repeater { + id: repeater + delegate: Button { + checkable: true + text: modelData + onClicked: listModel.remove(index) + } + model: ListModel { + id: listModel + Component.onCompleted: { + for (var i = 0; i < 10; ++i) + append({text: i}) + } + } + } + } + } + + function test_checkedButtonDestroyed() { + var column = createTemporaryObject(checkedButtonColumn, testCase) + verify(column) + + waitForRendering(column) + mouseClick(column.children[0]) + wait(0) // don't crash (QTBUG-62946, QTBUG-63470) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_checkbox.qml b/tests/auto/quickcontrols2/controls/data/tst_checkbox.qml new file mode 100644 index 0000000000..be68ac0d1f --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_checkbox.qml @@ -0,0 +1,547 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 200 + height: 200 + visible: true + when: windowShown + name: "CheckBox" + + Component { + id: checkBox + CheckBox { } + } + + Component { + id: signalSequenceSpy + SignalSequenceSpy { + signals: ["pressed", "released", "canceled", "clicked", "toggled", "pressedChanged", "checkedChanged", "checkStateChanged"] + } + } + + function test_text() { + var control = createTemporaryObject(checkBox, testCase) + verify(control) + + compare(control.text, "") + control.text = "CheckBox" + compare(control.text, "CheckBox") + control.text = "" + compare(control.text, "") + } + + function test_checked() { + var control = createTemporaryObject(checkBox, testCase) + verify(control) + + var sequenceSpy = signalSequenceSpy.createObject(control, {target: control}) + + sequenceSpy.expectedSequence = [] + compare(control.checked, false) + compare(control.checkState, Qt.Unchecked) + verify(sequenceSpy.success) + + sequenceSpy.expectedSequence = [["checkStateChanged", { "checked": true, "checkState": Qt.Checked }], + ["checkedChanged", { "checked": true, "checkState": Qt.Checked }]] + control.checked = true + compare(control.checked, true) + compare(control.checkState, Qt.Checked) + verify(sequenceSpy.success) + + sequenceSpy.expectedSequence = [["checkStateChanged", { "checked": false, "checkState": Qt.Unchecked }], + ["checkedChanged", { "checked": false, "checkState": Qt.Unchecked }]] + control.checked = false + compare(control.checked, false) + compare(control.checkState, Qt.Unchecked) + verify(sequenceSpy.success) + } + + function test_checkState() { + var control = createTemporaryObject(checkBox, testCase) + verify(control) + + var sequenceSpy = signalSequenceSpy.createObject(control, {target: control}) + + sequenceSpy.expectedSequence = [] + compare(control.checked, false) + compare(control.checkState, Qt.Unchecked) + verify(sequenceSpy.success) + + sequenceSpy.expectedSequence = [["checkStateChanged", { "checked": true, "checkState": Qt.Checked }], + ["checkedChanged", { "checked": true, "checkState": Qt.Checked }]] + control.checkState = Qt.Checked + compare(control.checked, true) + compare(control.checkState, Qt.Checked) + verify(sequenceSpy.success) + + sequenceSpy.expectedSequence = [["checkStateChanged", { "checked": false, "checkState": Qt.Unchecked }], + ["checkedChanged", { "checked": false, "checkState": Qt.Unchecked }]] + control.checkState = Qt.Unchecked + compare(control.checked, false) + compare(control.checkState, Qt.Unchecked) + verify(sequenceSpy.success) + } + + function test_mouse() { + var control = createTemporaryObject(checkBox, testCase) + verify(control) + + var sequenceSpy = signalSequenceSpy.createObject(control, {target: control}) + + // check + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false, "checkState": Qt.Unchecked }], + "pressed"] + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.pressed, true) + verify(sequenceSpy.success) + + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": false, "checkState": Qt.Unchecked }], + ["checkStateChanged", { "pressed": false, "checked": true, "checkState": Qt.Checked }], + ["checkedChanged", { "pressed": false, "checked": true, "checkState": Qt.Checked }], + "toggled", + "released", + "clicked"] + mouseRelease(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.checked, true) + compare(control.checkState, Qt.Checked) + compare(control.pressed, false) + verify(sequenceSpy.success) + + // uncheck + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true, "checkState": Qt.Checked }], + "pressed"] + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.pressed, true) + verify(sequenceSpy.success) + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": true, "checkState": Qt.Checked }], + ["checkStateChanged", { "pressed": false, "checked": false, "checkState": Qt.Unchecked }], + ["checkedChanged", { "pressed": false, "checked": false, "checkState": Qt.Unchecked }], + "toggled", + "released", + "clicked"] + mouseRelease(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.checked, false) + compare(control.checkState, Qt.Unchecked) + compare(control.pressed, false) + verify(sequenceSpy.success) + + // release outside + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false, "checkState": Qt.Unchecked }], + "pressed"] + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.pressed, true) + verify(sequenceSpy.success) + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": false, "checkState": Qt.Unchecked }]] + mouseMove(control, control.width * 2, control.height * 2, 0) + compare(control.pressed, false) + verify(sequenceSpy.success) + sequenceSpy.expectedSequence = [["canceled", { "pressed": false, "checked": false, "checkState": Qt.Unchecked }]] + mouseRelease(control, control.width * 2, control.height * 2, Qt.LeftButton) + compare(control.checked, false) + compare(control.checkState, Qt.Unchecked) + compare(control.pressed, false) + verify(sequenceSpy.success) + + // right button + sequenceSpy.expectedSequence = [] + mousePress(control, control.width / 2, control.height / 2, Qt.RightButton) + compare(control.pressed, false) + mouseRelease(control, control.width / 2, control.height / 2, Qt.RightButton) + compare(control.checked, false) + compare(control.checkState, Qt.Unchecked) + compare(control.pressed, false) + verify(sequenceSpy.success) + } + + function test_touch() { + var control = createTemporaryObject(checkBox, testCase) + verify(control) + + var touch = touchEvent(control) + + var sequenceSpy = signalSequenceSpy.createObject(control, {target: control}) + + // check + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false, "checkState": Qt.Unchecked }], + "pressed"] + touch.press(0, control, control.width / 2, control.height / 2).commit() + compare(control.pressed, true) + verify(sequenceSpy.success) + + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": false, "checkState": Qt.Unchecked }], + ["checkStateChanged", { "pressed": false, "checked": true, "checkState": Qt.Checked }], + ["checkedChanged", { "pressed": false, "checked": true, "checkState": Qt.Checked }], + "toggled", + "released", + "clicked"] + touch.release(0, control, control.width / 2, control.height / 2).commit() + compare(control.checked, true) + compare(control.checkState, Qt.Checked) + compare(control.pressed, false) + verify(sequenceSpy.success) + + // uncheck + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true, "checkState": Qt.Checked }], + "pressed"] + // Don't want to double-click. + wait(Qt.styleHints.mouseDoubleClickInterval + 50) + touch.press(0, control, control.width / 2, control.height / 2).commit() + compare(control.pressed, true) + verify(sequenceSpy.success) + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": true, "checkState": Qt.Checked }], + ["checkStateChanged", { "pressed": false, "checked": false, "checkState": Qt.Unchecked }], + ["checkedChanged", { "pressed": false, "checked": false, "checkState": Qt.Unchecked }], + "toggled", + "released", + "clicked"] + touch.release(0, control, control.width / 2, control.height / 2).commit() + compare(control.checked, false) + compare(control.checkState, Qt.Unchecked) + compare(control.pressed, false) + verify(sequenceSpy.success) + + // release outside + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false, "checkState": Qt.Unchecked }], + "pressed"] + wait(Qt.styleHints.mouseDoubleClickInterval + 50) + touch.press(0, control, control.width / 2, control.height / 2).commit() + compare(control.pressed, true) + verify(sequenceSpy.success) + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": false, "checkState": Qt.Unchecked }]] + touch.move(0, control, control.width * 2, control.height * 2).commit() + compare(control.pressed, false) + verify(sequenceSpy.success) + sequenceSpy.expectedSequence = [["canceled", { "pressed": false, "checked": false, "checkState": Qt.Unchecked }]] + touch.release(0, control, control.width * 2, control.height * 2).commit() + compare(control.checked, false) + compare(control.checkState, Qt.Unchecked) + compare(control.pressed, false) + verify(sequenceSpy.success) + } + + function test_keys() { + var control = createTemporaryObject(checkBox, testCase) + verify(control) + + var sequenceSpy = signalSequenceSpy.createObject(control, {target: control}) + + sequenceSpy.expectedSequence = [] + control.forceActiveFocus() + verify(control.activeFocus) + verify(sequenceSpy.success) + + // check + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false, "checkState": Qt.Unchecked }], + "pressed", + ["pressedChanged", { "pressed": false, "checked": false, "checkState": Qt.Unchecked }], + ["checkStateChanged", { "pressed": false, "checked": true, "checkState": Qt.Checked }], + ["checkedChanged", { "pressed": false, "checked": true, "checkState": Qt.Checked }], + "toggled", + "released", + "clicked"] + keyClick(Qt.Key_Space) + compare(control.checked, true) + compare(control.checkState, Qt.Checked) + verify(sequenceSpy.success) + + // uncheck + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true, "checkState": Qt.Checked }], + "pressed", + ["pressedChanged", { "pressed": false, "checked": true, "checkState": Qt.Checked }], + ["checkStateChanged", { "pressed": false, "checked": false, "checkState": Qt.Unchecked }], + ["checkedChanged", { "pressed": false, "checked": false, "checkState": Qt.Unchecked }], + "toggled", + "released", + "clicked"] + keyClick(Qt.Key_Space) + compare(control.checked, false) + compare(control.checkState, Qt.Unchecked) + verify(sequenceSpy.success) + + // no change + sequenceSpy.expectedSequence = [] + var keys = [Qt.Key_Enter, Qt.Key_Return, Qt.Key_Escape, Qt.Key_Tab] + for (var i = 0; i < keys.length; ++i) { + sequenceSpy.reset() + keyClick(keys[i]) + compare(control.checked, false) + verify(sequenceSpy.success) + } + } + + Component { + id: checkedBoundBoxes + Item { + property CheckBox cb1: CheckBox { id: cb1 } + property CheckBox cb2: CheckBox { id: cb2; checked: cb1.checked; enabled: false } + } + } + + function test_checked_binding() { + var container = createTemporaryObject(checkedBoundBoxes, testCase) + verify(container) + + compare(container.cb1.checked, false) + compare(container.cb1.checkState, Qt.Unchecked) + compare(container.cb2.checked, false) + compare(container.cb2.checkState, Qt.Unchecked) + + container.cb1.checked = true + compare(container.cb1.checked, true) + compare(container.cb1.checkState, Qt.Checked) + compare(container.cb2.checked, true) + compare(container.cb2.checkState, Qt.Checked) + + container.cb1.checked = false + compare(container.cb1.checked, false) + compare(container.cb1.checkState, Qt.Unchecked) + compare(container.cb2.checked, false) + compare(container.cb2.checkState, Qt.Unchecked) + } + + Component { + id: checkStateBoundBoxes + Item { + property CheckBox cb1: CheckBox { id: cb1 } + property CheckBox cb2: CheckBox { id: cb2; checkState: cb1.checkState; enabled: false } + } + } + + function test_checkState_binding() { + var container = createTemporaryObject(checkStateBoundBoxes, testCase) + verify(container) + + compare(container.cb1.checked, false) + compare(container.cb1.checkState, Qt.Unchecked) + compare(container.cb2.checked, false) + compare(container.cb2.checkState, Qt.Unchecked) + + container.cb1.checkState = Qt.Checked + compare(container.cb1.checked, true) + compare(container.cb1.checkState, Qt.Checked) + compare(container.cb2.checked, true) + compare(container.cb2.checkState, Qt.Checked) + + container.cb1.checkState = Qt.Unchecked + compare(container.cb1.checked, false) + compare(container.cb1.checkState, Qt.Unchecked) + compare(container.cb2.checked, false) + compare(container.cb2.checkState, Qt.Unchecked) + + compare(container.cb1.tristate, false) + compare(container.cb2.tristate, false) + + container.cb1.checkState = Qt.PartiallyChecked + compare(container.cb1.checked, false) + compare(container.cb1.checkState, Qt.PartiallyChecked) + compare(container.cb2.checked, false) + compare(container.cb2.checkState, Qt.PartiallyChecked) + + // note: since Qt Quick Controls 2.4 (Qt 5.11), CheckBox does not + // force tristate when checkState is set to Qt.PartiallyChecked + compare(container.cb1.tristate, false) + compare(container.cb2.tristate, false) + } + + function test_tristate() { + var control = createTemporaryObject(checkBox, testCase, {tristate: true}) + + var sequenceSpy = signalSequenceSpy.createObject(control, {target: control}) + + sequenceSpy.expectedSequence = [] + control.forceActiveFocus() + verify(control.activeFocus) + + compare(control.tristate, true) + compare(control.checked, false) + compare(control.checkState, Qt.Unchecked) + + sequenceSpy.expectedSequence = [["checkStateChanged", { "pressed": false, "checked": false, "checkState": Qt.PartiallyChecked }]] + control.checkState = Qt.PartiallyChecked + compare(control.checked, false) + compare(control.checkState, Qt.PartiallyChecked) + verify(sequenceSpy.success) + + // key: partial -> checked + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false, "checkState": Qt.PartiallyChecked }], + "pressed", + ["pressedChanged", { "pressed": false, "checked": false, "checkState": Qt.PartiallyChecked }], + ["checkStateChanged", { "pressed": false, "checked": true, "checkState": Qt.Checked }], + ["checkedChanged", { "pressed": false, "checked": true, "checkState": Qt.Checked }], + "released", + "clicked"] + keyClick(Qt.Key_Space) + compare(control.checked, true) + compare(control.checkState, Qt.Checked) + verify(sequenceSpy.success) + + // key: checked -> unchecked + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true, "checkState": Qt.Checked }], + "pressed", + ["pressedChanged", { "pressed": false, "checked": true, "checkState": Qt.Checked }], + ["checkStateChanged", { "pressed": false, "checked": false, "checkState": Qt.Unchecked }], + ["checkedChanged", { "pressed": false, "checked": false, "checkState": Qt.Unchecked }], + "released", + "clicked"] + keyClick(Qt.Key_Space) + compare(control.checked, false) + compare(control.checkState, Qt.Unchecked) + verify(sequenceSpy.success) + + // key: unchecked -> partial + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false, "checkState": Qt.Unchecked }], + "pressed", + ["pressedChanged", { "pressed": false, "checked": false, "checkState": Qt.Unchecked }], + ["checkStateChanged", { "pressed": false, "checked": false, "checkState": Qt.PartiallyChecked }], + "released", + "clicked"] + keyClick(Qt.Key_Space) + compare(control.checked, false) + compare(control.checkState, Qt.PartiallyChecked) + verify(sequenceSpy.success) + + // mouse: partial -> checked + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false, "checkState": Qt.PartiallyChecked }], + "pressed", + ["pressedChanged", { "pressed": false, "checked": false, "checkState": Qt.PartiallyChecked }], + ["checkStateChanged", { "pressed": false, "checked": true, "checkState": Qt.Checked }], + ["checkedChanged", { "pressed": false, "checked": true, "checkState": Qt.Checked }], + "released", + "clicked"] + mouseClick(control) + compare(control.checked, true) + compare(control.checkState, Qt.Checked) + verify(sequenceSpy.success) + + // mouse: checked -> unchecked + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true, "checkState": Qt.Checked }], + "pressed", + ["pressedChanged", { "pressed": false, "checked": true, "checkState": Qt.Checked }], + ["checkStateChanged", { "pressed": false, "checked": false, "checkState": Qt.Unchecked }], + ["checkedChanged", { "pressed": false, "checked": false, "checkState": Qt.Unchecked }], + "released", + "clicked"] + mouseClick(control) + compare(control.checked, false) + compare(control.checkState, Qt.Unchecked) + verify(sequenceSpy.success) + + // mouse: unchecked -> partial + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false, "checkState": Qt.Unchecked }], + "pressed", + ["pressedChanged", { "pressed": false, "checked": false, "checkState": Qt.Unchecked }], + ["checkStateChanged", { "pressed": false, "checked": false, "checkState": Qt.PartiallyChecked }], + "released", + "clicked"] + mouseClick(control) + compare(control.checked, false) + compare(control.checkState, Qt.PartiallyChecked) + verify(sequenceSpy.success) + } + + function test_baseline() { + var control = createTemporaryObject(checkBox, testCase) + verify(control) + compare(control.baselineOffset, control.contentItem.y + control.contentItem.baselineOffset) + } + + Component { + id: nextCheckStateBox + CheckBox { + tristate: true + nextCheckState: function() { + if (checkState === Qt.Checked) + return Qt.Unchecked + else + return Qt.Checked + } + } + } + + function test_nextCheckState_data() { + return [ + { tag: "unchecked", checkState: Qt.Unchecked, expectedState: Qt.Checked }, + { tag: "partially-checked", checkState: Qt.PartiallyChecked, expectedState: Qt.Checked }, + { tag: "checked", checkState: Qt.Checked, expectedState: Qt.Unchecked } + ] + } + + function test_nextCheckState(data) { + var control = createTemporaryObject(nextCheckStateBox, testCase) + verify(control) + + // mouse + control.checkState = data.checkState + compare(control.checkState, data.checkState) + mouseClick(control) + compare(control.checkState, data.expectedState) + + // touch + control.checkState = data.checkState + compare(control.checkState, data.checkState) + var touch = touchEvent(control) + touch.press(0, control).commit().release(0, control).commit() + compare(control.checkState, data.expectedState) + + // keyboard + control.forceActiveFocus() + tryCompare(control, "activeFocus", true) + control.checkState = data.checkState + compare(control.checkState, data.checkState) + keyClick(Qt.Key_Space) + compare(control.checkState, data.expectedState) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_checkdelegate.qml b/tests/auto/quickcontrols2/controls/data/tst_checkdelegate.qml new file mode 100644 index 0000000000..70f1ae9dbf --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_checkdelegate.qml @@ -0,0 +1,220 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 200 + height: 200 + visible: true + when: windowShown + name: "CheckDelegate" + + Component { + id: checkDelegate + CheckDelegate {} + } + + // TODO: data-fy tst_checkbox (rename to tst_check?) so we don't duplicate its tests here? + + function test_defaults() { + var control = createTemporaryObject(checkDelegate, testCase); + verify(control); + verify(!control.checked); + } + + function test_checked() { + var control = createTemporaryObject(checkDelegate, testCase); + verify(control); + + mouseClick(control); + verify(control.checked); + + mouseClick(control); + verify(!control.checked); + } + + function test_baseline() { + var control = createTemporaryObject(checkDelegate, testCase); + verify(control); + compare(control.baselineOffset, control.contentItem.y + control.contentItem.baselineOffset); + } + + function test_spacing() { + var control = createTemporaryObject(checkDelegate, testCase, { text: "Some long, long, long text" }) + verify(control) + verify(control.contentItem.implicitWidth + control.leftPadding + control.rightPadding > control.background.implicitWidth) + + var textLabel = findChild(control.contentItem, "label") + verify(textLabel) + + // The implicitWidth of the IconLabel that all buttons use as their contentItem should be + // equal to the implicitWidth of the Text and the check indicator + spacing while no icon is set. + compare(control.contentItem.implicitWidth, textLabel.implicitWidth + control.indicator.width + control.spacing) + + control.spacing += 100 + compare(control.contentItem.implicitWidth, textLabel.implicitWidth + control.indicator.width + control.spacing) + + compare(control.implicitWidth, textLabel.implicitWidth + control.indicator.width + control.spacing + control.leftPadding + control.rightPadding) + } + + function test_display_data() { + return [ + { "tag": "IconOnly", display: CheckDelegate.IconOnly }, + { "tag": "TextOnly", display: CheckDelegate.TextOnly }, + { "tag": "TextUnderIcon", display: CheckDelegate.TextUnderIcon }, + { "tag": "TextBesideIcon", display: CheckDelegate.TextBesideIcon }, + { "tag": "IconOnly, mirrored", display: CheckDelegate.IconOnly, mirrored: true }, + { "tag": "TextOnly, mirrored", display: CheckDelegate.TextOnly, mirrored: true }, + { "tag": "TextUnderIcon, mirrored", display: CheckDelegate.TextUnderIcon, mirrored: true }, + { "tag": "TextBesideIcon, mirrored", display: CheckDelegate.TextBesideIcon, mirrored: true } + ] + } + + function test_display(data) { + var control = createTemporaryObject(checkDelegate, testCase, { + text: "CheckDelegate", + display: data.display, + width: 400, + "icon.source": "qrc:/qt-project.org/imports/QtQuick/Controls/Basic/images/check.png", + "LayoutMirroring.enabled": !!data.mirrored + }) + verify(control) + compare(control.icon.source, "qrc:/qt-project.org/imports/QtQuick/Controls/Basic/images/check.png") + + var iconImage = findChild(control.contentItem, "image") + var textLabel = findChild(control.contentItem, "label") + + var availableWidth = control.availableWidth - control.indicator.width - control.spacing + var indicatorOffset = control.mirrored ? control.indicator.width + control.spacing : 0 + + switch (control.display) { + case CheckDelegate.IconOnly: + verify(iconImage) + verify(!textLabel) + compare(iconImage.x, indicatorOffset + (availableWidth - iconImage.width) / 2) + compare(iconImage.y, (control.availableHeight - iconImage.height) / 2) + break; + case CheckDelegate.TextOnly: + verify(!iconImage) + verify(textLabel) + compare(textLabel.x, control.mirrored ? control.availableWidth - textLabel.width : 0) + compare(textLabel.y, (control.availableHeight - textLabel.height) / 2) + break; + case CheckDelegate.TextUnderIcon: + verify(iconImage) + verify(textLabel) + compare(iconImage.x, indicatorOffset + (availableWidth - iconImage.width) / 2) + compare(textLabel.x, indicatorOffset + (availableWidth - textLabel.width) / 2) + verify(iconImage.y < textLabel.y) + break; + case CheckDelegate.TextBesideIcon: + verify(iconImage) + verify(textLabel) + if (control.mirrored) + verify(textLabel.x < iconImage.x) + else + verify(iconImage.x < textLabel.x) + compare(iconImage.y, (control.availableHeight - iconImage.height) / 2) + compare(textLabel.y, (control.availableHeight - textLabel.height) / 2) + break; + } + } + + Component { + id: nextCheckStateDelegate + CheckDelegate { + tristate: true + nextCheckState: function() { + if (checkState === Qt.Checked) + return Qt.Unchecked + else + return Qt.Checked + } + } + } + + function test_nextCheckState_data() { + return [ + { tag: "unchecked", checkState: Qt.Unchecked, expectedState: Qt.Checked }, + { tag: "partially-checked", checkState: Qt.PartiallyChecked, expectedState: Qt.Checked }, + { tag: "checked", checkState: Qt.Checked, expectedState: Qt.Unchecked } + ] + } + + function test_nextCheckState(data) { + var control = createTemporaryObject(nextCheckStateDelegate, testCase) + verify(control) + + // mouse + control.checkState = data.checkState + compare(control.checkState, data.checkState) + mouseClick(control) + compare(control.checkState, data.expectedState) + + // touch + control.checkState = data.checkState + compare(control.checkState, data.checkState) + var touch = touchEvent(control) + touch.press(0, control).commit().release(0, control).commit() + compare(control.checkState, data.expectedState) + + // keyboard + control.forceActiveFocus() + tryCompare(control, "activeFocus", true) + control.checkState = data.checkState + compare(control.checkState, data.checkState) + keyClick(Qt.Key_Space) + compare(control.checkState, data.expectedState) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_combobox.qml b/tests/auto/quickcontrols2/controls/data/tst_combobox.qml new file mode 100644 index 0000000000..5359f63c4a --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_combobox.qml @@ -0,0 +1,2283 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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.Window +import QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "ComboBox" + + Component { + id: signalSpy + SignalSpy { } + } + + Component { + id: comboBox + ComboBox { } + } + + Component { + id: emptyBox + ComboBox { + delegate: ItemDelegate { + width: parent.width + } + } + } + + Component { + id: mouseArea + MouseArea { } + } + + Component { + id: customPopup + Popup { + width: 100 + implicitHeight: contentItem.implicitHeight + contentItem: TextInput { + anchors.fill: parent + } + } + } + + Component { + id: comboBoxWithShaderEffect + ComboBox { + delegate: Rectangle { + Text { + id: txt + anchors.centerIn: parent + text: "item" + index + font.pixelSize: 20 + color: "red" + } + id: rect + objectName: "rect" + width: parent.width + height: txt.implicitHeight + gradient: Gradient { + GradientStop { color: "lightsteelblue"; position: 0.0 } + GradientStop { color: "blue"; position: 1.0 } + } + layer.enabled: true + layer.effect: ShaderEffect { + objectName: "ShaderFX" + width: rect.width + height: rect.height + fragmentShader: " + uniform lowp sampler2D source; // this item + uniform lowp float qt_Opacity; // inherited opacity of this item + varying highp vec2 qt_TexCoord0; + void main() { + lowp vec4 p = texture2D(source, qt_TexCoord0); + lowp float g = dot(p.xyz, vec3(0.344, 0.5, 0.156)); + gl_FragColor = vec4(g, g, g, p.a) * qt_Opacity; + }" + + } + } + } + } + + function init() { + // QTBUG-61225: Move the mouse away to avoid QQuickWindowPrivate::flushFrameSynchronousEvents() + // delivering interfering hover events based on the last mouse position from earlier tests. For + // example, ComboBox::test_activation() kept receiving hover events for the last mouse position + // from CheckDelegate::test_checked(). + mouseMove(testCase, testCase.width - 1, testCase.height - 1) + } + + function test_defaults() { + var control = createTemporaryObject(comboBox, testCase) + verify(control) + + compare(control.count, 0) + compare(control.model, undefined) + compare(control.flat, false) + compare(control.pressed, false) + compare(control.currentIndex, -1) + compare(control.highlightedIndex, -1) + compare(control.currentText, "") + verify(control.delegate) + verify(control.indicator) + verify(control.popup) + verify(control.acceptableInput) + compare(control.inputMethodHints, Qt.ImhNoPredictiveText) + } + + function test_array() { + var control = createTemporaryObject(comboBox, testCase) + verify(control) + + var items = [ "Banana", "Apple", "Coconut" ] + + control.model = items + compare(control.model, items) + + compare(control.count, 3) + compare(control.currentIndex, 0) + compare(control.currentText, "Banana") + + control.currentIndex = 2 + compare(control.currentIndex, 2) + compare(control.currentText, "Coconut") + + control.model = null + compare(control.model, null) + compare(control.count, 0) + compare(control.currentIndex, -1) + compare(control.currentText, "") + } + + function test_objects() { + var control = createTemporaryObject(emptyBox, testCase) + verify(control) + + var items = [ + { text: "Apple" }, + { text: "Orange" }, + { text: "Banana" } + ] + + control.model = items + compare(control.model, items) + + compare(control.count, 3) + compare(control.currentIndex, 0) + compare(control.currentText, "Apple") + + control.currentIndex = 2 + compare(control.currentIndex, 2) + compare(control.currentText, "Banana") + + control.model = null + compare(control.model, null) + compare(control.count, 0) + compare(control.currentIndex, -1) + compare(control.currentText, "") + } + + function test_qobjects() { + var control = createTemporaryObject(emptyBox, testCase, {textRole: "text"}) + verify(control) + + var obj1 = Qt.createQmlObject("import QtQml; QtObject { property string text: 'one' }", control) + var obj2 = Qt.createQmlObject("import QtQml; QtObject { property string text: 'two' }", control) + var obj3 = Qt.createQmlObject("import QtQml; QtObject { property string text: 'three' }", control) + + control.model = [obj1, obj2, obj3] + + compare(control.count, 3) + compare(control.currentIndex, 0) + compare(control.currentText, "one") + + control.currentIndex = 2 + compare(control.currentIndex, 2) + compare(control.currentText, "three") + + control.model = null + compare(control.model, null) + compare(control.count, 0) + compare(control.currentIndex, -1) + compare(control.currentText, "") + } + + function test_number() { + var control = createTemporaryObject(comboBox, testCase) + verify(control) + + control.model = 10 + compare(control.model, 10) + + compare(control.count, 10) + compare(control.currentIndex, 0) + compare(control.currentText, "0") + + control.currentIndex = 9 + compare(control.currentIndex, 9) + compare(control.currentText, "9") + + control.model = 0 + compare(control.model, 0) + compare(control.count, 0) + compare(control.currentIndex, -1) + compare(control.currentText, "") + } + + ListModel { + id: listmodel + ListElement { text: "First" } + ListElement { text: "Second" } + ListElement { text: "Third" } + ListElement { text: "Fourth" } + ListElement { text: "Fifth" } + } + + function test_listModel() { + var control = createTemporaryObject(comboBox, testCase) + verify(control) + + control.model = listmodel + compare(control.model, listmodel) + + compare(control.count, 5) + compare(control.currentIndex, 0) + compare(control.currentText, "First") + + control.currentIndex = 2 + compare(control.currentIndex, 2) + compare(control.currentText, "Third") + + control.model = undefined + compare(control.model, undefined) + compare(control.count, 0) + compare(control.currentIndex, -1) + compare(control.currentText, "") + } + + ListModel { + id: fruitmodel + ListElement { name: "Apple"; color: "red" } + ListElement { name: "Orange"; color: "orange" } + ListElement { name: "Banana"; color: "yellow" } + } + + Component { + id: fruitModelComponent + ListModel { + ListElement { name: "Apple"; color: "red" } + ListElement { name: "Orange"; color: "orange" } + ListElement { name: "Banana"; color: "yellow" } + } + } + + property var fruitarray: [ + { name: "Apple", color: "red" }, + { name: "Orange", color: "orange" }, + { name: "Banana", color: "yellow" } + ] + + Component { + id: birdModelComponent + ListModel { + ListElement { name: "Galah"; color: "pink" } + ListElement { name: "Kookaburra"; color: "brown" } + ListElement { name: "Magpie"; color: "black" } + } + } + + function test_textRole_data() { + return [ + { tag: "ListModel", model: fruitmodel }, + { tag: "ObjectArray", model: fruitarray } + ] + } + + function test_textRole(data) { + var control = createTemporaryObject(emptyBox, testCase) + verify(control) + + control.model = data.model + compare(control.count, 3) + compare(control.currentIndex, 0) + compare(control.currentText, "") + + control.textRole = "name" + compare(control.currentText, "Apple") + + control.textRole = "color" + compare(control.currentText, "red") + + control.currentIndex = 1 + compare(control.currentIndex, 1) + compare(control.currentText, "orange") + + control.textRole = "name" + compare(control.currentText, "Orange") + + control.textRole = "" + compare(control.currentText, "") + } + + function test_textAt() { + var control = createTemporaryObject(comboBox, testCase) + verify(control) + + control.model = ["Apple", "Orange", "Banana"] + compare(control.textAt(0), "Apple") + compare(control.textAt(1), "Orange") + compare(control.textAt(2), "Banana") + compare(control.textAt(-1), "") // TODO: null? + compare(control.textAt(5), "") // TODO: null? + } + + function test_find_data() { + return [ + { tag: "Banana (MatchExactly)", term: "Banana", flags: Qt.MatchExactly, index: 0 }, + { tag: "banana (MatchExactly)", term: "banana", flags: Qt.MatchExactly, index: 1 }, + { tag: "bananas (MatchExactly)", term: "bananas", flags: Qt.MatchExactly, index: -1 }, + { tag: "Cocomuffin (MatchExactly)", term: "Cocomuffin", flags: Qt.MatchExactly, index: 4 }, + + { tag: "b(an)+a (MatchRegularExpression)", term: "B(an)+a", flags: Qt.MatchRegularExpression, index: 0 }, + { tag: "b(an)+a (MatchRegularExpression|MatchCaseSensitive)", term: "b(an)+a", flags: Qt.MatchRegularExpression | Qt.MatchCaseSensitive, index: 1 }, + { tag: "[coc]+\\w+ (MatchRegularExpression)", term: "[coc]+\\w+", flags: Qt.MatchRegularExpression, index: 2 }, + + { tag: "?pp* (MatchWildcard)", term: "?pp*", flags: Qt.MatchWildcard, index: 3 }, + { tag: "app* (MatchWildcard|MatchCaseSensitive)", term: "app*", flags: Qt.MatchWildcard | Qt.MatchCaseSensitive, index: -1 }, + + { tag: "Banana (MatchFixedString)", term: "Banana", flags: Qt.MatchFixedString, index: 0 }, + { tag: "banana (MatchFixedString|MatchCaseSensitive)", term: "banana", flags: Qt.MatchFixedString | Qt.MatchCaseSensitive, index: 1 }, + + { tag: "coco (MatchStartsWith)", term: "coco", flags: Qt.MatchStartsWith, index: 2 }, + { tag: "coco (MatchStartsWith|MatchCaseSensitive)", term: "coco", flags: Qt.StartsWith | Qt.MatchCaseSensitive, index: -1 }, + + { tag: "MUFFIN (MatchEndsWith)", term: "MUFFIN", flags: Qt.MatchEndsWith, index: 4 }, + { tag: "MUFFIN (MatchEndsWith|MatchCaseSensitive)", term: "MUFFIN", flags: Qt.MatchEndsWith | Qt.MatchCaseSensitive, index: -1 }, + + { tag: "Con (MatchContains)", term: "Con", flags: Qt.MatchContains, index: 2 }, + { tag: "Con (MatchContains|MatchCaseSensitive)", term: "Con", flags: Qt.MatchContains | Qt.MatchCaseSensitive, index: -1 }, + ] + } + + function test_find(data) { + var control = createTemporaryObject(comboBox, testCase) + verify(control) + + control.model = ["Banana", "banana", "Coconut", "Apple", "Cocomuffin"] + + compare(control.find(data.term, data.flags), data.index) + } + + function test_valueRole_data() { + return [ + { tag: "ListModel", model: fruitmodel }, + { tag: "ObjectArray", model: fruitarray } + ] + } + + function test_valueRole(data) { + var control = createTemporaryObject(emptyBox, testCase, + { model: data.model, valueRole: "color" }) + verify(control) + compare(control.count, 3) + compare(control.currentIndex, 0) + compare(control.currentValue, "red") + + control.valueRole = "name" + compare(control.currentValue, "Apple") + + control.currentIndex = 1 + compare(control.currentIndex, 1) + compare(control.currentValue, "Orange") + + control.valueRole = "color" + compare(control.currentValue, "orange") + + control.model = null + compare(control.currentIndex, -1) + // An invalid QVariant is represented as undefined. + compare(control.currentValue, undefined) + + control.valueRole = "" + compare(control.currentValue, undefined) + } + + function test_valueAt() { + var control = createTemporaryObject(comboBox, testCase, + { model: fruitmodel, textRole: "name", valueRole: "color" }) + verify(control) + + compare(control.valueAt(0), "red") + compare(control.valueAt(1), "orange") + compare(control.valueAt(2), "yellow") + compare(control.valueAt(-1), undefined) + compare(control.valueAt(5), undefined) + } + + function test_indexOfValue_data() { + return [ + { tag: "red", expectedIndex: 0 }, + { tag: "orange", expectedIndex: 1 }, + { tag: "yellow", expectedIndex: 2 }, + { tag: "brown", expectedIndex: -1 }, + ] + } + + function test_indexOfValue(data) { + var control = createTemporaryObject(comboBox, testCase, + { model: fruitmodel, textRole: "name", valueRole: "color" }) + verify(control) + + compare(control.indexOfValue(data.tag), data.expectedIndex) + } + + function test_currentValueAfterModelChanged() { + let fruitModel = createTemporaryObject(fruitModelComponent, testCase) + verify(fruitModel) + + let control = createTemporaryObject(comboBox, testCase, + { model: fruitModel, textRole: "name", valueRole: "color", currentIndex: 1 }) + verify(control) + compare(control.currentText, "Orange") + compare(control.currentValue, "orange") + + // Remove "Apple"; the current item should now be "Banana", so currentValue should be "yellow". + fruitModel.remove(0) + compare(control.currentText, "Banana") + compare(control.currentValue, "yellow") + } + + function test_currentValueAfterNewModelSet() { + let control = createTemporaryObject(comboBox, testCase, + { model: fruitmodel, textRole: "name", valueRole: "color", currentIndex: 0 }) + verify(control) + compare(control.currentText, "Apple") + compare(control.currentValue, "red") + + // Swap the model out entirely. Since the currentIndex was 0 and + // is reset to 0 when a new model is set, it remains 0. + let birdModel = createTemporaryObject(birdModelComponent, testCase) + verify(birdModel) + control.model = birdModel + compare(control.currentText, "Galah") + compare(control.currentValue, "pink") + } + + function test_arrowKeys() { + var control = createTemporaryObject(comboBox, testCase, + { model: fruitmodel, textRole: "name", valueRole: "color" }) + verify(control) + + var activatedSpy = signalSpy.createObject(control, {target: control, signalName: "activated"}) + verify(activatedSpy.valid) + + var highlightedSpy = signalSpy.createObject(control, {target: control, signalName: "highlighted"}) + verify(highlightedSpy.valid) + + var openedSpy = signalSpy.createObject(control, {target: control.popup, signalName: "opened"}) + verify(openedSpy.valid) + + var closedSpy = signalSpy.createObject(control, {target: control.popup, signalName: "closed"}) + verify(closedSpy.valid) + + control.forceActiveFocus() + verify(control.activeFocus) + + compare(control.currentIndex, 0) + compare(control.highlightedIndex, -1) + + keyClick(Qt.Key_Down) + compare(control.currentIndex, 1) + compare(control.highlightedIndex, -1) + compare(highlightedSpy.count, 0) + compare(activatedSpy.count, 1) + compare(activatedSpy.signalArguments[0][0], 1) + activatedSpy.clear() + + keyClick(Qt.Key_Down) + compare(control.currentIndex, 2) + compare(control.highlightedIndex, -1) + compare(highlightedSpy.count, 0) + compare(activatedSpy.count, 1) + compare(activatedSpy.signalArguments[0][0], 2) + activatedSpy.clear() + + keyClick(Qt.Key_Down) + compare(control.currentIndex, 2) + compare(control.highlightedIndex, -1) + compare(highlightedSpy.count, 0) + compare(activatedSpy.count, 0) + + keyClick(Qt.Key_Up) + compare(control.currentIndex, 1) + compare(control.highlightedIndex, -1) + compare(highlightedSpy.count, 0) + compare(activatedSpy.count, 1) + compare(activatedSpy.signalArguments[0][0], 1) + activatedSpy.clear() + + keyClick(Qt.Key_Up) + compare(control.currentIndex, 0) + compare(control.highlightedIndex, -1) + compare(highlightedSpy.count, 0) + compare(activatedSpy.count, 1) + compare(activatedSpy.signalArguments[0][0], 0) + activatedSpy.clear() + + keyClick(Qt.Key_Up) + compare(control.currentIndex, 0) + compare(control.highlightedIndex, -1) + compare(highlightedSpy.count, 0) + compare(activatedSpy.count, 0) + + // show popup + keyClick(Qt.Key_Space) + openedSpy.wait() + compare(openedSpy.count, 1) + + compare(control.currentIndex, 0) + compare(control.highlightedIndex, 0) + + keyClick(Qt.Key_Down) + compare(control.currentIndex, 0) + compare(control.highlightedIndex, 1) + compare(activatedSpy.count, 0) + compare(highlightedSpy.count, 1) + compare(highlightedSpy.signalArguments[0][0], 1) + highlightedSpy.clear() + + keyClick(Qt.Key_Down) + compare(control.currentIndex, 0) + compare(control.highlightedIndex, 2) + compare(activatedSpy.count, 0) + compare(highlightedSpy.count, 1) + compare(highlightedSpy.signalArguments[0][0], 2) + highlightedSpy.clear() + + keyClick(Qt.Key_Down) + compare(control.currentIndex, 0) + compare(control.highlightedIndex, 2) + compare(activatedSpy.count, 0) + compare(highlightedSpy.count, 0) + + keyClick(Qt.Key_Up) + compare(control.currentIndex, 0) + compare(control.highlightedIndex, 1) + compare(activatedSpy.count, 0) + compare(highlightedSpy.count, 1) + compare(highlightedSpy.signalArguments[0][0], 1) + highlightedSpy.clear() + + keyClick(Qt.Key_Up) + compare(control.currentIndex, 0) + compare(control.highlightedIndex, 0) + compare(activatedSpy.count, 0) + compare(highlightedSpy.count, 1) + compare(highlightedSpy.signalArguments[0][0], 0) + highlightedSpy.clear() + + keyClick(Qt.Key_Up) + compare(control.currentIndex, 0) + compare(control.highlightedIndex, 0) + compare(activatedSpy.count, 0) + compare(highlightedSpy.count, 0) + + keyClick(Qt.Key_Down) + compare(control.currentIndex, 0) + compare(control.highlightedIndex, 1) + compare(activatedSpy.count, 0) + compare(highlightedSpy.count, 1) + compare(highlightedSpy.signalArguments[0][0], 1) + highlightedSpy.clear() + + // hide popup + keyClick(Qt.Key_Space) + closedSpy.wait() + compare(closedSpy.count, 1) + + compare(control.currentIndex, 1) + compare(control.highlightedIndex, -1) + } + + function test_keys_space_enter_escape_data() { + return [ + { tag: "space-space", key1: Qt.Key_Space, key2: Qt.Key_Space, showPopup: true, showPress: true, hidePopup: true, hidePress: true }, + { tag: "space-enter", key1: Qt.Key_Space, key2: Qt.Key_Enter, showPopup: true, showPress: true, hidePopup: true, hidePress: true }, + { tag: "space-return", key1: Qt.Key_Space, key2: Qt.Key_Return, showPopup: true, showPress: true, hidePopup: true, hidePress: true }, + { tag: "space-escape", key1: Qt.Key_Space, key2: Qt.Key_Escape, showPopup: true, showPress: true, hidePopup: true, hidePress: false }, + { tag: "space-0", key1: Qt.Key_Space, key2: Qt.Key_0, showPopup: true, showPress: true, hidePopup: false, hidePress: false }, + { tag: "enter-enter", key1: Qt.Key_Enter, key2: Qt.Key_Enter, showPopup: false, showPress: false, hidePopup: true, hidePress: false }, + { tag: "return-return", key1: Qt.Key_Return, key2: Qt.Key_Return, showPopup: false, showPress: false, hidePopup: true, hidePress: false }, + { tag: "escape-escape", key1: Qt.Key_Escape, key2: Qt.Key_Escape, showPopup: false, showPress: false, hidePopup: true, hidePress: false } + ] + } + + function test_keys_space_enter_escape(data) { + var control = createTemporaryObject(comboBox, testCase, {model: 3}) + verify(control) + + var openedSpy = signalSpy.createObject(control, {target: control.popup, signalName: "opened"}) + verify(openedSpy.valid) + + control.forceActiveFocus() + verify(control.activeFocus) + + compare(control.pressed, false) + compare(control.popup.visible, false) + + // show popup + keyPress(data.key1) + compare(control.pressed, data.showPress) + compare(control.popup.visible, false) + keyRelease(data.key1) + compare(control.pressed, false) + compare(control.popup.visible, data.showPopup) + if (data.showPopup) + openedSpy.wait() + + // hide popup + keyPress(data.key2) + compare(control.pressed, data.hidePress) + keyRelease(data.key2) + compare(control.pressed, false) + tryCompare(control.popup, "visible", !data.hidePopup) + } + + function test_keys_home_end() { + var control = createTemporaryObject(comboBox, testCase, {model: 5}) + verify(control) + + control.forceActiveFocus() + verify(control.activeFocus) + compare(control.currentIndex, 0) + compare(control.highlightedIndex, -1) + + var activatedCount = 0 + var activatedSpy = signalSpy.createObject(control, {target: control, signalName: "activated"}) + verify(activatedSpy.valid) + + var highlightedCount = 0 + var highlightedSpy = signalSpy.createObject(control, {target: control, signalName: "highlighted"}) + verify(highlightedSpy.valid) + + var currentIndexCount = 0 + var currentIndexSpy = signalSpy.createObject(control, {target: control, signalName: "currentIndexChanged"}) + verify(currentIndexSpy.valid) + + var highlightedIndexCount = 0 + var highlightedIndexSpy = signalSpy.createObject(control, {target: control, signalName: "highlightedIndexChanged"}) + verify(highlightedIndexSpy.valid) + + // end (popup closed) + keyClick(Qt.Key_End) + compare(control.currentIndex, 4) + compare(currentIndexSpy.count, ++currentIndexCount) + + compare(control.highlightedIndex, -1) + compare(highlightedIndexSpy.count, highlightedIndexCount) + + compare(activatedSpy.count, ++activatedCount) + compare(activatedSpy.signalArguments[activatedCount-1][0], 4) + + compare(highlightedSpy.count, highlightedCount) + + // repeat (no changes/signals) + keyClick(Qt.Key_End) + compare(currentIndexSpy.count, currentIndexCount) + compare(highlightedIndexSpy.count, highlightedIndexCount) + compare(activatedSpy.count, activatedCount) + compare(highlightedSpy.count, highlightedCount) + + // home (popup closed) + keyClick(Qt.Key_Home) + compare(control.currentIndex, 0) + compare(currentIndexSpy.count, ++currentIndexCount) + + compare(control.highlightedIndex, -1) + compare(highlightedIndexSpy.count, highlightedIndexCount) + + compare(activatedSpy.count, ++activatedCount) + compare(activatedSpy.signalArguments[activatedCount-1][0], 0) + + compare(highlightedSpy.count, highlightedCount) + + // repeat (no changes/signals) + keyClick(Qt.Key_Home) + compare(currentIndexSpy.count, currentIndexCount) + compare(highlightedIndexSpy.count, highlightedIndexCount) + compare(activatedSpy.count, activatedCount) + compare(highlightedSpy.count, highlightedCount) + + control.popup.open() + compare(control.highlightedIndex, 0) + compare(highlightedIndexSpy.count, ++highlightedIndexCount) + compare(highlightedSpy.count, highlightedCount) + + // end (popup open) + keyClick(Qt.Key_End) + compare(control.currentIndex, 0) + compare(currentIndexSpy.count, currentIndexCount) + + compare(control.highlightedIndex, 4) + compare(highlightedIndexSpy.count, ++highlightedIndexCount) + + compare(activatedSpy.count, activatedCount) + + compare(highlightedSpy.count, ++highlightedCount) + compare(highlightedSpy.signalArguments[highlightedCount-1][0], 4) + + // repeat (no changes/signals) + keyClick(Qt.Key_End) + compare(currentIndexSpy.count, currentIndexCount) + compare(highlightedIndexSpy.count, highlightedIndexCount) + compare(activatedSpy.count, activatedCount) + compare(highlightedSpy.count, highlightedCount) + + // home (popup open) + keyClick(Qt.Key_Home) + compare(control.currentIndex, 0) + compare(currentIndexSpy.count, currentIndexCount) + + compare(control.highlightedIndex, 0) + compare(highlightedIndexSpy.count, ++highlightedIndexCount) + + compare(activatedSpy.count, activatedCount) + + compare(highlightedSpy.count, ++highlightedCount) + compare(highlightedSpy.signalArguments[highlightedCount-1][0], 0) + + // repeat (no changes/signals) + keyClick(Qt.Key_Home) + compare(currentIndexSpy.count, currentIndexCount) + compare(highlightedIndexSpy.count, highlightedIndexCount) + compare(activatedSpy.count, activatedCount) + compare(highlightedSpy.count, highlightedCount) + } + + function test_keySearch() { + var control = createTemporaryObject(comboBox, testCase, {model: ["Banana", "Coco", "Coconut", "Apple", "Cocomuffin"]}) + verify(control) + + control.forceActiveFocus() + verify(control.activeFocus) + + compare(control.currentIndex, 0) + compare(control.currentText, "Banana") + compare(control.highlightedIndex, -1) + + keyPress(Qt.Key_C) + compare(control.currentIndex, 1) + compare(control.currentText, "Coco") + compare(control.highlightedIndex, -1) + + // no match + keyPress(Qt.Key_N) + compare(control.currentIndex, 1) + compare(control.currentText, "Coco") + compare(control.highlightedIndex, -1) + + keyPress(Qt.Key_C) + compare(control.currentIndex, 2) + compare(control.currentText, "Coconut") + compare(control.highlightedIndex, -1) + + keyPress(Qt.Key_C) + compare(control.currentIndex, 4) + compare(control.currentText, "Cocomuffin") + compare(control.highlightedIndex, -1) + + // wrap + keyPress(Qt.Key_C) + compare(control.currentIndex, 1) + compare(control.currentText, "Coco") + compare(control.highlightedIndex, -1) + + keyPress(Qt.Key_A) + compare(control.currentIndex, 3) + compare(control.currentText, "Apple") + compare(control.highlightedIndex, -1) + + keyPress(Qt.Key_B) + compare(control.currentIndex, 0) + compare(control.currentText, "Banana") + compare(control.highlightedIndex, -1) + + // popup + control.popup.open() + tryCompare(control.popup, "opened", true) + + compare(control.currentIndex, 0) + compare(control.highlightedIndex, 0) + + keyClick(Qt.Key_C) + compare(control.highlightedIndex, 1) // "Coco" + compare(control.currentIndex, 0) + + // no match + keyClick(Qt.Key_N) + compare(control.highlightedIndex, 1) + compare(control.currentIndex, 0) + + keyClick(Qt.Key_C) + compare(control.highlightedIndex, 2) // "Coconut" + compare(control.currentIndex, 0) + + keyClick(Qt.Key_C) + compare(control.highlightedIndex, 4) // "Cocomuffin" + compare(control.currentIndex, 0) + + // wrap + keyClick(Qt.Key_C) + compare(control.highlightedIndex, 1) // "Coco" + compare(control.currentIndex, 0) + + keyClick(Qt.Key_B) + compare(control.highlightedIndex, 0) // "Banana" + compare(control.currentIndex, 0) + + keyClick(Qt.Key_A) + compare(control.highlightedIndex, 3) // "Apple" + compare(control.currentIndex, 0) + + verify(control.popup.visible) + + // accept + keyClick(Qt.Key_Return) + tryCompare(control.popup, "visible", false) + compare(control.currentIndex, 3) + compare(control.currentText, "Apple") + compare(control.highlightedIndex, -1) + } + + function test_popup() { + var control = createTemporaryObject(comboBox, testCase, {model: 3}) + verify(control) + + // show below + mousePress(control) + compare(control.pressed, true) + compare(control.popup.visible, false) + mouseRelease(control) + compare(control.pressed, false) + compare(control.popup.visible, true) + verify(control.popup.contentItem.y >= control.y) + + // hide + mouseClick(control) + compare(control.pressed, false) + tryCompare(control.popup, "visible", false) + + // show above + control.y = control.Window.height - control.height + mousePress(control) + compare(control.pressed, true) + compare(control.popup.visible, false) + mouseRelease(control) + compare(control.pressed, false) + compare(control.popup.visible, true) + verify(control.popup.contentItem.y < control.y) + + + // Account for when a transition of a scale from 0.9-1.0 that it is placed above right away and not below + // first just because there is room at the 0.9 scale + if (control.popup.enter !== null) { + // hide + mouseClick(control) + compare(control.pressed, false) + tryCompare(control.popup, "visible", false) + control.y = control.Window.height - (control.popup.contentItem.height * 0.99) + var popupYSpy = createTemporaryObject(signalSpy, testCase, {target: control.popup, signalName: "yChanged"}) + verify(popupYSpy.valid) + mousePress(control) + compare(control.pressed, true) + compare(control.popup.visible, false) + mouseRelease(control) + compare(control.pressed, false) + compare(control.popup.visible, true) + tryCompare(control.popup.enter, "running", false) + verify(control.popup.contentItem.y < control.y) + verify(popupYSpy.count === 1) + } + + // follow the control outside the horizontal window bounds + control.x = -control.width / 2 + compare(control.x, -control.width / 2) + compare(control.popup.contentItem.parent.x, -control.width / 2) + control.x = testCase.width - control.width / 2 + compare(control.x, testCase.width - control.width / 2) + compare(control.popup.contentItem.parent.x, testCase.width - control.width / 2) + + // close the popup when hidden (QTBUG-67684) + control.popup.open() + tryCompare(control.popup, "opened", true) + control.visible = false + tryCompare(control.popup, "visible", false) + } + + Component { + id: reopenCombo + Window { + property alias innerCombo: innerCombo + visible: true + width: 300 + height: 300 + ComboBox { + id: innerCombo + model: 10 + anchors.verticalCenter: parent.verticalCenter + } + } + } + + // This test checks that when reopening the combobox that it is still appears at the same y position as + // previously + function test_reopen_popup() { + var control = createTemporaryObject(reopenCombo, testCase) + verify(control) + var y = 0; + for (var i = 0; i < 2; ++i) { + tryCompare(control.innerCombo.popup, "visible", false) + control.innerCombo.y = control.height - (control.innerCombo.popup.contentItem.height * 0.99) + var popupYSpy = createTemporaryObject(signalSpy, testCase, {target: control.innerCombo.popup, signalName: "yChanged"}) + verify(popupYSpy.valid) + mousePress(control.innerCombo) + compare(control.innerCombo.pressed, true) + compare(control.innerCombo.popup.visible, false) + mouseRelease(control.innerCombo) + compare(control.innerCombo.pressed, false) + compare(control.innerCombo.popup.visible, true) + if (control.innerCombo.popup.enter) + tryCompare(control.innerCombo.popup.enter, "running", false) + // Check on the second opening that it has the same y position as before + if (i !== 0) { + // y should not have changed again + verify(popupYSpy.count === 0) + verify(y === control.innerCombo.popup.y) + } else { + // In some cases on the initial show, y changes more than once + verify(popupYSpy.count >= 1) + y = control.innerCombo.popup.y + mouseClick(control.innerCombo) + compare(control.innerCombo.pressed, false) + tryCompare(control.innerCombo.popup, "visible", false) + } + } + } + + function test_mouse() { + var control = createTemporaryObject(comboBox, testCase, {model: 3, hoverEnabled: false}) + verify(control) + + var activatedSpy = signalSpy.createObject(control, {target: control, signalName: "activated"}) + verify(activatedSpy.valid) + + mouseClick(control) + compare(control.popup.visible, true) + + var content = control.popup.contentItem + waitForRendering(content) + + // press - move - release outside - not activated - not closed + mousePress(content) + compare(activatedSpy.count, 0) + mouseMove(content, content.width * 2) + compare(activatedSpy.count, 0) + mouseRelease(content, content.width * 2) + compare(activatedSpy.count, 0) + compare(control.popup.visible, true) + + // press - move - release inside - activated - closed + mousePress(content) + compare(activatedSpy.count, 0) + mouseMove(content, content.width / 2 + 1, content.height / 2 + 1) + compare(activatedSpy.count, 0) + mouseRelease(content) + compare(activatedSpy.count, 1) + tryCompare(control.popup, "visible", false) + } + + function test_touch() { + var control = createTemporaryObject(comboBox, testCase, {model: 3}) + verify(control) + + var touch = touchEvent(control) + + var activatedSpy = signalSpy.createObject(control, {target: control, signalName: "activated"}) + verify(activatedSpy.valid) + + var highlightedSpy = signalSpy.createObject(control, {target: control, signalName: "highlighted"}) + verify(highlightedSpy.valid) + + touch.press(0, control).commit() + touch.release(0, control).commit() + compare(control.popup.visible, true) + + var content = control.popup.contentItem + waitForRendering(content) + + // press - move - release outside - not activated - not closed + touch.press(0, control).commit() + compare(activatedSpy.count, 0) + compare(highlightedSpy.count, 0) + touch.move(0, control, control.width * 2, control.height / 2).commit() + compare(activatedSpy.count, 0) + compare(highlightedSpy.count, 0) + touch.release(0, control, control.width * 2, control.height / 2).commit() + compare(activatedSpy.count, 0) + compare(highlightedSpy.count, 0) + compare(control.popup.visible, true) + + // press - move - release inside - activated - closed + touch.press(0, content).commit() + compare(activatedSpy.count, 0) + compare(highlightedSpy.count, 0) + touch.move(0, content, content.width / 2 + 1, content.height / 2 + 1).commit() + compare(activatedSpy.count, 0) + compare(highlightedSpy.count, 0) + touch.release(0, content).commit() + compare(activatedSpy.count, 1) + compare(highlightedSpy.count, 1) + tryCompare(control.popup, "visible", false) + } + + function test_down() { + var control = createTemporaryObject(comboBox, testCase, {model: 3}) + verify(control) + + // some styles position the popup over the combo button. move it out + // of the way to avoid stealing mouse presses. we want to test the + // combinations of the button being pressed and the popup being visible. + control.popup.y = control.height + + var downSpy = signalSpy.createObject(control, {target: control, signalName: "downChanged"}) + verify(downSpy.valid) + + var pressedSpy = signalSpy.createObject(control, {target: control, signalName: "pressedChanged"}) + verify(pressedSpy.valid) + + mousePress(control) + compare(control.popup.visible, false) + compare(control.pressed, true) + compare(control.down, true) + compare(downSpy.count, 1) + compare(pressedSpy.count, 1) + + mouseRelease(control) + compare(control.popup.visible, true) + compare(control.pressed, false) + compare(control.down, true) + compare(downSpy.count, 3) + compare(pressedSpy.count, 2) + + compare(control.popup.y, control.height) + + control.down = false + compare(control.down, false) + compare(downSpy.count, 4) + + mousePress(control) + compare(control.popup.visible, true) + compare(control.pressed, true) + compare(control.down, false) // explicit false + compare(downSpy.count, 4) + compare(pressedSpy.count, 3) + + control.down = undefined + compare(control.down, true) + compare(downSpy.count, 5) + + mouseRelease(control) + tryCompare(control.popup, "visible", false) + compare(control.pressed, false) + compare(control.down, false) + compare(downSpy.count, 6) + compare(pressedSpy.count, 4) + + control.popup.open() + compare(control.popup.visible, true) + compare(control.pressed, false) + compare(control.down, true) + compare(downSpy.count, 7) + compare(pressedSpy.count, 4) + + control.popup.close() + tryCompare(control.popup, "visible", false) + compare(control.pressed, false) + compare(control.down, false) + compare(downSpy.count, 8) + compare(pressedSpy.count, 4) + } + + function test_focus() { + var control = createTemporaryObject(comboBox, testCase, {model: 3}) + verify(control) + + var openedSpy = signalSpy.createObject(control, {target: control.popup, signalName: "opened"}) + verify(openedSpy.valid) + + var closedSpy = signalSpy.createObject(control, {target: control.popup, signalName: "closed"}) + verify(openedSpy.valid) + + // click - gain focus - show popup + mouseClick(control) + verify(control.activeFocus) + openedSpy.wait() + compare(openedSpy.count, 1) + compare(control.popup.visible, true) + + // lose focus - hide popup + control.focus = false + verify(!control.activeFocus) + closedSpy.wait() + compare(closedSpy.count, 1) + compare(control.popup.visible, false) + } + + function test_baseline() { + var control = createTemporaryObject(comboBox, testCase) + verify(control) + compare(control.baselineOffset, control.contentItem.y + control.contentItem.baselineOffset) + } + + Component { + id: displayBox + ComboBox { + textRole: "key" + model: ListModel { + ListElement { key: "First"; value: 123 } + ListElement { key: "Second"; value: 456 } + ListElement { key: "Third"; value: 789 } + } + } + } + + function test_displayText() { + var control = createTemporaryObject(displayBox, testCase) + verify(control) + + compare(control.displayText, "First") + control.currentIndex = 1 + compare(control.displayText, "Second") + control.textRole = "value" + compare(control.displayText, "456") + control.displayText = "Display" + compare(control.displayText, "Display") + control.currentIndex = 2 + compare(control.displayText, "Display") + control.displayText = undefined + compare(control.displayText, "789") + } + + Component { + id: component + Pane { + id: panel + property alias button: _button; + property alias combobox: _combobox; + font.pixelSize: 30 + Column { + Button { + id: _button + text: "Button" + font.pixelSize: 20 + } + ComboBox { + id: _combobox + model: ["ComboBox", "With"] + delegate: ItemDelegate { + width: _combobox.width + text: _combobox.textRole ? (Array.isArray(_combobox.model) ? modelData[_combobox.textRole] : model[_combobox.textRole]) : modelData + objectName: "delegate" + autoExclusive: true + checked: _combobox.currentIndex === index + highlighted: _combobox.highlightedIndex === index + } + } + } + } + } + + function getChild(control, objname, idx) { + var index = idx + for (var i = index+1; i < control.children.length; i++) + { + if (control.children[i].objectName === objname) { + index = i + break + } + } + return index + } + + function test_font() { // QTBUG_50984, QTBUG-51696 + var control = createTemporaryObject(component, testCase) + verify(control) + verify(control.button) + verify(control.combobox) + + compare(control.font.pixelSize, 30) + compare(control.button.font.pixelSize, 20) + compare(control.combobox.font.pixelSize, 30) + +// verify(control.combobox.popup) +// var popup = control.combobox.popup +// popup.open() + +// verify(popup.contentItem) + +// var listview = popup.contentItem +// verify(listview.contentItem) +// waitForRendering(listview) + +// var idx1 = getChild(listview.contentItem, "delegate", -1) +// compare(listview.contentItem.children[idx1].font.pixelSize, 25) +// var idx2 = getChild(listview.contentItem, "delegate", idx1) +// compare(listview.contentItem.children[idx2].font.pixelSize, 25) + +// compare(listview.contentItem.children[idx1].font.pixelSize, 25) +// compare(listview.contentItem.children[idx2].font.pixelSize, 25) + + control.font.pixelSize = control.font.pixelSize + 10 + compare(control.combobox.font.pixelSize, 40) +// waitForRendering(listview) +// compare(listview.contentItem.children[idx1].font.pixelSize, 25) +// compare(listview.contentItem.children[idx2].font.pixelSize, 25) + + control.combobox.font.pixelSize = control.combobox.font.pixelSize + 5 + compare(control.combobox.font.pixelSize, 45) +// waitForRendering(listview) + +// idx1 = getChild(listview.contentItem, "delegate", -1) +// compare(listview.contentItem.children[idx1].font.pixelSize, 25) +// idx2 = getChild(listview.contentItem, "delegate", idx1) +// compare(listview.contentItem.children[idx2].font.pixelSize, 25) + } + + function test_wheel() { + var ma = createTemporaryObject(mouseArea, testCase, {width: 100, height: 100}) + verify(ma) + + var control = comboBox.createObject(ma, {model: 2, wheelEnabled: true}) + verify(control) + + var delta = 120 + + var spy = signalSpy.createObject(ma, {target: ma, signalName: "wheel"}) + verify(spy.valid) + + mouseWheel(control, control.width / 2, control.height / 2, -delta, -delta) + compare(control.currentIndex, 1) + compare(spy.count, 0) // no propagation + + // reached bounds -> no change + mouseWheel(control, control.width / 2, control.height / 2, -delta, -delta) + compare(control.currentIndex, 1) + compare(spy.count, 0) // no propagation + + mouseWheel(control, control.width / 2, control.height / 2, delta, delta) + compare(control.currentIndex, 0) + compare(spy.count, 0) // no propagation + + // reached bounds -> no change + mouseWheel(control, control.width / 2, control.height / 2, delta, delta) + compare(control.currentIndex, 0) + compare(spy.count, 0) // no propagation + } + + function test_activation_data() { + return [ + { tag: "open:enter", key: Qt.Key_Enter, open: true }, + { tag: "open:return", key: Qt.Key_Return, open: true }, + { tag: "closed:enter", key: Qt.Key_Enter, open: false }, + { tag: "closed:return", key: Qt.Key_Return, open: false } + ] + } + + // QTBUG-51645 + function test_activation(data) { + var control = createTemporaryObject(comboBox, testCase, {currentIndex: 1, model: ["Apple", "Orange", "Banana"]}) + verify(control) + + control.forceActiveFocus() + verify(control.activeFocus) + + if (data.open) { + var openedSpy = signalSpy.createObject(control, {target: control.popup, signalName: "opened"}) + verify(openedSpy.valid) + + keyClick(Qt.Key_Space) + openedSpy.wait() + compare(openedSpy.count, 1) + } + compare(control.popup.visible, data.open) + + compare(control.currentIndex, 1) + compare(control.currentText, "Orange") + compare(control.displayText, "Orange") + + keyClick(data.key) + + compare(control.currentIndex, 1) + compare(control.currentText, "Orange") + compare(control.displayText, "Orange") + } + + Component { + id: asyncLoader + Loader { + active: false + asynchronous: true + sourceComponent: ComboBox { + model: ["First", "Second", "Third"] + } + } + } + + // QTBUG-51972 + function test_async() { + var loader = createTemporaryObject(asyncLoader, testCase) + verify(loader) + + loader.active = true + tryCompare(loader, "status", Loader.Ready) + verify(loader.item) + compare(loader.item.currentText, "First") + compare(loader.item.displayText, "First") + } + + // QTBUG-52615 + function test_currentIndex() { + var control = createTemporaryObject(comboBox, testCase, {currentIndex: -1, model: 3}) + verify(control) + + compare(control.currentIndex, -1) + } + + ListModel { + id: resetmodel + ListElement { text: "First" } + ListElement { text: "Second" } + ListElement { text: "Third" } + } + + // QTBUG-54573 + function test_modelReset() { + var control = createTemporaryObject(comboBox, testCase, {model: resetmodel}) + verify(control) + control.popup.open() + + var listview = control.popup.contentItem + verify(listview) + + tryCompare(listview.contentItem.children, "length", resetmodel.count + 1) // + highlight item + + resetmodel.clear() + resetmodel.append({text: "Fourth"}) + resetmodel.append({text: "Fifth"}) + + tryCompare(listview.contentItem.children, "length", resetmodel.count + 1) // + highlight item + } + + // QTBUG-55118 + function test_currentText() { + var control = createTemporaryObject(comboBox, testCase, {model: listmodel}) + verify(control) + + compare(control.currentIndex, 0) + compare(control.currentText, "First") + + listmodel.setProperty(0, "text", "1st") + compare(control.currentText, "1st") + + control.currentIndex = 1 + compare(control.currentText, "Second") + + listmodel.setProperty(0, "text", "First") + compare(control.currentText, "Second") + } + + // QTBUG-55030 + function test_highlightRange() { + var control = createTemporaryObject(comboBox, testCase, {model: 100}) + verify(control) + + control.currentIndex = 50 + compare(control.currentIndex, 50) + compare(control.highlightedIndex, -1) + + var openedSpy = signalSpy.createObject(control, {target: control.popup, signalName: "opened"}) + verify(openedSpy.valid) + + control.popup.open() + compare(control.highlightedIndex, 50) + tryCompare(openedSpy, "count", 1) + + var listview = control.popup.contentItem + verify(listview) + + var first = listview.itemAt(0, listview.contentY) + verify(first) + compare(first.text, "50") + + var closedSpy = signalSpy.createObject(control, {target: control.popup, signalName: "closed"}) + verify(closedSpy.valid) + + control.popup.close() + tryCompare(closedSpy, "count", 1) + compare(control.highlightedIndex, -1) + + control.currentIndex = 99 + compare(control.currentIndex, 99) + compare(control.highlightedIndex, -1) + + control.popup.open() + compare(control.highlightedIndex, 99) + tryCompare(openedSpy, "count", 2) + tryVerify(function() { return listview.height > 0 }) + + var last = listview.itemAt(0, listview.contentY + listview.height - 1) + verify(last) + compare(last.text, "99") + + openedSpy.target = null + closedSpy.target = null + } + + function test_mouseHighlight() { + if ((Qt.platform.pluginName === "offscreen") + || (Qt.platform.pluginName === "minimal")) + skip("Mouse highlight not functional on offscreen/minimal platforms") + var control = createTemporaryObject(comboBox, testCase, {model: 20}) + verify(control) + + compare(control.highlightedIndex, -1) + + var openedSpy = signalSpy.createObject(control, {target: control.popup, signalName: "opened"}) + verify(openedSpy.valid) + + control.popup.open() + compare(control.highlightedIndex, 0) + tryCompare(openedSpy, "count", 1) + + var listview = control.popup.contentItem + verify(listview) + waitForRendering(listview) + + // hover-highlight through all visible list items one by one + var hoverIndex = -1 + var prevHoverItem = null + for (var y = 0; y < listview.height; ++y) { + var hoverItem = listview.itemAt(0, listview.contentY + y) + if (!hoverItem || !hoverItem.visible || hoverItem === prevHoverItem) + continue + mouseMove(hoverItem, 0, 0) + tryCompare(control, "highlightedIndex", ++hoverIndex) + prevHoverItem = hoverItem + } + + mouseMove(listview, listview.width / 2, listview.height / 2) + + // wheel-highlight the rest of the items + var delta = 120 + var prevWheelItem = null + while (!listview.atYEnd) { + var prevContentY = listview.contentY + mouseWheel(listview, listview.width / 2, listview.height / 2, -delta, -delta) + tryCompare(listview, "moving", false) + verify(listview.contentY > prevContentY) + + var wheelItem = listview.itemAt(listview.width / 2, listview.contentY + listview.height / 2) + if (!wheelItem || !wheelItem.visible || wheelItem === prevWheelItem) + continue + + tryCompare(control, "highlightedIndex", parseInt(wheelItem.text)) + prevWheelItem = wheelItem + } + } + + RegularExpressionValidator { + id: regExpValidator + regularExpression: /(red|blue|green)?/ + } + + function test_validator() { + var control = createTemporaryObject(comboBox, testCase, {editable: true, validator: regExpValidator}) + + control.editText = "blu" + compare(control.acceptableInput, false) + control.editText = "blue" + compare(control.acceptableInput, true) + control.editText = "bluee" + compare(control.acceptableInput, false) + control.editText = "" + compare(control.acceptableInput, true) + control.editText = "" + control.contentItem.forceActiveFocus() + keyPress(Qt.Key_A) + compare(control.editText, "") + keyPress(Qt.Key_A) + compare(control.editText, "") + keyPress(Qt.Key_R) + compare(control.editText, "r") + keyPress(Qt.Key_A) + compare(control.editText, "r") + compare(control.acceptableInput, false) + keyPress(Qt.Key_E) + compare(control.editText, "re") + compare(control.acceptableInput, false) + keyPress(Qt.Key_D) + compare(control.editText, "red") + compare(control.acceptableInput, true) + } + + Component { + id: appendFindBox + ComboBox { + editable: true + model: ListModel { + ListElement { text:"first" } + } + onAccepted: { + if (find(editText) === -1) + model.append({text: editText}) + } + } + } + + function test_append_find() { + var control = createTemporaryObject(appendFindBox, testCase) + + compare(control.currentIndex, 0) + compare(control.currentText, "first") + control.contentItem.forceActiveFocus() + compare(control.activeFocus, true) + + control.selectAll() + keyPress(Qt.Key_T) + keyPress(Qt.Key_H) + keyPress(Qt.Key_I) + keyPress(Qt.Key_R) + keyPress(Qt.Key_D) + compare(control.count, 1) + compare(control.currentText, "first") + compare(control.editText, "third") + + keyPress(Qt.Key_Enter) + compare(control.count, 2) + compare(control.currentIndex, 1) + compare(control.currentText, "third") + } + + function test_editable() { + var control = createTemporaryObject(comboBox, testCase, {editable: true, model: ["Banana", "Coco", "Coconut", "Apple", "Cocomuffin"]}) + verify(control) + + control.contentItem.forceActiveFocus() + verify(control.activeFocus) + + var acceptCount = 0 + + var acceptSpy = signalSpy.createObject(control, {target: control, signalName: "accepted"}) + verify(acceptSpy.valid) + + compare(control.editText, "Banana") + compare(control.currentText, "Banana") + compare(control.currentIndex, 0) + compare(acceptSpy.count, 0) + control.editText = "" + + keyPress(Qt.Key_C) + compare(control.editText, "coco") + compare(control.currentText, "Banana") + compare(control.currentIndex, 0) + + keyPress(Qt.Key_Right) + keyPress(Qt.Key_N) + compare(control.editText, "coconut") + compare(control.currentText, "Banana") + compare(control.currentIndex, 0) + + keyPress(Qt.Key_Enter) // Accept + compare(control.editText, "Coconut") + compare(control.currentText, "Coconut") + compare(control.currentIndex, 2) + compare(acceptSpy.count, ++acceptCount) + + keyPress(Qt.Key_Backspace) + keyPress(Qt.Key_Backspace) + keyPress(Qt.Key_Backspace) + keyPress(Qt.Key_M) + compare(control.editText, "Cocomuffin") + compare(control.currentText, "Coconut") + compare(control.currentIndex, 2) + + keyPress(Qt.Key_Enter) // Accept + compare(control.editText, "Cocomuffin") + compare(control.currentText, "Cocomuffin") + compare(control.currentIndex, 4) + compare(acceptSpy.count, ++acceptCount) + + keyPress(Qt.Key_Return) // Accept + compare(control.editText, "Cocomuffin") + compare(control.currentText, "Cocomuffin") + compare(control.currentIndex, 4) + compare(acceptSpy.count, ++acceptCount) + + control.editText = "" + compare(control.editText, "") + compare(control.currentText, "Cocomuffin") + compare(control.currentIndex, 4) + + keyPress(Qt.Key_A) + compare(control.editText, "apple") + compare(control.currentText, "Cocomuffin") + compare(control.currentIndex, 4) + + keyPress(Qt.Key_Return) // Accept + compare(control.editText, "Apple") + compare(control.currentText, "Apple") + compare(control.currentIndex, 3) + compare(acceptSpy.count, ++acceptCount) + + control.editText = "" + keyPress(Qt.Key_A) + keyPress(Qt.Key_B) + compare(control.editText, "ab") + compare(control.currentText, "Apple") + compare(control.currentIndex, 3) + + keyPress(Qt.Key_Return) // Accept + compare(control.editText, "ab") + compare(control.currentText, "") + compare(control.currentIndex, -1) + compare(acceptSpy.count, ++acceptCount) + + control.editText = "" + compare(control.editText, "") + compare(control.currentText, "") + compare(control.currentIndex, -1) + + keyPress(Qt.Key_C) + keyPress(Qt.Key_Return) // Accept + compare(control.editText, "Coco") + compare(control.currentText, "Coco") + compare(control.currentIndex, 1) + compare(acceptSpy.count, ++acceptCount) + + keyPress(Qt.Key_Down) + compare(control.editText, "Coconut") + compare(control.currentText, "Coconut") + compare(control.currentIndex, 2) + + keyPress(Qt.Key_Up) + compare(control.editText, "Coco") + compare(control.currentText, "Coco") + compare(control.currentIndex, 1) + + control.editText = "" + compare(control.editText, "") + compare(control.currentText, "Coco") + compare(control.currentIndex, 1) + + keyPress(Qt.Key_C) + keyPress(Qt.Key_O) + keyPress(Qt.Key_C) // autocompletes "coco" + keyPress(Qt.Key_Backspace) + keyPress(Qt.Key_Return) // Accept "coc" + compare(control.editText, "coc") + compare(control.currentText, "") + compare(control.currentIndex, -1) + compare(acceptSpy.count, ++acceptCount) + + control.editText = "" + compare(control.editText, "") + compare(control.currentText, "") + compare(control.currentIndex, -1) + + keyPress(Qt.Key_C) + keyPress(Qt.Key_O) + keyPress(Qt.Key_C) // autocompletes "coc" + keyPress(Qt.Key_Space) + keyPress(Qt.Key_Return) // Accept "coc " + compare(control.editText, "coc ") + compare(control.currentText, "") + compare(control.currentIndex, -1) + compare(acceptSpy.count, ++acceptCount) + } + + Component { + id: keysAttachedBox + ComboBox { + editable: true + property bool gotit: false + Keys.onPressed: { + if (!gotit && event.key === Qt.Key_B) { + gotit = true + event.accepted = true + } + } + } + } + + function test_keys_attached() { + var control = createTemporaryObject(keysAttachedBox, testCase) + verify(control) + + control.contentItem.forceActiveFocus() + verify(control.activeFocus) + + verify(!control.gotit) + compare(control.editText, "") + + keyPress(Qt.Key_A) + verify(control.activeFocus) + verify(!control.gotit) + compare(control.editText, "a") + + keyPress(Qt.Key_B) + verify(control.activeFocus) + verify(control.gotit) + compare(control.editText, "a") + + keyPress(Qt.Key_B) + verify(control.activeFocus) + verify(control.gotit) + compare(control.editText, "ab") + } + + function test_minusOneIndexResetsSelection_QTBUG_35794_data() { + return [ + { tag: "editable", editable: true }, + { tag: "non-editable", editable: false } + ] + } + + function test_minusOneIndexResetsSelection_QTBUG_35794(data) { + var control = createTemporaryObject(comboBox, testCase, {editable: data.editable, model: ["A", "B", "C"]}) + verify(control) + + compare(control.currentIndex, 0) + compare(control.currentText, "A") + control.currentIndex = -1 + compare(control.currentIndex, -1) + compare(control.currentText, "") + control.currentIndex = 1 + compare(control.currentIndex, 1) + compare(control.currentText, "B") + } + + function test_minusOneToZeroSelection_QTBUG_38036() { + var control = createTemporaryObject(comboBox, testCase, {model: ["A", "B", "C"]}) + verify(control) + + compare(control.currentIndex, 0) + compare(control.currentText, "A") + control.currentIndex = -1 + compare(control.currentIndex, -1) + compare(control.currentText, "") + control.currentIndex = 0 + compare(control.currentIndex, 0) + compare(control.currentText, "A") + } + + function test_emptyPopupAfterModelCleared() { + var control = createTemporaryObject(comboBox, testCase, { model: 1 }) + verify(control) + compare(control.popup.implicitHeight, 0) + + // Ensure that it's open so that the popup's implicitHeight changes when we increase the model count. + control.popup.open() + tryCompare(control.popup, "visible", true) + + // Add lots of items to the model. The popup should take up the entire height of the window. + control.model = 100 + compare(control.popup.height, control.Window.height - control.popup.topMargin - control.popup.bottomMargin) + + control.popup.close() + + // Clearing the model should result in a zero height. + control.model = 0 + control.popup.open() + tryCompare(control.popup, "visible", true) + compare(control.popup.height, control.popup.topPadding + control.popup.bottomPadding) + } + + Component { + id: keysMonitor + Item { + property int pressedKeys: 0 + property int releasedKeys: 0 + property int lastPressedKey: 0 + property int lastReleasedKey: 0 + property alias comboBox: comboBox + + width: 200 + height: 200 + + Keys.onPressed: { ++pressedKeys; lastPressedKey = event.key } + Keys.onReleased: { ++releasedKeys; lastReleasedKey = event.key } + + ComboBox { + id: comboBox + } + } + } + + function test_keyClose_data() { + return [ + { tag: "Escape", key: Qt.Key_Escape }, + { tag: "Back", key: Qt.Key_Back } + ] + } + + function test_keyClose(data) { + var container = createTemporaryObject(keysMonitor, testCase) + verify(container) + + var control = comboBox.createObject(container) + verify(control) + + control.forceActiveFocus() + verify(control.activeFocus) + + var pressedKeys = 0 + var releasedKeys = 0 + + // popup not visible -> propagates + keyPress(data.key) + compare(container.pressedKeys, ++pressedKeys) + compare(container.lastPressedKey, data.key) + + keyRelease(data.key) + compare(container.releasedKeys, ++releasedKeys) + compare(container.lastReleasedKey, data.key) + + verify(control.activeFocus) + + // popup visible -> handled -> does not propagate + control.popup.open() + tryCompare(control.popup, "opened", true) + + keyPress(data.key) + compare(container.pressedKeys, pressedKeys) + + keyRelease(data.key) + // Popup receives the key release event if it has an exit transition, but + // not if it has been immediately closed on press, without a transition. + // ### TODO: Should Popup somehow always block the key release event? + if (!control.popup.exit) + ++releasedKeys + compare(container.releasedKeys, releasedKeys) + + tryCompare(control.popup, "visible", false) + verify(control.activeFocus) + + // popup not visible -> propagates + keyPress(data.key) + compare(container.pressedKeys, ++pressedKeys) + compare(container.lastPressedKey, data.key) + + keyRelease(data.key) + compare(container.releasedKeys, ++releasedKeys) + compare(container.lastReleasedKey, data.key) + } + + function test_popupFocus_QTBUG_74661() { + var control = createTemporaryObject(comboBox, testCase) + verify(control) + + var popup = createTemporaryObject(customPopup, testCase) + verify(popup) + + control.popup = popup + + var openedSpy = signalSpy.createObject(control, {target: popup, signalName: "opened"}) + verify(openedSpy.valid) + + var closedSpy = signalSpy.createObject(control, {target: popup, signalName: "closed"}) + verify(closedSpy.valid) + + control.forceActiveFocus() + verify(control.activeFocus) + + // show popup + keyClick(Qt.Key_Space) + openedSpy.wait() + compare(openedSpy.count, 1) + + popup.contentItem.forceActiveFocus() + verify(popup.contentItem.activeFocus) + + // type something in the text field + keyClick(Qt.Key_Space) + keyClick(Qt.Key_H) + keyClick(Qt.Key_I) + compare(popup.contentItem.text, " hi") + + compare(closedSpy.count, 0) + + // hide popup + keyClick(Qt.Key_Escape) + closedSpy.wait() + compare(closedSpy.count, 1) + } + + function test_comboBoxWithShaderEffect() { + var control = createTemporaryObject(comboBoxWithShaderEffect, testCase, {model: 9}) + verify(control) + waitForRendering(control) + control.forceActiveFocus() + var openedSpy = signalSpy.createObject(control, {target: control.popup, signalName: "opened"}) + verify(openedSpy.valid) + + var closedSpy = signalSpy.createObject(control, {target: control.popup, signalName: "closed"}) + verify(closedSpy.valid) + + control.popup.open() + openedSpy.wait() + compare(openedSpy.count, 1) + control.popup.close() + closedSpy.wait() + compare(closedSpy.count, 1) + } + + function test_comboBoxSelectTextByMouse() { + let control = createTemporaryObject(comboBox, testCase, + { editable: true, selectTextByMouse: true, model: [ "Some text" ], width: parent.width }) + verify(control) + waitForRendering(control) + control.forceActiveFocus() + + // Position the text cursor at the beginning of the text. + mouseClick(control, control.leftPadding, control.height / 2) + // Select all of the text. + mousePress(control, control.leftPadding, control.height / 2) + mouseMove(control, control.leftPadding + control.contentItem.width, control.height / 2) + mouseRelease(control, control.leftPadding + control.contentItem.width, control.height / 2) + compare(control.contentItem.selectedText, "Some text") + } + + // QTBUG-78885: When the edit text is changed on an editable ComboBox, + // and then that ComboBox loses focus, its currentIndex should change + // to the index of the edit text (assuming a match is found). + function test_currentIndexChangeOnLostFocus() { + if (Qt.styleHints.tabFocusBehavior !== Qt.TabFocusAllControls) + skip("This platform only allows tab focus for text controls") + + let theModel = [] + for (let i = 0; i < 10; ++i) + theModel.push("Item " + (i + 1)) + + let comboBox1 = createTemporaryObject(comboBox, testCase, + { objectName: "comboBox1", editable: true, model: theModel }) + verify(comboBox1) + compare(comboBox1.currentIndex, 0) + + let comboBox2 = createTemporaryObject(comboBox, testCase, { objectName: "comboBox2" }) + verify(comboBox2) + + // Give the first ComboBox focus and type in 0 to select "Item 10" (default is "Item 1"). + waitForRendering(comboBox1) + comboBox1.contentItem.forceActiveFocus() + verify(comboBox1.activeFocus) + keyClick(Qt.Key_0) + compare(comboBox1.editText, "Item 10") + + let currentIndexSpy = signalSpy.createObject(comboBox1, + { target: comboBox1, signalName: "currentIndexChanged" }) + verify(currentIndexSpy.valid) + + // Give focus to the other ComboBox so that the first one loses it. + // The first ComboBox's currentIndex should change to that of "Item 10". + keyClick(Qt.Key_Tab) + verify(comboBox2.activeFocus) + compare(comboBox1.currentIndex, 9) + compare(currentIndexSpy.count, 1) + + // Give focus back to the first ComboBox, and try the same thing except + // with non-existing text; the currentIndex should not change. + comboBox1.contentItem.forceActiveFocus() + verify(comboBox1.activeFocus) + keySequence(StandardKey.SelectAll) + compare(comboBox1.contentItem.selectedText, "Item 10") + keyClick(Qt.Key_N) + keyClick(Qt.Key_O) + keyClick(Qt.Key_P) + keyClick(Qt.Key_E) + compare(comboBox1.editText, "nope") + compare(comboBox1.currentIndex, 9) + compare(currentIndexSpy.count, 1) + } + + Component { + id: appFontTextFieldComponent + TextField { + objectName: "appFontTextField" + font: Qt.application.font + // We don't want the background's implicit width to interfere with our tests, + // which are about implicit width of the contentItem of ComboBox, which is by default TextField. + background: null + } + } + + Component { + id: appFontContentItemComboBoxComponent + ComboBox { + // Override the contentItem so that the font doesn't vary between styles. + contentItem: TextField { + objectName: "appFontContentItemTextField" + // We do this just to be extra sure that the font never comes from the control, + // as we want it to match that of the TextField in the appFontTextFieldComponent. + font: Qt.application.font + background: null + } + } + } + + Component { + id: twoItemListModelComponent + + ListModel { + ListElement { display: "Short" } + ListElement { display: "Kinda long" } + } + } + + function appendedToModel(model, item) { + if (Array.isArray(model)) { + let newModel = model + newModel.push(item) + return newModel + } + + if (model.hasOwnProperty("append")) { + model.append({ display: item }) + // To account for the fact that changes to a JS array are not seen by the QML engine, + // we need to reassign the entire model and hence return it. For simplicity in the + // calling code, we do it for the ListModel code path too. It should be a no-op. + return model + } + + console.warn("appendedToModel: unrecognised model") + return undefined + } + + function removedFromModel(model, index, count) { + if (Array.isArray(model)) { + let newModel = model + newModel.splice(index, count) + return newModel + } + + if (model.hasOwnProperty("remove")) { + model.remove(index, count) + return model + } + + console.warn("removedFromModel: unrecognised model") + return undefined + } + + // We don't use a data-driven test for the policy because the checks vary a lot based on which enum we're testing. + function test_implicitContentWidthPolicy_ContentItemImplicitWidth() { + // Set ContentItemImplicitWidth and ensure that implicitContentWidth is as wide as the current item + // by comparing it against the implicitWidth of an identical TextField + let control = createTemporaryObject(appFontContentItemComboBoxComponent, testCase, { + model: ["Short", "Kinda long"], + implicitContentWidthPolicy: ComboBox.ContentItemImplicitWidth + }) + verify(control) + compare(control.implicitContentWidthPolicy, ComboBox.ContentItemImplicitWidth) + + let textField = createTemporaryObject(appFontTextFieldComponent, testCase) + verify(textField) + // Don't set any text on textField because we're not accounting for the widest + // text here, so we want to compare it against an empty TextField. + compare(control.implicitContentWidth, textField.implicitWidth) + + textField.font.pixelSize *= 2 + control.font.pixelSize *= 2 + compare(control.implicitContentWidth, textField.implicitWidth) + } + + function test_implicitContentWidthPolicy_WidestText_data() { + return [ + { tag: "Array", model: ["Short", "Kinda long"] }, + { tag: "ListModel", model: twoItemListModelComponent.createObject(testCase) }, + ] + } + + function test_implicitContentWidthPolicy_WidestText(data) { + let control = createTemporaryObject(appFontContentItemComboBoxComponent, testCase, { + model: data.model, + implicitContentWidthPolicy: ComboBox.WidestText + }) + verify(control) + compare(control.implicitContentWidthPolicy, ComboBox.WidestText) + + let textField = createTemporaryObject(appFontTextFieldComponent, testCase) + verify(textField) + textField.text = "Kinda long" + // Note that we don't need to change the current index here, as the implicitContentWidth + // is set to the implicitWidth of the TextField within the ComboBox as if it had the largest + // text from the model set on it. + // We use Math.ceil because TextInput uses qCeil internally, whereas the implicitWidth + // binding for TextField does not. + compare(Math.ceil(control.implicitContentWidth), Math.ceil(textField.implicitWidth)) + + // Add a longer item; it should affect the implicit content width. + let modifiedModel = appendedToModel(data.model, "Moderately long") + control.model = modifiedModel + textField.text = "Moderately long" + compare(Math.ceil(control.implicitContentWidth), Math.ceil(textField.implicitWidth)) + + // Remove the last two items; it should use the only remaining item's width. + modifiedModel = removedFromModel(data.model, 1, 2) + control.model = modifiedModel + compare(control.count, 1) + compare(control.currentText, "Short") + textField.text = "Short" + compare(Math.ceil(control.implicitContentWidth), Math.ceil(textField.implicitWidth)) + + // Changes in font should result in the implicitContentWidth being updated. + textField.font.pixelSize *= 2 + // We have to change the contentItem's font size manually since we break the + // style's binding to the control's font when we set Qt.application.font to it. + control.contentItem.font.pixelSize *= 2 + control.font.pixelSize *= 2 + compare(Math.ceil(control.implicitContentWidth), Math.ceil(textField.implicitWidth)) + } + + function test_implicitContentWidthPolicy_WidestTextWhenCompleted_data() { + return test_implicitContentWidthPolicy_WidestText_data() + } + + function test_implicitContentWidthPolicy_WidestTextWhenCompleted(data) { + let control = createTemporaryObject(appFontContentItemComboBoxComponent, testCase, { + model: data.model, + implicitContentWidthPolicy: ComboBox.WidestTextWhenCompleted + }) + verify(control) + compare(control.implicitContentWidthPolicy, ComboBox.WidestTextWhenCompleted) + + let textField = createTemporaryObject(appFontTextFieldComponent, testCase) + verify(textField) + textField.text = "Kinda long" + compare(Math.ceil(control.implicitContentWidth), Math.ceil(textField.implicitWidth)) + + // Add a longer item; it should not affect the implicit content width + // since we've already accounted for it once. + let modifiedModel = appendedToModel(data.model, "Moderately long") + control.model = modifiedModel + compare(Math.ceil(control.implicitContentWidth), Math.ceil(textField.implicitWidth)) + + // Remove the last two items; it should still not affect the implicit content width. + modifiedModel = removedFromModel(data.model, 1, 2) + control.model = modifiedModel + compare(control.count, 1) + compare(control.currentText, "Short") + compare(Math.ceil(control.implicitContentWidth), Math.ceil(textField.implicitWidth)) + + // Changes in font should not result in the implicitContentWidth being updated. + let oldTextFieldImplicitWidth = textField.implicitWidth + // Changes in font should result in the implicitContentWidth being updated. + textField.font.pixelSize *= 2 + control.contentItem.font.pixelSize *= 2 + control.font.pixelSize *= 2 + compare(Math.ceil(control.implicitContentWidth), Math.ceil(oldTextFieldImplicitWidth)) + } + + // QTBUG-61021: text line should not be focused by default + // It causes (e.g. on Android) showing virtual keyboard when it is not needed + function test_doNotFocusTextLineByDefault() { + var control = createTemporaryObject(comboBox, testCase) + // Focus not set after creating combobox + verify(!control.activeFocus) + verify(!control.contentItem.focus) + + // After setting focus on combobox, text line should not be focused + control.forceActiveFocus() + verify(control.activeFocus) + verify(!control.contentItem.focus) + + // Text line is focused after intentional setting focus on it + control.contentItem.forceActiveFocus() + verify(control.activeFocus) + verify(control.contentItem.focus) + } + + Component { + id: intValidatorComponent + IntValidator { + bottom: 0 + top: 255 + } + } + + function test_acceptableInput_QTBUG_94307() { + let items = [ + { text: "A" }, + { text: "2" }, + { text: "3" } + ] + let control = createTemporaryObject(comboBox, testCase, {model: items, editable: true}) + verify(control) + + verify(control.acceptableInput) + compare(control.displayText, "A") + + let acceptableInputSpy = signalSpy.createObject(control, {target: control, signalName: "acceptableInputChanged"}) + verify(acceptableInputSpy.valid) + + let intValidator = intValidatorComponent.createObject(testCase) + verify(intValidator) + + control.validator = intValidator + + compare(acceptableInputSpy.count, 1) + compare(control.displayText, "A") + compare(control.acceptableInput, false) + + control.currentIndex = 1 + + compare(acceptableInputSpy.count, 2) + compare(control.displayText, "2") + compare(control.acceptableInput, true) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_container.qml b/tests/auto/quickcontrols2/controls/data/tst_container.qml new file mode 100644 index 0000000000..be2b9a12ba --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_container.qml @@ -0,0 +1,216 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls +import QtQuick.Templates as T + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "Container" + + Component { + id: container + Container { } + } + + Component { + id: rectangle + Rectangle { } + } + + function test_implicitSize() { + var control = createTemporaryObject(container, testCase) + verify(control) + + compare(control.implicitWidth, 0) + compare(control.implicitHeight, 0) + + control.contentItem = rectangle.createObject(control, {implicitWidth: 10, implicitHeight: 20}) + compare(control.implicitWidth, 10) + compare(control.implicitHeight, 20) + + control.background = rectangle.createObject(control, {implicitWidth: 20, implicitHeight: 30}) + compare(control.implicitWidth, 20) + compare(control.implicitHeight, 30) + + control.padding = 100 + compare(control.implicitWidth, 210) + compare(control.implicitHeight, 220) + } + + function test_currentIndex() { + var control1 = createTemporaryObject(container, testCase) + verify(control1) + + var control2 = createTemporaryObject(container, testCase) + verify(control2) + + compare(control1.currentIndex, -1) + compare(control2.currentIndex, -1) + + for (var i = 0; i < 3; ++i) { + control1.addItem(rectangle.createObject(control1)) + control2.addItem(rectangle.createObject(control2)) + } + + compare(control1.count, 3) + compare(control2.count, 3) + compare(control1.currentIndex, 0) + compare(control2.currentIndex, 0) + + control1.currentIndex = Qt.binding(function() { return control2.currentIndex }) + control2.currentIndex = Qt.binding(function() { return control1.currentIndex }) + + control1.setCurrentIndex(1) + compare(control1.currentIndex, 1) + compare(control2.currentIndex, 1) + + control1.incrementCurrentIndex() + compare(control1.currentIndex, 2) + compare(control2.currentIndex, 2) + + control2.decrementCurrentIndex() + compare(control1.currentIndex, 1) + compare(control2.currentIndex, 1) + } + + Component { + id: repeaterContainer1 + Container { + id: container + Item { objectName: "0" } + Item { objectName: "1" } + Item { objectName: "2" } + Item { objectName: "3" } + contentItem: Row { + Repeater { + model: container.contentModel + } + } + } + } + + Component { + id: repeaterContainer2 + Container { + id: container + contentItem: Item { + Repeater { + model: container.contentModel + } + Rectangle { objectName: "extra" } + } + Rectangle { objectName: "0" } + Rectangle { objectName: "1" } + Rectangle { objectName: "2" } + Rectangle { objectName: "3" } + } + } + + function test_repeater_data() { + return [ + { tag: "1", component: repeaterContainer1 }, + { tag: "2", component: repeaterContainer2 } + ] + } + + // don't crash (QTBUG-61310) + function test_repeater(data) { + var control = createTemporaryObject(data.component) + verify(control) + + compare(control.itemAt(0).objectName, "0") + compare(control.itemAt(1).objectName, "1") + compare(control.itemAt(2).objectName, "2") + compare(control.itemAt(3).objectName, "3") + } + + function test_removeTakeItem() { + var control = createTemporaryObject(container, testCase) + verify(control) + + var item1 = rectangle.createObject(control) + var item2 = rectangle.createObject(control) + var item3 = rectangle.createObject(control) + + item1.Component.onDestruction.connect(function() { item1 = null }) + item2.Component.onDestruction.connect(function() { item2 = null }) + item3.Component.onDestruction.connect(function() { item3 = null }) + + control.addItem(item1) + control.addItem(item2) + control.addItem(item3) + compare(control.count, 3) + + // takeItem(int) does not destroy + compare(control.takeItem(1), item2) + compare(control.count, 2) + wait(1) + verify(item2) + + // removeItem(Item) destroys + control.removeItem(item1) + compare(control.count, 1) + wait(1) + verify(!item1) + + // removeItem(null) must not call removeItem(0) + control.removeItem(null) + compare(control.count, 1) + wait(1) + verify(item3) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_control.qml b/tests/auto/quickcontrols2/controls/data/tst_control.qml new file mode 100644 index 0000000000..4afa719ef6 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_control.qml @@ -0,0 +1,1417 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls +import QtQuick.Templates as T + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "Control" + + Component { + id: component + Control { } + } + + Component { + id: rectangle + Rectangle { } + } + + Component { + id: button + T.Button { } + } + + Component { + id: signalSpy + SignalSpy { } + } + + function test_padding() { + var control = createTemporaryObject(component, testCase) + verify(control) + + var paddingSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "paddingChanged"}) + verify(paddingSpy.valid) + + var topPaddingSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "topPaddingChanged"}) + verify(topPaddingSpy.valid) + + var leftPaddingSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "leftPaddingChanged"}) + verify(leftPaddingSpy.valid) + + var rightPaddingSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "rightPaddingChanged"}) + verify(rightPaddingSpy.valid) + + var bottomPaddingSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "bottomPaddingChanged"}) + verify(bottomPaddingSpy.valid) + + var horizontalPaddingSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "horizontalPaddingChanged"}) + verify(horizontalPaddingSpy.valid) + + var verticalPaddingSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "verticalPaddingChanged"}) + verify(verticalPaddingSpy.valid) + + var paddingChanges = 0 + var topPaddingChanges = 0 + var leftPaddingChanges = 0 + var rightPaddingChanges = 0 + var bottomPaddingChanges = 0 + var horizontalPaddingChanges = 0 + var verticalPaddingChanges = 0 + + compare(control.padding, 0) + compare(control.topPadding, 0) + compare(control.leftPadding, 0) + compare(control.rightPadding, 0) + compare(control.bottomPadding, 0) + compare(control.horizontalPadding, 0) + compare(control.verticalPadding, 0) + compare(control.availableWidth, 0) + compare(control.availableHeight, 0) + + control.width = 100 + control.height = 100 + + control.padding = 10 + compare(control.padding, 10) + compare(control.topPadding, 10) + compare(control.leftPadding, 10) + compare(control.rightPadding, 10) + compare(control.bottomPadding, 10) + compare(control.horizontalPadding, 10) + compare(control.verticalPadding, 10) + compare(paddingSpy.count, ++paddingChanges) + compare(topPaddingSpy.count, ++topPaddingChanges) + compare(leftPaddingSpy.count, ++leftPaddingChanges) + compare(rightPaddingSpy.count, ++rightPaddingChanges) + compare(bottomPaddingSpy.count, ++bottomPaddingChanges) + compare(horizontalPaddingSpy.count, ++horizontalPaddingChanges) + compare(verticalPaddingSpy.count, ++verticalPaddingChanges) + + control.topPadding = 20 + compare(control.padding, 10) + compare(control.topPadding, 20) + compare(control.leftPadding, 10) + compare(control.rightPadding, 10) + compare(control.bottomPadding, 10) + compare(control.horizontalPadding, 10) + compare(control.verticalPadding, 10) + compare(paddingSpy.count, paddingChanges) + compare(topPaddingSpy.count, ++topPaddingChanges) + compare(leftPaddingSpy.count, leftPaddingChanges) + compare(rightPaddingSpy.count, rightPaddingChanges) + compare(bottomPaddingSpy.count, bottomPaddingChanges) + compare(horizontalPaddingSpy.count, horizontalPaddingChanges) + compare(verticalPaddingSpy.count, verticalPaddingChanges) + + control.leftPadding = 30 + compare(control.padding, 10) + compare(control.topPadding, 20) + compare(control.leftPadding, 30) + compare(control.rightPadding, 10) + compare(control.bottomPadding, 10) + compare(control.horizontalPadding, 10) + compare(control.verticalPadding, 10) + compare(paddingSpy.count, paddingChanges) + compare(topPaddingSpy.count, topPaddingChanges) + compare(leftPaddingSpy.count, ++leftPaddingChanges) + compare(rightPaddingSpy.count, rightPaddingChanges) + compare(bottomPaddingSpy.count, bottomPaddingChanges) + compare(horizontalPaddingSpy.count, horizontalPaddingChanges) + compare(verticalPaddingSpy.count, verticalPaddingChanges) + + control.rightPadding = 40 + compare(control.padding, 10) + compare(control.topPadding, 20) + compare(control.leftPadding, 30) + compare(control.rightPadding, 40) + compare(control.bottomPadding, 10) + compare(control.horizontalPadding, 10) + compare(control.verticalPadding, 10) + compare(paddingSpy.count, paddingChanges) + compare(topPaddingSpy.count, topPaddingChanges) + compare(leftPaddingSpy.count, leftPaddingChanges) + compare(rightPaddingSpy.count, ++rightPaddingChanges) + compare(bottomPaddingSpy.count, bottomPaddingChanges) + compare(horizontalPaddingSpy.count, horizontalPaddingChanges) + compare(verticalPaddingSpy.count, verticalPaddingChanges) + + control.bottomPadding = 50 + compare(control.padding, 10) + compare(control.topPadding, 20) + compare(control.leftPadding, 30) + compare(control.rightPadding, 40) + compare(control.bottomPadding, 50) + compare(control.horizontalPadding, 10) + compare(control.verticalPadding, 10) + compare(paddingSpy.count, paddingChanges) + compare(topPaddingSpy.count, topPaddingChanges) + compare(leftPaddingSpy.count, leftPaddingChanges) + compare(rightPaddingSpy.count, rightPaddingChanges) + compare(bottomPaddingSpy.count, ++bottomPaddingChanges) + compare(horizontalPaddingSpy.count, horizontalPaddingChanges) + compare(verticalPaddingSpy.count, verticalPaddingChanges) + + control.padding = 60 + compare(control.padding, 60) + compare(control.topPadding, 20) + compare(control.leftPadding, 30) + compare(control.rightPadding, 40) + compare(control.bottomPadding, 50) + compare(control.horizontalPadding, 60) + compare(control.verticalPadding, 60) + compare(paddingSpy.count, ++paddingChanges) + compare(topPaddingSpy.count, topPaddingChanges) + compare(leftPaddingSpy.count, leftPaddingChanges) + compare(rightPaddingSpy.count, rightPaddingChanges) + compare(bottomPaddingSpy.count, bottomPaddingChanges) + compare(horizontalPaddingSpy.count, ++horizontalPaddingChanges) + compare(verticalPaddingSpy.count, ++verticalPaddingChanges) + + control.horizontalPadding = 80 + compare(control.padding, 60) + compare(control.topPadding, 20) + compare(control.leftPadding, 30) + compare(control.rightPadding, 40) + compare(control.bottomPadding, 50) + compare(control.horizontalPadding, 80) + compare(control.verticalPadding, 60) + compare(paddingSpy.count, paddingChanges) + compare(topPaddingSpy.count, topPaddingChanges) + compare(leftPaddingSpy.count, leftPaddingChanges) + compare(rightPaddingSpy.count, rightPaddingChanges) + compare(bottomPaddingSpy.count, bottomPaddingChanges) + compare(horizontalPaddingSpy.count, ++horizontalPaddingChanges) + compare(verticalPaddingSpy.count, verticalPaddingChanges) + + control.verticalPadding = 90 + compare(control.padding, 60) + compare(control.topPadding, 20) + compare(control.leftPadding, 30) + compare(control.rightPadding, 40) + compare(control.bottomPadding, 50) + compare(control.horizontalPadding, 80) + compare(control.verticalPadding, 90) + compare(paddingSpy.count, paddingChanges) + compare(topPaddingSpy.count, topPaddingChanges) + compare(leftPaddingSpy.count, leftPaddingChanges) + compare(rightPaddingSpy.count, rightPaddingChanges) + compare(bottomPaddingSpy.count, bottomPaddingChanges) + compare(horizontalPaddingSpy.count, horizontalPaddingChanges) + compare(verticalPaddingSpy.count, ++verticalPaddingChanges) + + control.leftPadding = undefined + compare(control.padding, 60) + compare(control.topPadding, 20) + compare(control.leftPadding, 80) + compare(control.rightPadding, 40) + compare(control.bottomPadding, 50) + compare(control.horizontalPadding, 80) + compare(control.verticalPadding, 90) + compare(paddingSpy.count, paddingChanges) + compare(topPaddingSpy.count, topPaddingChanges) + compare(leftPaddingSpy.count, ++leftPaddingChanges) + compare(rightPaddingSpy.count, rightPaddingChanges) + compare(bottomPaddingSpy.count, bottomPaddingChanges) + compare(horizontalPaddingSpy.count, horizontalPaddingChanges) + compare(verticalPaddingSpy.count, verticalPaddingChanges) + + control.rightPadding = undefined + compare(control.padding, 60) + compare(control.topPadding, 20) + compare(control.leftPadding, 80) + compare(control.rightPadding, 80) + compare(control.bottomPadding, 50) + compare(control.horizontalPadding, 80) + compare(control.verticalPadding, 90) + compare(paddingSpy.count, paddingChanges) + compare(topPaddingSpy.count, topPaddingChanges) + compare(leftPaddingSpy.count, leftPaddingChanges) + compare(rightPaddingSpy.count, ++rightPaddingChanges) + compare(bottomPaddingSpy.count, bottomPaddingChanges) + compare(horizontalPaddingSpy.count, horizontalPaddingChanges) + compare(verticalPaddingSpy.count, verticalPaddingChanges) + + control.topPadding = undefined + compare(control.padding, 60) + compare(control.topPadding, 90) + compare(control.leftPadding, 80) + compare(control.rightPadding, 80) + compare(control.bottomPadding, 50) + compare(control.horizontalPadding, 80) + compare(control.verticalPadding, 90) + compare(paddingSpy.count, paddingChanges) + compare(topPaddingSpy.count, ++topPaddingChanges) + compare(leftPaddingSpy.count, leftPaddingChanges) + compare(rightPaddingSpy.count, rightPaddingChanges) + compare(bottomPaddingSpy.count, bottomPaddingChanges) + compare(horizontalPaddingSpy.count, horizontalPaddingChanges) + compare(verticalPaddingSpy.count, verticalPaddingChanges) + + control.bottomPadding = undefined + compare(control.padding, 60) + compare(control.topPadding, 90) + compare(control.leftPadding, 80) + compare(control.rightPadding, 80) + compare(control.bottomPadding, 90) + compare(control.horizontalPadding, 80) + compare(control.verticalPadding, 90) + compare(paddingSpy.count, paddingChanges) + compare(topPaddingSpy.count, topPaddingChanges) + compare(leftPaddingSpy.count, leftPaddingChanges) + compare(rightPaddingSpy.count, rightPaddingChanges) + compare(bottomPaddingSpy.count, ++bottomPaddingChanges) + compare(horizontalPaddingSpy.count, horizontalPaddingChanges) + compare(verticalPaddingSpy.count, verticalPaddingChanges) + + control.horizontalPadding = undefined + compare(control.padding, 60) + compare(control.topPadding, 90) + compare(control.leftPadding, 60) + compare(control.rightPadding, 60) + compare(control.bottomPadding, 90) + compare(control.horizontalPadding, 60) + compare(control.verticalPadding, 90) + compare(paddingSpy.count, paddingChanges) + compare(topPaddingSpy.count, topPaddingChanges) + compare(leftPaddingSpy.count, ++leftPaddingChanges) + compare(rightPaddingSpy.count, ++rightPaddingChanges) + compare(bottomPaddingSpy.count, bottomPaddingChanges) + compare(horizontalPaddingSpy.count, ++horizontalPaddingChanges) + compare(verticalPaddingSpy.count, verticalPaddingChanges) + + control.verticalPadding = undefined + compare(control.padding, 60) + compare(control.topPadding, 60) + compare(control.leftPadding, 60) + compare(control.rightPadding, 60) + compare(control.bottomPadding, 60) + compare(control.horizontalPadding, 60) + compare(control.verticalPadding, 60) + compare(paddingSpy.count, paddingChanges) + compare(topPaddingSpy.count, ++topPaddingChanges) + compare(leftPaddingSpy.count, leftPaddingChanges) + compare(rightPaddingSpy.count, rightPaddingChanges) + compare(bottomPaddingSpy.count, ++bottomPaddingChanges) + compare(horizontalPaddingSpy.count, horizontalPaddingChanges) + compare(verticalPaddingSpy.count, ++verticalPaddingChanges) + } + + function test_availableSize() { + var control = createTemporaryObject(component, testCase) + verify(control) + + var availableWidthSpy = signalSpy.createObject(control, {target: control, signalName: "availableWidthChanged"}) + verify(availableWidthSpy.valid) + + var availableHeightSpy = signalSpy.createObject(control, {target: control, signalName: "availableHeightChanged"}) + verify(availableHeightSpy.valid) + + var availableWidthChanges = 0 + var availableHeightChanges = 0 + + control.width = 100 + compare(control.availableWidth, 100) + compare(availableWidthSpy.count, ++availableWidthChanges) + compare(availableHeightSpy.count, availableHeightChanges) + + control.height = 100 + compare(control.availableHeight, 100) + compare(availableWidthSpy.count, availableWidthChanges) + compare(availableHeightSpy.count, ++availableHeightChanges) + + control.padding = 10 + compare(control.availableWidth, 80) + compare(control.availableHeight, 80) + compare(availableWidthSpy.count, ++availableWidthChanges) + compare(availableHeightSpy.count, ++availableHeightChanges) + + control.topPadding = 20 + compare(control.availableWidth, 80) + compare(control.availableHeight, 70) + compare(availableWidthSpy.count, availableWidthChanges) + compare(availableHeightSpy.count, ++availableHeightChanges) + + control.leftPadding = 30 + compare(control.availableWidth, 60) + compare(control.availableHeight, 70) + compare(availableWidthSpy.count, ++availableWidthChanges) + compare(availableHeightSpy.count, availableHeightChanges) + + control.rightPadding = 40 + compare(control.availableWidth, 30) + compare(control.availableHeight, 70) + compare(availableWidthSpy.count, ++availableWidthChanges) + compare(availableHeightSpy.count, availableHeightChanges) + + control.bottomPadding = 50 + compare(control.availableWidth, 30) + compare(control.availableHeight, 30) + compare(availableWidthSpy.count, availableWidthChanges) + compare(availableHeightSpy.count, ++availableHeightChanges) + + control.padding = 60 + compare(control.availableWidth, 30) + compare(control.availableHeight, 30) + compare(availableWidthSpy.count, availableWidthChanges) + compare(availableHeightSpy.count, availableHeightChanges) + + control.width = 0 + compare(control.availableWidth, 0) + compare(availableWidthSpy.count, ++availableWidthChanges) + compare(availableHeightSpy.count, availableHeightChanges) + + control.height = 0 + compare(control.availableHeight, 0) + compare(availableWidthSpy.count, availableWidthChanges) + compare(availableHeightSpy.count, ++availableHeightChanges) + } + + function test_mirrored() { + var control = createTemporaryObject(component, testCase) + verify(control) + + var mirroredSpy = signalSpy.createObject(control, {target: control, signalName: "mirroredChanged"}) + verify(mirroredSpy.valid) + + control.locale = Qt.locale("en_US") + compare(control.locale.name, "en_US") + verify(!control.LayoutMirroring.enabled) + compare(control.mirrored, false) + + control.locale = Qt.locale("ar_EG") + compare(control.mirrored, false) + compare(mirroredSpy.count, 0) + + control.LayoutMirroring.enabled = true + compare(control.mirrored, true) + compare(mirroredSpy.count, 1) + + control.locale = Qt.locale("en_US") + compare(control.mirrored, true) + compare(mirroredSpy.count, 1) + + control.LayoutMirroring.enabled = false + compare(control.mirrored, false) + compare(mirroredSpy.count, 2) + } + + function test_background() { + var control = createTemporaryObject(component, testCase) + verify(control) + + control.background = component.createObject(control) + + // background has no x or width set, so its width follows control's width + control.width = 320 + compare(control.background.width, control.width) + + // background has no y or height set, so its height follows control's height + compare(control.background.height, control.height) + control.height = 240 + + // change implicit size (QTBUG-66455) + control.background.implicitWidth = 160 + control.background.implicitHeight = 120 + compare(control.background.width, control.width) + compare(control.background.height, control.height) + + // has width => width does not follow + control.background.width /= 2 + control.width += 20 + verify(control.background.width !== control.width) + + // reset width => width follows again + control.background.width = undefined + control.width += 20 + compare(control.background.width, control.width) + + // has x => width does not follow + control.background.x = 10 + control.width += 20 + verify(control.background.width !== control.width) + + // has height => height does not follow + control.background.height /= 2 + control.height -= 20 + verify(control.background.height !== control.height) + + // reset height => height follows again + control.background.height = undefined + control.height -= 20 + compare(control.background.height, control.height) + + // has y => height does not follow + control.background.y = 10 + control.height -= 20 + verify(control.background.height !== control.height) + } + + Component { + id: component2 + T.Control { + id: item2 + objectName: "item2" + property alias item2_2: _item2_2; + property alias item2_3: _item2_3; + property alias item2_4: _item2_4; + property alias item2_5: _item2_5; + property alias item2_6: _item2_6; + font.family: "Arial" + T.Control { + id: _item2_2 + objectName: "_item2_2" + T.Control { + id: _item2_3 + objectName: "_item2_3" + } + } + T.TextArea { + id: _item2_4 + objectName: "_item2_4" + text: "Text Area" + } + T.TextField { + id: _item2_5 + objectName: "_item2_5" + text: "Text Field" + } + T.Label { + id: _item2_6 + objectName: "_item2_6" + text: "Label" + } + } + } + + function test_font() { + var control2 = createTemporaryObject(component2, testCase) + verify(control2) + verify(control2.item2_2) + verify(control2.item2_3) + verify(control2.item2_4) + verify(control2.item2_5) + verify(control2.item2_6) + + compare(control2.font.family, "Arial") + compare(control2.item2_2.font.family, control2.font.family) + compare(control2.item2_3.font.family, control2.font.family) + compare(control2.item2_4.font.family, control2.font.family) + compare(control2.item2_5.font.family, control2.font.family) + compare(control2.item2_6.font.family, control2.font.family) + + control2.font.pointSize = 48 + compare(control2.item2_2.font.pointSize, 48) + compare(control2.item2_3.font.pointSize, 48) + compare(control2.item2_4.font.pointSize, 48) + compare(control2.item2_5.font.pointSize, 48) + + control2.font.bold = true + compare(control2.item2_2.font.weight, Font.Bold) + compare(control2.item2_3.font.weight, Font.Bold) + compare(control2.item2_4.font.weight, Font.Bold) + compare(control2.item2_5.font.weight, Font.Bold) + + control2.item2_2.font.pointSize = 36 + compare(control2.item2_2.font.pointSize, 36) + compare(control2.item2_3.font.pointSize, 36) + + control2.item2_2.font.weight = Font.Light + compare(control2.item2_2.font.pointSize, 36) + compare(control2.item2_3.font.pointSize, 36) + + compare(control2.item2_3.font.family, control2.item2_2.font.family) + compare(control2.item2_3.font.pointSize, control2.item2_2.font.pointSize) + compare(control2.item2_3.font.weight, control2.item2_2.font.weight) + + control2.font.pointSize = 50 + compare(control2.item2_2.font.pointSize, 36) + compare(control2.item2_3.font.pointSize, 36) + compare(control2.item2_4.font.pointSize, 50) + compare(control2.item2_5.font.pointSize, 50) + compare(control2.item2_6.font.pointSize, 50) + + control2.item2_3.font.pointSize = 60 + compare(control2.item2_3.font.pointSize, 60) + + control2.item2_3.font.weight = Font.Normal + compare(control2.item2_3.font.weight, Font.Normal) + + control2.item2_4.font.pointSize = 16 + compare(control2.item2_4.font.pointSize, 16) + + control2.item2_4.font.weight = Font.Normal + compare(control2.item2_4.font.weight, Font.Normal) + + control2.item2_5.font.pointSize = 32 + compare(control2.item2_5.font.pointSize, 32) + + control2.item2_5.font.weight = Font.DemiBold + compare(control2.item2_5.font.weight, Font.DemiBold) + + control2.item2_6.font.pointSize = 36 + compare(control2.item2_6.font.pointSize, 36) + + control2.item2_6.font.weight = Font.Black + compare(control2.item2_6.font.weight, Font.Black) + + compare(control2.font.family, "Arial") + compare(control2.font.pointSize, 50) + compare(control2.font.weight, Font.Bold) + + compare(control2.item2_2.font.family, "Arial") + compare(control2.item2_2.font.pointSize, 36) + compare(control2.item2_2.font.weight, Font.Light) + + compare(control2.item2_3.font.family, "Arial") + compare(control2.item2_3.font.pointSize, 60) + compare(control2.item2_3.font.weight, Font.Normal) + + compare(control2.item2_4.font.family, "Arial") + compare(control2.item2_4.font.pointSize, 16) + compare(control2.item2_4.font.weight, Font.Normal) + + compare(control2.item2_5.font.family, "Arial") + compare(control2.item2_5.font.pointSize, 32) + compare(control2.item2_5.font.weight, Font.DemiBold) + + compare(control2.item2_6.font.family, "Arial") + compare(control2.item2_6.font.pointSize, 36) + compare(control2.item2_6.font.weight, Font.Black) + } + + Component { + id: component3 + T.Control { + id: item3 + objectName: "item3" + property alias item3_2: _item3_2; + property alias item3_3: _item3_3; + property alias item3_4: _item3_4; + property alias item3_5: _item3_5; + property alias item3_6: _item3_6; + property alias item3_7: _item3_7; + property alias item3_8: _item3_8; + font.family: "Arial" + Item { + id: _item3_2 + objectName: "_item3_2" + T.Control { + id: _item3_3 + objectName: "_item3_3" + Item { + id: _item3_6 + objectName: "_item3_6" + T.Control { + id: _item3_7 + objectName: "_item3_7" + } + } + } + T.TextArea { + id: _item3_4 + objectName: "_item3_4" + text: "Text Area" + } + T.TextField { + id: _item3_5 + objectName: "_item3_5" + text: "Text Field" + } + T.Label { + id: _item3_8 + objectName: "_item3_8" + text: "Label" + } + } + } + } + + function test_font_2() { + var control3 = createTemporaryObject(component3, testCase) + verify(control3) + verify(control3.item3_2) + verify(control3.item3_3) + verify(control3.item3_4) + verify(control3.item3_5) + verify(control3.item3_6) + verify(control3.item3_7) + verify(control3.item3_8) + + compare(control3.font.family, "Arial") + compare(control3.item3_3.font.family, control3.font.family) + compare(control3.item3_4.font.family, control3.font.family) + compare(control3.item3_5.font.family, control3.font.family) + compare(control3.item3_7.font.family, control3.font.family) + compare(control3.item3_8.font.family, control3.font.family) + + control3.font.pointSize = 48 + compare(control3.item3_3.font.pointSize, 48) + compare(control3.item3_4.font.pointSize, 48) + compare(control3.item3_5.font.pointSize, 48) + + control3.font.bold = true + compare(control3.item3_3.font.weight, Font.Bold) + compare(control3.item3_4.font.weight, Font.Bold) + compare(control3.item3_5.font.weight, Font.Bold) + + compare(control3.item3_3.font.family, control3.font.family) + compare(control3.item3_3.font.pointSize, control3.font.pointSize) + compare(control3.item3_3.font.weight, control3.font.weight) + compare(control3.item3_7.font.family, control3.font.family) + compare(control3.item3_7.font.pointSize, control3.font.pointSize) + compare(control3.item3_7.font.weight, control3.font.weight) + + control3.item3_3.font.pointSize = 60 + compare(control3.item3_3.font.pointSize, 60) + + control3.item3_3.font.weight = Font.Normal + compare(control3.item3_3.font.weight, Font.Normal) + + control3.item3_4.font.pointSize = 16 + compare(control3.item3_4.font.pointSize, 16) + + control3.item3_4.font.weight = Font.Normal + compare(control3.item3_4.font.weight, Font.Normal) + + control3.item3_5.font.pointSize = 32 + compare(control3.item3_5.font.pointSize, 32) + + control3.item3_5.font.weight = Font.DemiBold + compare(control3.item3_5.font.weight, Font.DemiBold) + + control3.item3_8.font.pointSize = 36 + compare(control3.item3_8.font.pointSize, 36) + + control3.item3_8.font.weight = Font.Black + compare(control3.item3_8.font.weight, Font.Black) + + control3.font.pointSize = 100 + compare(control3.font.pointSize, 100) + compare(control3.item3_3.font.pointSize, 60) + compare(control3.item3_4.font.pointSize, 16) + compare(control3.item3_5.font.pointSize, 32) + compare(control3.item3_8.font.pointSize, 36) + + compare(control3.font.family, "Arial") + compare(control3.font.pointSize, 100) + compare(control3.font.weight, Font.Bold) + + compare(control3.item3_3.font.family, "Arial") + compare(control3.item3_3.font.pointSize, 60) + compare(control3.item3_3.font.weight, Font.Normal) + compare(control3.item3_7.font.family, control3.item3_3.font.family) + compare(control3.item3_7.font.pointSize, control3.item3_3.font.pointSize) + compare(control3.item3_7.font.weight, control3.item3_3.font.weight) + + compare(control3.item3_4.font.family, "Arial") + compare(control3.item3_4.font.pointSize, 16) + compare(control3.item3_4.font.weight, Font.Normal) + + compare(control3.item3_5.font.family, "Arial") + compare(control3.item3_5.font.pointSize, 32) + compare(control3.item3_5.font.weight, Font.DemiBold) + + compare(control3.item3_8.font.family, "Arial") + compare(control3.item3_8.font.pointSize, 36) + compare(control3.item3_8.font.weight, Font.Black) + } + + Component { + id: component4 + T.Control { + id: item4 + objectName: "item4" + property alias item4_2: _item4_2; + property alias item4_3: _item4_3; + property alias item4_4: _item4_4; + T.Control { + id: _item4_2 + objectName: "_item4_2" + font.pixelSize: item4.font.pixelSize + 10 + T.Control { + id: _item4_3 + objectName: "_item4_3" + font.pixelSize: item4.font.pixelSize - 1 + } + T.Control { + id: _item4_4 + objectName: "_item4_4" + } + } + } + } + + function test_font_3() { + var control4 = createTemporaryObject(component4, testCase) + verify(control4) + verify(control4.item4_2) + verify(control4.item4_3) + verify(control4.item4_4) + + var family = control4.font.family + var ps = control4.font.pixelSize + + compare(control4.item4_2.font.family, control4.font.family) + compare(control4.item4_3.font.family, control4.font.family) + compare(control4.item4_4.font.family, control4.font.family) + + compare(control4.item4_2.font.pixelSize, control4.font.pixelSize + 10) + compare(control4.item4_3.font.pixelSize, control4.font.pixelSize - 1) + compare(control4.item4_4.font.pixelSize, control4.font.pixelSize + 10) + + control4.item4_2.font.pixelSize = control4.font.pixelSize + 15 + compare(control4.item4_2.font.pixelSize, control4.font.pixelSize + 15) + compare(control4.item4_3.font.pixelSize, control4.font.pixelSize - 1) + compare(control4.item4_4.font.pixelSize, control4.font.pixelSize + 15) + } + + function test_font_explicit_attributes_data() { + return [ + {tag: "bold", value: true}, + {tag: "capitalization", value: Font.Capitalize}, + {tag: "family", value: "Courier"}, + {tag: "italic", value: true}, + {tag: "strikeout", value: true}, + {tag: "underline", value: true}, + {tag: "weight", value: Font.Black}, + {tag: "wordSpacing", value: 55} + ] + } + + function test_font_explicit_attributes(data) { + var control = createTemporaryObject(component, testCase) + verify(control) + + var child = component.createObject(control) + verify(child) + + var controlSpy = signalSpy.createObject(control, {target: control, signalName: "fontChanged"}) + verify(controlSpy.valid) + + var childSpy = signalSpy.createObject(child, {target: child, signalName: "fontChanged"}) + verify(childSpy.valid) + + var defaultValue = control.font[data.tag] + child.font[data.tag] = defaultValue + + compare(child.font[data.tag], defaultValue) + compare(childSpy.count, 0) + + control.font[data.tag] = data.value + + compare(control.font[data.tag], data.value) + compare(controlSpy.count, 1) + + compare(child.font[data.tag], defaultValue) + compare(childSpy.count, 0) + } + + function test_locale() { + var control = createTemporaryObject(component, testCase) + verify(control) + + control.locale = Qt.locale("en_US") + compare(control.locale.name, "en_US") + + control.locale = Qt.locale("nb_NO") + compare(control.locale.name, "nb_NO") + } + + Component { + id: component5 + T.Control { + id: item2 + objectName: "item2" + property alias localespy: _lspy; + property alias mirroredspy: _mspy; + property alias localespy_2: _lspy_2; + property alias mirroredspy_2: _mspy_2; + property alias localespy_3: _lspy_3; + property alias mirroredspy_3: _mspy_3; + property alias item2_2: _item2_2; + property alias item2_3: _item2_3; + T.Control { + id: _item2_2 + objectName: "_item2_2" + T.Control { + id: _item2_3 + objectName: "_item2_3" + + SignalSpy { + id: _lspy_3 + target: item2_3 + signalName: "localeChanged" + } + + SignalSpy { + id: _mspy_3 + target: item2_3 + signalName: "mirroredChanged" + } + } + + SignalSpy { + id: _lspy_2 + target: item2_2 + signalName: "localeChanged" + } + + SignalSpy { + id: _mspy_2 + target: item2_2 + signalName: "mirroredChanged" + } + } + + SignalSpy { + id: _lspy + target: item2 + signalName: "localeChanged" + } + + SignalSpy { + id: _mspy + target: item2 + signalName: "mirroredChanged" + } + } + } + + function test_locale_2() { + var control = createTemporaryObject(component5, testCase) + verify(control) + verify(control.item2_2) + verify(control.item2_3) + + var defaultLocale = Qt.locale() + + compare(control.locale.name, defaultLocale.name) + compare(control.item2_2.locale.name, defaultLocale.name) + compare(control.item2_3.locale.name, defaultLocale.name) + + control.locale = Qt.locale("nb_NO") + control.localespy.wait() + compare(control.localespy.count, 1) + compare(control.mirroredspy.count, 0) + compare(control.locale.name, "nb_NO") + compare(control.item2_2.locale.name, "nb_NO") + compare(control.item2_3.locale.name, "nb_NO") + compare(control.localespy_2.count, 1) + compare(control.mirroredspy_2.count, 0) + compare(control.localespy_3.count, 1) + compare(control.mirroredspy_3.count, 0) + + control.locale = Qt.locale("ar_EG") + control.localespy.wait() + compare(control.localespy.count, 2) + compare(control.mirroredspy.count, 0) + compare(control.locale.name, "ar_EG") + compare(control.item2_2.locale.name, "ar_EG") + compare(control.item2_3.locale.name, "ar_EG") + compare(control.localespy_2.count, 2) + compare(control.mirroredspy_2.count, 0) + compare(control.localespy_3.count, 2) + compare(control.mirroredspy_3.count, 0) + } + + Component { + id: component6 + T.Control { + id: item6 + objectName: "item6" + property alias localespy: _lspy; + property alias mirroredspy: _mspy; + property alias localespy_5: _lspy_5; + property alias mirroredspy_5: _mspy_5; + property alias item6_2: _item6_2; + property alias item6_3: _item6_3; + property alias item6_4: _item6_4; + property alias item6_5: _item6_5; + Item { + id: _item6_2 + objectName: "_item6_2" + T.Control { + id: _item6_3 + objectName: "_item6_3" + Item { + id: _item6_4 + objectName: "_item6_4" + T.Control { + id: _item6_5 + objectName: "_item6_5" + + SignalSpy { + id: _lspy_5 + target: _item6_5 + signalName: "localeChanged" + } + + SignalSpy { + id: _mspy_5 + target: _item6_5 + signalName: "mirroredChanged" + } + } + } + } + } + + SignalSpy { + id: _lspy + target: item6 + signalName: "localeChanged" + } + + SignalSpy { + id: _mspy + target: item6 + signalName: "mirroredChanged" + } + } + } + + function test_locale_3() { + var control = createTemporaryObject(component6, testCase) + verify(control) + verify(control.item6_2) + verify(control.item6_3) + verify(control.item6_4) + verify(control.item6_5) + + var defaultLocale = Qt.locale() + + compare(control.locale.name, defaultLocale.name) + compare(control.item6_5.locale.name, defaultLocale.name) + + control.locale = Qt.locale("nb_NO") + control.localespy.wait() + compare(control.localespy.count, 1) + compare(control.mirroredspy.count, 0) + compare(control.locale.name, "nb_NO") + compare(control.item6_5.locale.name, "nb_NO") + compare(control.localespy_5.count, 1) + compare(control.mirroredspy_5.count, 0) + + control.locale = Qt.locale("ar_EG") + control.localespy.wait() + compare(control.localespy.count, 2) + compare(control.mirroredspy.count, 0) + compare(control.locale.name, "ar_EG") + compare(control.item6_5.locale.name, "ar_EG") + compare(control.localespy_5.count, 2) + compare(control.mirroredspy_5.count, 0) + } + + function test_hover_data() { + return [ + { tag: "normal", target: component, pressed: false }, + { tag: "pressed", target: button, pressed: true } + ] + } + + function test_hover(data) { + var control = createTemporaryObject(data.target, testCase, {width: 100, height: 100}) + verify(control) + + compare(control.hovered, false) + compare(control.hoverEnabled, Qt.styleHints.useHoverEffects) + + control.hoverEnabled = false + + mouseMove(control, control.width / 2, control.height / 2) + compare(control.hovered, false) + + control.hoverEnabled = true + + mouseMove(control, control.width / 2, control.height / 2) + compare(control.hovered, true) + + if (data.pressed) { + mousePress(control, control.width / 2, control.height / 2) + compare(control.hovered, true) + } + + mouseMove(control, -10, -10) + compare(control.hovered, false) + + if (data.pressed) { + mouseRelease(control, -10, control.height / 2) + compare(control.hovered, false) + } + + mouseMove(control, control.width / 2, control.height / 2) + compare(control.hovered, true) + + control.visible = false + compare(control.hovered, false) + } + + function test_hoverEnabled() { + var control = createTemporaryObject(component, testCase) + compare(control.hoverEnabled, Qt.styleHints.useHoverEffects) + + var child = component.createObject(control) + var grandChild = component.createObject(child) + + var childExplicitHoverEnabled = component.createObject(control, {hoverEnabled: true}) + var grandChildExplicitHoverDisabled = component.createObject(childExplicitHoverEnabled, {hoverEnabled: false}) + + var childExplicitHoverDisabled = component.createObject(control, {hoverEnabled: false}) + var grandChildExplicitHoverEnabled = component.createObject(childExplicitHoverDisabled, {hoverEnabled: true}) + + control.hoverEnabled = false + compare(control.hoverEnabled, false) + compare(grandChild.hoverEnabled, false) + + compare(childExplicitHoverEnabled.hoverEnabled, true) + compare(grandChildExplicitHoverDisabled.hoverEnabled, false) + + compare(childExplicitHoverDisabled.hoverEnabled, false) + compare(grandChildExplicitHoverEnabled.hoverEnabled, true) + + control.hoverEnabled = true + compare(control.hoverEnabled, true) + compare(grandChild.hoverEnabled, true) + + compare(childExplicitHoverEnabled.hoverEnabled, true) + compare(grandChildExplicitHoverDisabled.hoverEnabled, false) + + compare(childExplicitHoverDisabled.hoverEnabled, false) + compare(grandChildExplicitHoverEnabled.hoverEnabled, true) + } + + function test_implicitSize() { + var control = createTemporaryObject(component, testCase) + verify(control) + + var implicitWidthSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "implicitWidthChanged"}) + verify(implicitWidthSpy.valid) + + var implicitHeightSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "implicitHeightChanged"}) + verify(implicitHeightSpy.valid) + + var implicitContentWidthSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "implicitContentWidthChanged"}) + verify(implicitContentWidthSpy.valid) + + var implicitContentHeightSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "implicitContentHeightChanged"}) + verify(implicitContentHeightSpy.valid) + + var implicitBackgroundWidthSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "implicitBackgroundWidthChanged"}) + verify(implicitBackgroundWidthSpy.valid) + + var implicitBackgroundHeightSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "implicitBackgroundHeightChanged"}) + verify(implicitBackgroundHeightSpy.valid) + + var implicitWidthChanges = 0 + var implicitHeightChanges = 0 + var implicitContentWidthChanges = 0 + var implicitContentHeightChanges = 0 + var implicitBackgroundWidthChanges = 0 + var implicitBackgroundHeightChanges = 0 + + compare(control.implicitWidth, 0) + compare(control.implicitHeight, 0) + compare(control.implicitContentWidth, 0) + compare(control.implicitContentHeight, 0) + compare(control.implicitBackgroundWidth, 0) + compare(control.implicitBackgroundHeight, 0) + + control.contentItem = rectangle.createObject(control, {implicitWidth: 10, implicitHeight: 20}) + compare(control.implicitWidth, 10) + compare(control.implicitHeight, 20) + compare(control.implicitContentWidth, 10) + compare(control.implicitContentHeight, 20) + compare(control.implicitBackgroundWidth, 0) + compare(control.implicitBackgroundHeight, 0) + compare(implicitWidthSpy.count, ++implicitWidthChanges) + compare(implicitHeightSpy.count, ++implicitHeightChanges) + compare(implicitBackgroundWidthSpy.count, implicitBackgroundWidthChanges) + compare(implicitBackgroundHeightSpy.count, implicitBackgroundHeightChanges) + compare(implicitContentWidthSpy.count, ++implicitContentWidthChanges) + compare(implicitContentHeightSpy.count, ++implicitContentHeightChanges) + + control.contentItem.implicitWidth += 1 + control.contentItem.implicitHeight += 1 + compare(control.implicitWidth, 11) + compare(control.implicitHeight, 21) + compare(control.implicitContentWidth, 11) + compare(control.implicitContentHeight, 21) + compare(control.implicitBackgroundWidth, 0) + compare(control.implicitBackgroundHeight, 0) + compare(implicitWidthSpy.count, ++implicitWidthChanges) + compare(implicitHeightSpy.count, ++implicitHeightChanges) + compare(implicitContentWidthSpy.count, ++implicitContentWidthChanges) + compare(implicitContentHeightSpy.count, ++implicitContentHeightChanges) + compare(implicitBackgroundWidthSpy.count, implicitBackgroundWidthChanges) + compare(implicitBackgroundHeightSpy.count, implicitBackgroundHeightChanges) + + control.background = rectangle.createObject(control, {implicitWidth: 20, implicitHeight: 30}) + compare(control.implicitWidth, 20) + compare(control.implicitHeight, 30) + compare(control.implicitContentWidth,11) + compare(control.implicitContentHeight, 21) + compare(control.implicitBackgroundWidth, 20) + compare(control.implicitBackgroundHeight, 30) + compare(implicitWidthSpy.count, ++implicitWidthChanges) + compare(implicitHeightSpy.count, ++implicitHeightChanges) + compare(implicitContentWidthSpy.count, implicitContentWidthChanges) + compare(implicitContentHeightSpy.count, implicitContentHeightChanges) + compare(implicitBackgroundWidthSpy.count, ++implicitBackgroundWidthChanges) + compare(implicitBackgroundHeightSpy.count, ++implicitBackgroundHeightChanges) + + control.background.implicitWidth += 1 + control.background.implicitHeight += 1 + compare(control.implicitWidth, 21) + compare(control.implicitHeight, 31) + compare(control.implicitContentWidth, 11) + compare(control.implicitContentHeight, 21) + compare(control.implicitBackgroundWidth, 21) + compare(control.implicitBackgroundHeight, 31) + compare(implicitWidthSpy.count, ++implicitWidthChanges) + compare(implicitHeightSpy.count, ++implicitHeightChanges) + compare(implicitContentWidthSpy.count, implicitContentWidthChanges) + compare(implicitContentHeightSpy.count, implicitContentHeightChanges) + compare(implicitBackgroundWidthSpy.count, ++implicitBackgroundWidthChanges) + compare(implicitBackgroundHeightSpy.count, ++implicitBackgroundHeightChanges) + } + + function test_baseline() { + var control = createTemporaryObject(component, testCase) + verify(control) + + compare(control.baselineOffset, 0) + + var baselineSpy = signalSpy.createObject(control, {target: control, signalName: "baselineOffsetChanged"}) + verify(baselineSpy.valid) + + control.contentItem = rectangle.createObject(control, {baselineOffset: 12}) + compare(control.baselineOffset, 12) + compare(baselineSpy.count, 1) + + control.padding = 6 + compare(control.baselineOffset, 18) + compare(baselineSpy.count, 2) + + control.baselineOffset = 3 + compare(control.baselineOffset, 3) + compare(baselineSpy.count, 3) + + control.padding = 9 + compare(control.baselineOffset, 3) + compare(baselineSpy.count, 3) + + control.baselineOffset = undefined + compare(control.baselineOffset, 21) + compare(baselineSpy.count, 4) + + control.contentItem.baselineOffset = 3 + compare(control.baselineOffset, 12) + compare(baselineSpy.count, 5) + + control.contentItem = null + compare(control.baselineOffset, 0) + compare(baselineSpy.count, 6) + } + + function test_inset() { + var control = createTemporaryObject(component, testCase, {background: rectangle.createObject(control)}) + verify(control) + + var topInsetSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "topInsetChanged"}) + verify(topInsetSpy.valid) + + var leftInsetSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "leftInsetChanged"}) + verify(leftInsetSpy.valid) + + var rightInsetSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "rightInsetChanged"}) + verify(rightInsetSpy.valid) + + var bottomInsetSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "bottomInsetChanged"}) + verify(bottomInsetSpy.valid) + + var topInsetChanges = 0 + var leftInsetChanges = 0 + var rightInsetChanges = 0 + var bottomInsetChanges = 0 + + compare(control.topInset, 0) + compare(control.leftInset, 0) + compare(control.rightInset, 0) + compare(control.bottomInset, 0) + + control.width = 100 + control.height = 100 + compare(control.background.x, 0) + compare(control.background.y, 0) + compare(control.background.width, 100) + compare(control.background.height, 100) + + control.topInset = 10 + compare(control.topInset, 10) + compare(control.leftInset, 0) + compare(control.rightInset, 0) + compare(control.bottomInset, 0) + compare(topInsetSpy.count, ++topInsetChanges) + compare(leftInsetSpy.count, leftInsetChanges) + compare(rightInsetSpy.count, rightInsetChanges) + compare(bottomInsetSpy.count, bottomInsetChanges) + compare(control.background.x, 0) + compare(control.background.y, 10) + compare(control.background.width, 100) + compare(control.background.height, 90) + + control.leftInset = 20 + compare(control.topInset, 10) + compare(control.leftInset, 20) + compare(control.rightInset, 0) + compare(control.bottomInset, 0) + compare(topInsetSpy.count, topInsetChanges) + compare(leftInsetSpy.count, ++leftInsetChanges) + compare(rightInsetSpy.count, rightInsetChanges) + compare(bottomInsetSpy.count, bottomInsetChanges) + compare(control.background.x, 20) + compare(control.background.y, 10) + compare(control.background.width, 80) + compare(control.background.height, 90) + + control.rightInset = 30 + compare(control.topInset, 10) + compare(control.leftInset, 20) + compare(control.rightInset, 30) + compare(control.bottomInset, 0) + compare(topInsetSpy.count, topInsetChanges) + compare(leftInsetSpy.count, leftInsetChanges) + compare(rightInsetSpy.count, ++rightInsetChanges) + compare(bottomInsetSpy.count, bottomInsetChanges) + compare(control.background.x, 20) + compare(control.background.y, 10) + compare(control.background.width, 50) + compare(control.background.height, 90) + + control.bottomInset = 40 + compare(control.topInset, 10) + compare(control.leftInset, 20) + compare(control.rightInset, 30) + compare(control.bottomInset, 40) + compare(topInsetSpy.count, topInsetChanges) + compare(leftInsetSpy.count, leftInsetChanges) + compare(rightInsetSpy.count, rightInsetChanges) + compare(bottomInsetSpy.count, ++bottomInsetChanges) + compare(control.background.x, 20) + compare(control.background.y, 10) + compare(control.background.width, 50) + compare(control.background.height, 50) + + control.topInset = undefined + compare(control.topInset, 0) + compare(control.leftInset, 20) + compare(control.rightInset, 30) + compare(control.bottomInset, 40) + compare(topInsetSpy.count, ++topInsetChanges) + compare(leftInsetSpy.count, leftInsetChanges) + compare(rightInsetSpy.count, rightInsetChanges) + compare(bottomInsetSpy.count, bottomInsetChanges) + compare(control.background.x, 20) + compare(control.background.y, 0) + compare(control.background.width, 50) + compare(control.background.height, 60) + + control.leftInset = undefined + compare(control.topInset, 0) + compare(control.leftInset, 0) + compare(control.rightInset, 30) + compare(control.bottomInset, 40) + compare(topInsetSpy.count, topInsetChanges) + compare(leftInsetSpy.count, ++leftInsetChanges) + compare(rightInsetSpy.count, rightInsetChanges) + compare(bottomInsetSpy.count, bottomInsetChanges) + compare(control.background.x, 0) + compare(control.background.y, 0) + compare(control.background.width, 70) + compare(control.background.height, 60) + + control.rightInset = undefined + compare(control.topInset, 0) + compare(control.leftInset, 0) + compare(control.rightInset, 0) + compare(control.bottomInset, 40) + compare(topInsetSpy.count, topInsetChanges) + compare(leftInsetSpy.count, leftInsetChanges) + compare(rightInsetSpy.count, ++rightInsetChanges) + compare(bottomInsetSpy.count, bottomInsetChanges) + compare(control.background.x, 0) + compare(control.background.y, 0) + compare(control.background.width, 100) + compare(control.background.height, 60) + + control.bottomInset = undefined + compare(control.topInset, 0) + compare(control.leftInset, 0) + compare(control.rightInset, 0) + compare(control.bottomInset, 0) + compare(topInsetSpy.count, topInsetChanges) + compare(leftInsetSpy.count, leftInsetChanges) + compare(rightInsetSpy.count, rightInsetChanges) + compare(bottomInsetSpy.count, ++bottomInsetChanges) + compare(control.background.x, 0) + compare(control.background.y, 0) + compare(control.background.width, 100) + compare(control.background.height, 100) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_delaybutton.qml b/tests/auto/quickcontrols2/controls/data/tst_delaybutton.qml new file mode 100644 index 0000000000..0e8d188dd2 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_delaybutton.qml @@ -0,0 +1,336 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 200 + height: 200 + visible: true + when: windowShown + name: "DelayButton" + + Component { + id: delayButton + DelayButton { + delay: 200 + } + } + + Component { + id: signalSequenceSpy + SignalSequenceSpy { + signals: ["pressed", "released", "canceled", "clicked", "toggled", "doubleClicked", "pressedChanged", "downChanged", "checkedChanged", "activated"] + } + } + + Component { + id: signalSpy + SignalSpy { } + } + + function test_mouse() { + var control = createTemporaryObject(delayButton, testCase) + verify(control) + + var sequenceSpy = signalSequenceSpy.createObject(control, {target: control}) + + // click + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], + ["downChanged", { "down": true }], + "pressed", + ["pressedChanged", { "pressed": false }], + ["downChanged", { "down": false }], + "released", + "clicked"] + mouseClick(control) + verify(sequenceSpy.success) + + // check + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], + ["downChanged", { "down": true }], + "pressed", + "activated"] + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.pressed, true) + tryVerify(function() { return sequenceSpy.success}) + + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false }], + ["downChanged", { "down": false }], + ["checkedChanged", { "checked": true }], + "released", + "clicked"] + mouseRelease(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.pressed, false) + verify(sequenceSpy.success) + + // uncheck + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], + ["downChanged", { "down": true }], + "pressed"] + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.pressed, true) + verify(sequenceSpy.success) + + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false }], + ["downChanged", { "down": false }], + ["checkedChanged", { "checked": false }], + "released", + "clicked"] + mouseRelease(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.pressed, false) + verify(sequenceSpy.success) + + // release outside + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], + ["downChanged", { "down": true }], + "pressed"] + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.pressed, true) + verify(sequenceSpy.success) + + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false }], + ["downChanged", { "down": false }]] + mouseMove(control, control.width * 2, control.height * 2, 0) + compare(control.pressed, false) + verify(sequenceSpy.success) + + sequenceSpy.expectedSequence = [["canceled", { "pressed": false }]] + mouseRelease(control, control.width * 2, control.height * 2, Qt.LeftButton) + compare(control.pressed, false) + verify(sequenceSpy.success) + + // right button + sequenceSpy.expectedSequence = [] + mousePress(control, control.width / 2, control.height / 2, Qt.RightButton) + compare(control.pressed, false) + + mouseRelease(control, control.width / 2, control.height / 2, Qt.RightButton) + compare(control.pressed, false) + verify(sequenceSpy.success) + + // double click + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], + ["downChanged", { "down": true }], + "pressed", + ["pressedChanged", { "pressed": false }], + ["downChanged", { "down": false }], + "released", + "clicked", + ["pressedChanged", { "pressed": true }], + ["downChanged", { "down": true }], + "pressed", + "doubleClicked", + ["pressedChanged", { "pressed": false }], + ["downChanged", { "down": false }], + "released"] + mouseDoubleClickSequence(control, control.width / 2, control.height / 2, Qt.LeftButton) + verify(sequenceSpy.success) + } + + function test_touch() { + var control = createTemporaryObject(delayButton, testCase) + verify(control) + + var touch = touchEvent(control) + + var sequenceSpy = signalSequenceSpy.createObject(control, {target: control}) + + // click + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], + ["downChanged", { "down": true }], + "pressed", + ["pressedChanged", { "pressed": false }], + ["downChanged", { "down": false }], + "released", + "clicked"] + touch.press(0, control).commit() + touch.release(0, control).commit() + verify(sequenceSpy.success) + + // check + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], + ["downChanged", { "down": true }], + "pressed", + "activated"] + // Don't want to double-click. + wait(Qt.styleHints.mouseDoubleClickInterval + 50) + touch.press(0, control, control.width / 2, control.height / 2).commit() + compare(control.pressed, true) + tryVerify(function() { return sequenceSpy.success}) + + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false }], + ["downChanged", { "down": false }], + ["checkedChanged", { "checked": true }], + "released", + "clicked"] + touch.release(0, control, control.width / 2, control.height / 2).commit() + compare(control.pressed, false) + verify(sequenceSpy.success) + + // uncheck + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], + ["downChanged", { "down": true }], + "pressed"] + wait(Qt.styleHints.mouseDoubleClickInterval + 50) + touch.press(0, control, control.width / 2, control.height / 2).commit() + compare(control.pressed, true) + verify(sequenceSpy.success) + + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false }], + ["downChanged", { "down": false }], + ["checkedChanged", { "checked": false }], + "released", + "clicked"] + touch.release(0, control, control.width / 2, control.height / 2).commit() + compare(control.pressed, false) + verify(sequenceSpy.success) + + // release outside + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], + ["downChanged", { "down": true }], + "pressed"] + wait(Qt.styleHints.mouseDoubleClickInterval + 50) + touch.press(0, control, control.width / 2, control.height / 2).commit() + compare(control.pressed, true) + verify(sequenceSpy.success) + + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false }], + ["downChanged", { "down": false }]] + touch.move(0, control, control.width * 2, control.height * 2).commit() + compare(control.pressed, false) + verify(sequenceSpy.success) + + sequenceSpy.expectedSequence = [["canceled", { "pressed": false }]] + touch.release(0, control, control.width * 2, control.height * 2).commit() + compare(control.pressed, false) + verify(sequenceSpy.success) + } + + function test_keys() { + var control = createTemporaryObject(delayButton, testCase) + verify(control) + + control.forceActiveFocus() + verify(control.activeFocus) + + var sequenceSpy = signalSequenceSpy.createObject(control, {target: control}) + + // click + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], + ["downChanged", { "down": true }], + "pressed", + ["pressedChanged", { "pressed": false }], + ["downChanged", { "down": false }], + "released", + "clicked"] + keyClick(Qt.Key_Space) + verify(sequenceSpy.success) + + // check + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], + ["downChanged", { "down": true }], + "pressed", + "activated"] + keyPress(Qt.Key_Space) + tryVerify(function() { return sequenceSpy.success}) + + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false }], + ["downChanged", { "down": false }], + ["checkedChanged", { "checked": true }], + "released", + "clicked"] + keyRelease(Qt.Key_Space) + verify(sequenceSpy.success) + + // uncheck + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], + ["downChanged", { "down": true }], + "pressed", + ["pressedChanged", { "pressed": false }], + ["downChanged", { "down": false }], + ["checkedChanged", { "checked": false }], + "released", + "clicked"] + keyClick(Qt.Key_Space) + verify(sequenceSpy.success) + + // no change + sequenceSpy.expectedSequence = [] + var keys = [Qt.Key_Enter, Qt.Key_Return, Qt.Key_Escape, Qt.Key_Tab] + for (var i = 0; i < keys.length; ++i) { + sequenceSpy.reset() + keyClick(keys[i]) + verify(sequenceSpy.success) + } + } + + function test_progress() { + var control = createTemporaryObject(delayButton, testCase) + verify(control) + + var progressSpy = signalSpy.createObject(control, {target: control, signalName: "progressChanged"}) + verify(progressSpy.valid) + + compare(control.progress, 0.0) + mousePress(control) + tryCompare(control, "progress", 1.0) + verify(progressSpy.count > 0) + } + + function test_baseline() { + var control = createTemporaryObject(delayButton, testCase) + verify(control) + compare(control.baselineOffset, control.contentItem.y + control.contentItem.baselineOffset) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_dial.qml b/tests/auto/quickcontrols2/controls/data/tst_dial.qml new file mode 100644 index 0000000000..2d0c702e70 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_dial.qml @@ -0,0 +1,709 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 450 + height: 450 + visible: true + when: windowShown + name: "Dial" + + Component { + id: dialComponent + Dial { + width: 100 + height: 100 + anchors.centerIn: parent + } + } + + Component { + id: signalSpy + SignalSpy {} + } + + function test_instance() { + var dial = createTemporaryObject(dialComponent, testCase); + verify(dial); + compare(dial.value, 0.0); + compare(dial.from, 0.0); + compare(dial.to, 1.0); + compare(dial.stepSize, 0.0); + verify(dial.activeFocusOnTab); + verify(!dial.pressed); + } + + function test_value() { + var dial = createTemporaryObject(dialComponent, testCase); + verify(dial); + compare(dial.value, 0.0); + + dial.value = 0.5; + compare(dial.value, 0.5); + + dial.value = 1.0; + compare(dial.value, 1.0); + + dial.value = -1.0; + compare(dial.value, 0.0); + + dial.value = 2.0; + compare(dial.value, 1.0); + } + + function test_range() { + var dial = createTemporaryObject(dialComponent, testCase); + verify(dial); + + dial.from = 0; + dial.to = 100; + dial.value = 50; + compare(dial.from, 0); + compare(dial.to, 100); + compare(dial.value, 50); + compare(dial.position, 0.5); + + dial.value = 1000 + compare(dial.value, 100); + compare(dial.position, 1); + + dial.value = -1 + compare(dial.value, 0); + compare(dial.position, 0); + + dial.from = 25 + compare(dial.from, 25); + compare(dial.value, 25); + compare(dial.position, 0); + + dial.to = 75 + compare(dial.to, 75); + compare(dial.value, 25); + compare(dial.position, 0); + + dial.value = 50 + compare(dial.value, 50); + compare(dial.position, 0.5); + } + + function test_inverted() { + var dial = createTemporaryObject(dialComponent, testCase, { from: 1.0, to: -1.0 }); + verify(dial); + compare(dial.from, 1.0); + compare(dial.to, -1.0); + compare(dial.value, 0.0); + compare(dial.position, 0.5); + + dial.value = 2.0; + compare(dial.value, 1.0); + compare(dial.position, 0.0); + + dial.value = -2.0; + compare(dial.value, -1.0); + compare(dial.position, 1.0); + + dial.value = 0.0; + compare(dial.value, 0.0); + compare(dial.position, 0.5); + } + + SignalSpy { + id: pressSpy + signalName: "pressedChanged" + } + + function test_pressed() { + var dial = createTemporaryObject(dialComponent, testCase); + verify(dial); + + pressSpy.target = dial; + verify(pressSpy.valid); + verify(!dial.pressed); + + mousePress(dial, dial.width / 2, dial.height / 2); + verify(dial.pressed); + compare(pressSpy.count, 1); + + mouseRelease(dial, dial.width / 2, dial.height / 2); + verify(!dial.pressed); + compare(pressSpy.count, 2); + + var touch = touchEvent(dial); + touch.press(0).commit(); + verify(dial.pressed); + compare(pressSpy.count, 3); + + touch.release(0).commit(); + verify(!dial.pressed); + compare(pressSpy.count, 4); + } + + SignalSpy { + id: valueSpy + signalName: "valueChanged" + } + + function test_dragging_data() { + return [ + { tag: "default", from: 0, to: 1, leftValue: 0.20, topValue: 0.5, rightValue: 0.8, bottomValue: 1.0, live: false }, + { tag: "scaled2", from: 0, to: 2, leftValue: 0.4, topValue: 1.0, rightValue: 1.6, bottomValue: 2.0, live: false }, + { tag: "scaled1", from: -1, to: 0, leftValue: -0.8, topValue: -0.5, rightValue: -0.2, bottomValue: 0.0, live: false }, + { tag: "live", from: 0, to: 1, leftValue: 0.20, topValue: 0.5, rightValue: 0.8, bottomValue: 1.0, live: true } + ] + } + + function test_dragging(data) { + var dial = createTemporaryObject(dialComponent, testCase); + verify(dial); + + dial.wrap = true; + verify(dial.wrap); + dial.from = data.from; + dial.to = data.to; + dial.live = data.live; + + valueSpy.target = dial; + verify(valueSpy.valid); + + var moveSpy = createTemporaryObject(signalSpy, testCase, {target: dial, signalName: "moved"}); + verify(moveSpy.valid); + + var minimumExpectedValueCount = data.live ? 2 : 1; + + // drag to the left + // we always add or subtract 1 to ensure we start the drag from the opposite side + // of where we're dragging to, for more reliable tests + mouseDrag(dial, dial.width / 2 + 1, dial.height / 2, -dial.width / 2, 0, Qt.LeftButton); + fuzzyCompare(dial.value, data.leftValue, 0.1); + verify(valueSpy.count >= minimumExpectedValueCount, "expected valueChanged to be emitted at least " + + minimumExpectedValueCount + " time(s), but it was only emitted " + valueSpy.count + " time(s)"); + valueSpy.clear(); + verify(moveSpy.count > 0); + moveSpy.clear(); + + // drag to the top + mouseDrag(dial, dial.width / 2, dial.height / 2 + 1, 0, -dial.height / 2, Qt.LeftButton); + fuzzyCompare(dial.value, data.topValue, 0.1); + verify(valueSpy.count >= minimumExpectedValueCount, "expected valueChanged to be emitted at least " + + minimumExpectedValueCount + " time(s), but it was only emitted " + valueSpy.count + " time(s)"); + valueSpy.clear(); + verify(moveSpy.count > 0); + moveSpy.clear(); + + // drag to the right + mouseDrag(dial, dial.width / 2 - 1, dial.height / 2, dial.width / 2, 0, Qt.LeftButton); + fuzzyCompare(dial.value, data.rightValue, 0.1); + verify(valueSpy.count >= minimumExpectedValueCount, "expected valueChanged to be emitted at least " + + minimumExpectedValueCount + " time(s), but it was only emitted " + valueSpy.count + " time(s)"); + valueSpy.clear(); + verify(moveSpy.count > 0); + moveSpy.clear(); + + // drag to the bottom (* 0.6 to ensure we don't go over to the minimum position) + mouseDrag(dial, dial.width / 2, dial.height / 2 - 1, 10, dial.height / 2, Qt.LeftButton); + fuzzyCompare(dial.value, data.bottomValue, 0.1); + verify(valueSpy.count >= minimumExpectedValueCount, "expected valueChanged to be emitted at least " + + minimumExpectedValueCount + " time(s), but it was only emitted " + valueSpy.count + " time(s)"); + valueSpy.clear(); + verify(moveSpy.count > 0); + moveSpy.clear(); + } + + function test_nonWrapping() { + var dial = createTemporaryObject(dialComponent, testCase); + verify(dial); + + compare(dial.wrap, false); + dial.value = 0; + + // Ensure that dragging from bottom left to bottom right doesn't work. + var yPos = dial.height * 0.75; + mousePress(dial, dial.width * 0.25, yPos, Qt.LeftButton); + var positionAtPress = dial.position; + mouseMove(dial, dial.width * 0.5, yPos); + compare(dial.position, positionAtPress); + mouseMove(dial, dial.width * 0.75, yPos); + compare(dial.position, positionAtPress); + mouseRelease(dial, dial.width * 0.75, yPos, Qt.LeftButton); + compare(dial.position, positionAtPress); + + // Try the same thing, but a bit higher. + yPos = dial.height * 0.6; + mousePress(dial, dial.width * 0.25, yPos, Qt.LeftButton); + positionAtPress = dial.position; + mouseMove(dial, dial.width * 0.5, yPos); + compare(dial.position, positionAtPress); + mouseMove(dial, dial.width * 0.75, yPos); + compare(dial.position, positionAtPress); + mouseRelease(dial, dial.width * 0.75, yPos, Qt.LeftButton); + compare(dial.position, positionAtPress); + + // Going from below the center of the dial to above it should work (once it gets above the center). + mousePress(dial, dial.width * 0.25, dial.height * 0.75, Qt.LeftButton); + positionAtPress = dial.position; + mouseMove(dial, dial.width * 0.5, dial.height * 0.6); + compare(dial.position, positionAtPress); + mouseMove(dial, dial.width * 0.75, dial.height * 0.4); + verify(dial.position > positionAtPress); + mouseRelease(dial, dial.width * 0.75, dial.height * 0.3, Qt.LeftButton); + verify(dial.position > positionAtPress); + } + + function test_touch() { + var dial = createTemporaryObject(dialComponent, testCase); + verify(dial); + + var touch = touchEvent(dial); + + // Ensure that dragging from bottom left to bottom right doesn't work. + var yPos = dial.height * 0.75; + touch.press(0, dial, dial.width * 0.25, yPos).commit(); + var positionAtPress = dial.position; + touch.move(0, dial, dial.width * 0.5, yPos).commit(); + compare(dial.position, positionAtPress); + touch.move(0, dial, dial.width * 0.75, yPos).commit(); + compare(dial.position, positionAtPress); + touch.release(0, dial, dial.width * 0.75, yPos).commit(); + compare(dial.position, positionAtPress); + + // Try the same thing, but a bit higher. + yPos = dial.height * 0.6; + touch.press(0, dial, dial.width * 0.25, yPos).commit(); + positionAtPress = dial.position; + touch.move(0, dial, dial.width * 0.5, yPos).commit(); + compare(dial.position, positionAtPress); + touch.move(0, dial, dial.width * 0.75, yPos).commit(); + compare(dial.position, positionAtPress); + touch.release(0, dial, dial.width * 0.75, yPos).commit(); + compare(dial.position, positionAtPress); + + // Going from below the center of the dial to above it should work (once it gets above the center). + touch.press(0, dial, dial.width * 0.25, dial.height * 0.75).commit(); + positionAtPress = dial.position; + touch.move(0, dial, dial.width * 0.5, dial.height * 0.6).commit(); + compare(dial.position, positionAtPress); + touch.move(0, dial, dial.width * 0.75, dial.height * 0.4).commit(); + verify(dial.position > positionAtPress); + touch.release(0, dial, dial.width * 0.75, dial.height * 0.3).commit(); + verify(dial.position > positionAtPress); + } + + function test_multiTouch() { + var dial1 = createTemporaryObject(dialComponent, testCase); + verify(dial1); + + var touch = touchEvent(dial1); + touch.press(0, dial1).commit().move(0, dial1, dial1.width / 4, dial1.height / 4).commit(); + compare(dial1.pressed, true); + verify(dial1.position > 0.0); + + var pos1Before = dial1.position; + + // second touch point on the same control is ignored + touch.stationary(0).press(1, dial1, 0, 0).commit() + touch.stationary(0).move(1, dial1).commit() + touch.stationary(0).release(1).commit() + compare(dial1.pressed, true); + compare(dial1.position, pos1Before); + + var dial2 = createTemporaryObject(dialComponent, testCase, {y: dial1.height}); + verify(dial2); + + // press the second dial + touch.stationary(0).press(2, dial2, 0, 0).commit(); + compare(dial2.pressed, true); + compare(dial2.position, 0.0); + + pos1Before = dial1.position; + var pos2Before = dial2.position; + + // move both dials + touch.move(0, dial1).move(2, dial2, dial2.width / 4, dial2.height / 4).commit(); + compare(dial1.pressed, true); + verify(dial1.position !== pos1Before); + compare(dial2.pressed, true); + verify(dial2.position !== pos2Before); + + // release both dials + touch.release(0, dial1).release(2, dial2).commit(); + compare(dial1.pressed, false); + compare(dial1.value, dial1.position); + compare(dial2.pressed, false); + compare(dial2.value, dial2.position); + } + + property Component focusTest: Component { + FocusScope { + signal receivedKeyPress + + Component.onCompleted: forceActiveFocus() + anchors.fill: parent + Keys.onPressed: receivedKeyPress() + } + } + + SignalSpy { + id: parentEventSpy + } + + function test_keyboardNavigation() { + var dial = createTemporaryObject(dialComponent, testCase); + verify(dial); + + var focusScope = createTemporaryObject(focusTest, testCase); + verify(focusScope); + + var moveCount = 0; + + // Tests that we've accepted events that we're interested in. + parentEventSpy.target = focusScope; + parentEventSpy.signalName = "receivedKeyPress"; + + var moveSpy = createTemporaryObject(signalSpy, testCase, {target: dial, signalName: "moved"}); + verify(moveSpy.valid); + + dial.parent = focusScope; + compare(dial.activeFocusOnTab, true); + compare(dial.value, 0); + + dial.focus = true; + compare(dial.activeFocus, true); + dial.stepSize = 0.1; + + keyClick(Qt.Key_Left); + compare(parentEventSpy.count, 0); + compare(moveSpy.count, moveCount); + compare(dial.value, 0); + + var oldValue = 0.0; + var keyPairs = [[Qt.Key_Left, Qt.Key_Right], [Qt.Key_Down, Qt.Key_Up]]; + for (var keyPairIndex = 0; keyPairIndex < 2; ++keyPairIndex) { + for (var i = 1; i <= 10; ++i) { + oldValue = dial.value; + keyClick(keyPairs[keyPairIndex][1]); + compare(parentEventSpy.count, 0); + if (oldValue !== dial.value) + compare(moveSpy.count, ++moveCount); + compare(dial.value, dial.stepSize * i); + } + + compare(dial.value, dial.to); + + for (i = 10; i > 0; --i) { + oldValue = dial.value; + keyClick(keyPairs[keyPairIndex][0]); + compare(parentEventSpy.count, 0); + if (oldValue !== dial.value) + compare(moveSpy.count, ++moveCount); + compare(dial.value, dial.stepSize * (i - 1)); + } + } + + dial.value = 0.5; + + keyClick(Qt.Key_Home); + compare(parentEventSpy.count, 0); + compare(moveSpy.count, ++moveCount); + compare(dial.value, dial.from); + + keyClick(Qt.Key_Home); + compare(parentEventSpy.count, 0); + compare(moveSpy.count, moveCount); + compare(dial.value, dial.from); + + keyClick(Qt.Key_End); + compare(parentEventSpy.count, 0); + compare(moveSpy.count, ++moveCount); + compare(dial.value, dial.to); + + keyClick(Qt.Key_End); + compare(parentEventSpy.count, 0); + compare(moveSpy.count, moveCount); + compare(dial.value, dial.to); + } + + function test_snapMode_data(immediate) { + return [ + { tag: "NoSnap", snapMode: Dial.NoSnap, from: 0, to: 2, values: [0, 0, 1], positions: [0, 0.5, 0.5] }, + { tag: "SnapAlways (0..2)", snapMode: Dial.SnapAlways, from: 0, to: 2, values: [0.0, 0.0, 1.0], positions: [0.0, 0.5, 0.5] }, + { tag: "SnapAlways (1..3)", snapMode: Dial.SnapAlways, from: 1, to: 3, values: [1.0, 1.0, 2.0], positions: [0.0, 0.5, 0.5] }, + { tag: "SnapAlways (-1..1)", snapMode: Dial.SnapAlways, from: -1, to: 1, values: [0.0, 0.0, 0.0], positions: [0.5, 0.5, 0.5] }, + { tag: "SnapAlways (1..-1)", snapMode: Dial.SnapAlways, from: 1, to: -1, values: [1.0, 1.0, 0.0], positions: [0.0, 0.5, 0.5] }, + { tag: "SnapOnRelease (0..2)", snapMode: Dial.SnapOnRelease, from: 0, to: 2, values: [0.0, 0.0, 1.0], positions: [0.0, 0.5, 0.5] }, + { tag: "SnapOnRelease (1..3)", snapMode: Dial.SnapOnRelease, from: 1, to: 3, values: [1.0, 1.0, 2.0], positions: [0.0, 0.5, 0.5] }, + { tag: "SnapOnRelease (-1..1)", snapMode: Dial.SnapOnRelease, from: -1, to: 1, values: [0.0, 0.0, 0.0], positions: [immediate ? 0.0 : 0.5, 0.5, 0.5] }, + { tag: "SnapOnRelease (1..-1)", snapMode: Dial.SnapOnRelease, from: 1, to: -1, values: [1.0, 1.0, 0.0], positions: [0.0, 0.5, 0.5] } + ] + } + + function test_snapMode_mouse_data() { + return test_snapMode_data(true) + } + + function test_snapMode_mouse(data) { + var dial = createTemporaryObject(dialComponent, testCase, {live: false}); + verify(dial); + + dial.snapMode = data.snapMode; + dial.from = data.from; + dial.to = data.to; + dial.stepSize = 0.2; + + var fuzz = 0.055; + + mousePress(dial, dial.width * 0.25, dial.height * 0.75); + fuzzyCompare(dial.value, data.values[0], fuzz); + fuzzyCompare(dial.position, data.positions[0], fuzz); + + mouseMove(dial, dial.width * 0.5, dial.height * 0.25); + fuzzyCompare(dial.value, data.values[1], fuzz); + fuzzyCompare(dial.position, data.positions[1], fuzz); + + mouseRelease(dial, dial.width * 0.5, dial.height * 0.25); + fuzzyCompare(dial.value, data.values[2], fuzz); + fuzzyCompare(dial.position, data.positions[2], fuzz); + } + + function test_snapMode_touch_data() { + return test_snapMode_data(false) + } + + function test_snapMode_touch(data) { + var dial = createTemporaryObject(dialComponent, testCase, {live: false}); + verify(dial); + + dial.snapMode = data.snapMode; + dial.from = data.from; + dial.to = data.to; + dial.stepSize = 0.2; + + var fuzz = 0.05; + + var touch = touchEvent(dial); + touch.press(0, dial, dial.width * 0.25, dial.height * 0.75).commit() + compare(dial.value, data.values[0]); + compare(dial.position, data.positions[0]); + + touch.move(0, dial, dial.width * 0.5, dial.height * 0.25).commit(); + fuzzyCompare(dial.value, data.values[1], fuzz); + fuzzyCompare(dial.position, data.positions[1], fuzz); + + touch.release(0, dial, dial.width * 0.5, dial.height * 0.25).commit(); + fuzzyCompare(dial.value, data.values[2], fuzz); + fuzzyCompare(dial.position, data.positions[2], fuzz); + } + + function test_wheel_data() { + return [ + { tag: "horizontal", orientation: Qt.Horizontal, dx: 120, dy: 0 }, + { tag: "vertical", orientation: Qt.Vertical, dx: 0, dy: 120 } + ] + } + + function test_wheel(data) { + var control = createTemporaryObject(dialComponent, testCase, {wheelEnabled: true, orientation: data.orientation}) + verify(control) + + compare(control.value, 0.0) + + mouseWheel(control, control.width / 2, control.height / 2, data.dx, data.dy) + compare(control.value, 0.1) + compare(control.position, 0.1) + + control.stepSize = 0.2 + + mouseWheel(control, control.width / 2, control.height / 2, data.dx, data.dy) + compare(control.value, 0.3) + compare(control.position, 0.3) + + control.stepSize = 10.0 + + mouseWheel(control, control.width / 2, control.height / 2, -data.dx, -data.dy) + compare(control.value, 0.0) + compare(control.position, 0.0) + + control.to = 10.0 + control.stepSize = 5.0 + + mouseWheel(control, control.width / 2, control.height / 2, data.dx, data.dy) + compare(control.value, 5.0) + compare(control.position, 0.5) + + mouseWheel(control, control.width / 2, control.height / 2, 0.5 * data.dx, 0.5 * data.dy) + compare(control.value, 7.5) + compare(control.position, 0.75) + + mouseWheel(control, control.width / 2, control.height / 2, -data.dx, -data.dy) + compare(control.value, 2.5) + compare(control.position, 0.25) + } + + function test_nullHandle() { + var control = createTemporaryObject(dialComponent, testCase) + verify(control) + + control.handle = null + + mousePress(control) + verify(control.pressed, true) + + mouseRelease(control) + compare(control.pressed, false) + } + + function move(inputEventType, control, x, y) { + if (inputEventType === "mouseInput") { + mouseMove(control, x, y); + } else { + var touch = touchEvent(control); + touch.move(0, control, x, y).commit(); + } + } + + function press(inputEventType, control, x, y) { + if (inputEventType === "mouseInput") { + mousePress(control, x, y); + } else { + var touch = touchEvent(control); + touch.press(0, control, x, y).commit(); + } + } + + function release(inputEventType, control, x, y) { + if (inputEventType === "mouseInput") { + mouseRelease(control, x, y); + } else { + var touch = touchEvent(control); + touch.release(0, control, x, y).commit(); + } + } + + function test_horizontalAndVertical_data() { + var data = [ + { eventType: "mouseInput", inputMode: Dial.Vertical, moveToX: 0.5, moveToY: 0.25, expectedPosition: 0.125 }, + // Horizontal movement should have no effect on a vertical dial. + { eventType: "mouseInput", inputMode: Dial.Vertical, moveToX: 2.0, moveToY: 0.25, expectedPosition: 0.125 }, + { eventType: "mouseInput", inputMode: Dial.Vertical, moveToX: 0.5, moveToY: 0.0, expectedPosition: 0.25 }, + { eventType: "mouseInput", inputMode: Dial.Vertical, moveToX: 0.5, moveToY: -1.5, expectedPosition: 1.0 }, + // Going above the drag area shouldn't make the position higher than 1.0. + { eventType: "mouseInput", inputMode: Dial.Vertical, moveToX: 0.5, moveToY: -2.0, expectedPosition: 1.0 }, + // Try to decrease the position by moving the mouse down. + // The dial's position is 0 before the press event, so nothing should happen. + { eventType: "mouseInput", inputMode: Dial.Vertical, moveToX: 0.5, moveToY: 1.25, expectedPosition: 0.0 }, + + { eventType: "mouseInput", inputMode: Dial.Horizontal, moveToX: 0.75, moveToY: 0.5, expectedPosition: 0.125 }, + // Vertical movement should have no effect on a horizontal dial. + { eventType: "mouseInput", inputMode: Dial.Horizontal, moveToX: 0.75, moveToY: 2.0, expectedPosition: 0.125 }, + { eventType: "mouseInput", inputMode: Dial.Horizontal, moveToX: 1.0, moveToY: 0.5, expectedPosition: 0.25 }, + { eventType: "mouseInput", inputMode: Dial.Horizontal, moveToX: 1.5, moveToY: 0.5, expectedPosition: 0.5 }, + { eventType: "mouseInput", inputMode: Dial.Horizontal, moveToX: 2.5, moveToY: 0.5, expectedPosition: 1.0 }, + // Going above the drag area shouldn't make the position higher than 1.0. + { eventType: "mouseInput", inputMode: Dial.Horizontal, moveToX: 2.525, moveToY: 0.5, expectedPosition: 1.0 }, + // Try to decrease the position by moving the mouse to the left. + // The dial's position is 0 before the press event, so nothing should happen. + { eventType: "mouseInput", inputMode: Dial.Vertical, moveToX: 0.25, moveToY: 0.5, expectedPosition: 0.0 } + ]; + + // Do the same tests for touch by copying the mouse tests and adding them to the end of the array. + var mouseTestCount = data.length; + for (var i = mouseTestCount; i < mouseTestCount * 2; ++i) { + // Shallow-copy the object. + data[i] = JSON.parse(JSON.stringify(data[i - mouseTestCount])); + data[i].eventType = "touchInput"; + } + + for (i = 0; i < data.length; ++i) { + var row = data[i]; + row.tag = "eventType=" + row.eventType + ", " + + "inputMode=" + (row.inputMode === Dial.Vertical ? "Vertical" : "Horizontal") + ", " + + "moveToX=" + row.moveToX + ", moveToY=" + row.moveToY + ", " + + "expectedPosition=" + row.expectedPosition; + } + + return data; + } + + function test_horizontalAndVertical(data) { + var control = createTemporaryObject(dialComponent, testCase, { inputMode: data.inputMode }); + verify(control); + + press(data.eventType, control); + compare(control.pressed, true); + // The position shouldn't change until the mouse has actually moved. + compare(control.position, 0); + + move(data.eventType, control, control.width * data.moveToX, control.width * data.moveToY); + compare(control.position, data.expectedPosition); + + release(data.eventType, control, control.width * data.moveToX, control.width * data.moveToY); + compare(control.pressed, false); + compare(control.position, data.expectedPosition); + } + + function test_integerStepping() { + var dial = createTemporaryObject(dialComponent, testCase) + verify(dial) + + dial.from = 1 + dial.to = 8 + dial.stepSize = 1 + + for (let i = 1; i < 8; ++i) { + // compare as strings to avoid a fuzzy compare; we want an exact match + compare(""+dial.value, ""+1) + keyClick(Qt.Key_Right) + } + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_dialog.qml b/tests/auto/quickcontrols2/controls/data/tst_dialog.qml new file mode 100644 index 0000000000..b227d82e1b --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_dialog.qml @@ -0,0 +1,494 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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.Window +import QtTest +import QtQuick.Controls +import QtQuick.Templates as T + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "Dialog" + + Component { + id: dialog + Dialog { } + } + + Component { + id: qtbug71444 + Dialog { + header: null + footer: null + } + } + + Component { + id: buttonBox + DialogButtonBox { } + } + + Component { + id: signalSpy + SignalSpy { } + } + + function init() { + tryCompare(testCase.Window.window, "active", true) + } + + function test_defaults() { + var control = createTemporaryObject(dialog, testCase) + verify(control) + verify(control.header) + verify(control.footer) + compare(control.standardButtons, 0) + } + + function test_accept() { + var control = createTemporaryObject(dialog, testCase) + + var openedSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "opened"}) + verify(openedSpy.valid) + + control.open() + openedSpy.wait() + compare(openedSpy.count, 1) + verify(control.visible) + + var acceptedSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "accepted"}) + verify(acceptedSpy.valid) + + var closedSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "closed"}) + verify(closedSpy.valid) + + control.accept() + compare(acceptedSpy.count, 1) + compare(control.result, Dialog.Accepted) + + tryCompare(control, "visible", false) + compare(acceptedSpy.count, 1) + compare(closedSpy.count, 1) + } + + function test_reject() { + var control = createTemporaryObject(dialog, testCase) + + var openedSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "opened"}) + verify(openedSpy.valid) + + control.open() + openedSpy.wait() + compare(openedSpy.count, 1) + verify(control.visible) + + var rejectedSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "rejected"}) + verify(rejectedSpy.valid) + + var closedSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "closed"}) + verify(closedSpy.valid) + + control.reject() + compare(rejectedSpy.count, 1) + compare(control.result, Dialog.Rejected) + + tryCompare(control, "visible", false) + compare(rejectedSpy.count, 1) + compare(closedSpy.count, 1) + + // Check that rejected() is emitted when CloseOnEscape is triggered. + control.x = 10 + control.y = 10 + control.width = 100 + control.height = 100 + control.closePolicy = Popup.CloseOnEscape + control.open() + verify(control.visible) + + keyPress(Qt.Key_Escape) + compare(rejectedSpy.count, 2) + tryCompare(control, "visible", false) + compare(rejectedSpy.count, 2) + compare(closedSpy.count, 2) + + keyRelease(Qt.Key_Escape) + compare(rejectedSpy.count, 2) + compare(closedSpy.count, 2) + + // Check that rejected() is emitted when CloseOnPressOutside is triggered. + control.closePolicy = Popup.CloseOnPressOutside + control.open() + verify(control.visible) + + mousePress(testCase, 1, 1) + compare(rejectedSpy.count, 3) + tryCompare(control, "visible", false) + compare(rejectedSpy.count, 3) + compare(closedSpy.count, 3) + + mouseRelease(testCase, 1, 1) + compare(rejectedSpy.count, 3) + compare(closedSpy.count, 3) + + // Check that rejected() is emitted when CloseOnReleaseOutside is triggered. + // For this, we need to make the dialog modal, because the overlay won't accept + // the press event because it doesn't want to block the press. + control.modal = true + control.closePolicy = Popup.CloseOnReleaseOutside + control.open() + verify(control.visible) + + mousePress(testCase, 1, 1) + compare(rejectedSpy.count, 3) + verify(control.visible) + + mouseRelease(testCase, 1, 1) + compare(rejectedSpy.count, 4) + tryCompare(control, "visible", false) + compare(rejectedSpy.count, 4) + compare(closedSpy.count, 4) + } + + function test_buttonBox_data() { + return [ + { tag: "default" }, + { tag: "custom", custom: true } + ] + } + + function test_buttonBox(data) { + var control = createTemporaryObject(dialog, testCase) + + if (data.custom) + control.footer = buttonBox.createObject(testCase) + control.standardButtons = Dialog.Ok | Dialog.Cancel + var box = control.footer + verify(box) + compare(box.standardButtons, Dialog.Ok | Dialog.Cancel) + + var acceptedSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "accepted"}) + verify(acceptedSpy.valid) + box.accepted() + compare(acceptedSpy.count, 1) + + var rejectedSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "rejected"}) + verify(rejectedSpy.valid) + box.rejected() + compare(rejectedSpy.count, 1) + } + + function test_qtbug71444() { + var control = createTemporaryObject(qtbug71444, testCase) + verify(control) + } + + function test_standardButtons() { + var control = createTemporaryObject(dialog, testCase) + + control.standardButtons = Dialog.Ok + + var box = control.footer ? control.footer : control.header + verify(box) + compare(box.count, 1) + var okButton = box.itemAt(0) + verify(okButton) + compare(okButton.text.toUpperCase(), "OK") + + control.standardButtons = Dialog.Cancel + compare(box.count, 1) + var cancelButton = control.footer.itemAt(0) + verify(cancelButton) + compare(cancelButton.text.toUpperCase(), "CANCEL") + + control.standardButtons = Dialog.Ok | Dialog.Cancel + compare(box.count, 2) + if (box.itemAt(0).text.toUpperCase() === "OK") { + okButton = box.itemAt(0) + cancelButton = box.itemAt(1) + } else { + okButton = box.itemAt(1) + cancelButton = box.itemAt(0) + } + verify(okButton) + verify(cancelButton) + compare(okButton.text.toUpperCase(), "OK") + compare(cancelButton.text.toUpperCase(), "CANCEL") + + control.standardButtons = 0 + compare(box.count, 0) + } + + function test_layout() { + var control = createTemporaryObject(dialog, testCase, {width: 100, height: 100}) + verify(control) + + var openedSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "opened"}) + verify(openedSpy.valid) + + control.open() + openedSpy.wait() + compare(openedSpy.count, 1) + verify(control.visible) + + compare(control.width, 100) + compare(control.height, 100) + compare(control.contentItem.width, control.availableWidth) + compare(control.contentItem.height, control.availableHeight) + + control.header = buttonBox.createObject(control.contentItem) + compare(control.header.width, control.width) + verify(control.header.height > 0) + compare(control.contentItem.width, control.availableWidth) + compare(control.contentItem.height, control.availableHeight - control.header.height) + + control.footer = buttonBox.createObject(control.contentItem) + compare(control.footer.width, control.width) + verify(control.footer.height > 0) + compare(control.contentItem.width, control.availableWidth) + compare(control.contentItem.height, control.availableHeight - control.header.height - control.footer.height) + + control.topPadding = 9 + control.leftPadding = 2 + control.rightPadding = 6 + control.bottomPadding = 7 + + compare(control.header.x, 0) + compare(control.header.y, 0) + compare(control.header.width, control.width) + verify(control.header.height > 0) + + compare(control.footer.x, 0) + compare(control.footer.y, control.height - control.footer.height) + compare(control.footer.width, control.width) + verify(control.footer.height > 0) + + compare(control.contentItem.x, control.leftPadding) + compare(control.contentItem.y, control.topPadding + control.header.height) + compare(control.contentItem.width, control.availableWidth) + compare(control.contentItem.height, control.availableHeight - control.header.height - control.footer.height) + + control.header.visible = false + compare(control.contentItem.x, control.leftPadding) + compare(control.contentItem.y, control.topPadding) + compare(control.contentItem.width, control.availableWidth) + compare(control.contentItem.height, control.availableHeight - control.footer.height) + + control.footer.visible = false + compare(control.contentItem.x, control.leftPadding) + compare(control.contentItem.y, control.topPadding) + compare(control.contentItem.width, control.availableWidth) + compare(control.contentItem.height, control.availableHeight) + + control.contentItem.implicitWidth = 50 + control.contentItem.implicitHeight = 60 + compare(control.implicitWidth, control.contentItem.implicitWidth + control.leftPadding + control.rightPadding) + compare(control.implicitHeight, control.contentItem.implicitHeight + control.topPadding + control.bottomPadding) + + control.header.visible = true + compare(control.implicitHeight, control.contentItem.implicitHeight + control.topPadding + control.bottomPadding + + control.header.implicitHeight) + + control.footer.visible = true + compare(control.implicitHeight, control.contentItem.implicitHeight + control.topPadding + control.bottomPadding + + control.header.implicitHeight + control.footer.implicitHeight) + + control.header.implicitWidth = 150 + compare(control.implicitWidth, control.header.implicitWidth) + + control.footer.implicitWidth = 160 + compare(control.implicitWidth, control.footer.implicitWidth) + } + + function test_spacing_data() { + return [ + { tag: "content", header: false, content: true, footer: false }, + { tag: "header,content", header: true, content: true, footer: false }, + { tag: "content,footer", header: false, content: true, footer: true }, + { tag: "header,content,footer", header: true, content: true, footer: true }, + { tag: "header,footer", header: true, content: false, footer: true }, + { tag: "header", header: true, content: false, footer: false }, + { tag: "footer", header: false, content: false, footer: true }, + ] + } + + function test_spacing(data) { + var control = createTemporaryObject(dialog, testCase, {spacing: 20, width: 100, height: 100}) + verify(control) + + var openedSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "opened"}) + verify(openedSpy.valid) + + control.open() + openedSpy.wait() + compare(openedSpy.count, 1) + verify(control.visible) + + control.contentItem.visible = data.content + control.header = buttonBox.createObject(control.contentItem, {visible: data.header}) + control.footer = buttonBox.createObject(control.contentItem, {visible: data.footer}) + + compare(control.header.x, 0) + compare(control.header.y, 0) + compare(control.header.width, control.width) + verify(control.header.height > 0) + + compare(control.footer.x, 0) + compare(control.footer.y, control.height - control.footer.height) + compare(control.footer.width, control.width) + verify(control.footer.height > 0) + + compare(control.contentItem.x, control.leftPadding) + compare(control.contentItem.y, control.topPadding + (data.header ? control.header.height + control.spacing : 0)) + compare(control.contentItem.width, control.availableWidth) + compare(control.contentItem.height, control.availableHeight + - (data.header ? control.header.height + control.spacing : 0) + - (data.footer ? control.footer.height + control.spacing : 0)) + } + + function test_signals_data() { + return [ + { tag: "Ok", standardButton: Dialog.Ok, signalName: "accepted" }, + { tag: "Open", standardButton: Dialog.Open, signalName: "accepted" }, + { tag: "Save", standardButton: Dialog.Save, signalName: "accepted" }, + { tag: "Cancel", standardButton: Dialog.Cancel, signalName: "rejected" }, + { tag: "Close", standardButton: Dialog.Close, signalName: "rejected" }, + { tag: "Discard", standardButton: Dialog.Discard, signalName: "discarded" }, + { tag: "Apply", standardButton: Dialog.Apply, signalName: "applied" }, + { tag: "Reset", standardButton: Dialog.Reset, signalName: "reset" }, + { tag: "RestoreDefaults", standardButton: Dialog.RestoreDefaults, signalName: "reset" }, + { tag: "Help", standardButton: Dialog.Help, signalName: "helpRequested" }, + { tag: "SaveAll", standardButton: Dialog.SaveAll, signalName: "accepted" }, + { tag: "Yes", standardButton: Dialog.Yes, signalName: "accepted" }, + { tag: "YesToAll", standardButton: Dialog.YesToAll, signalName: "accepted" }, + { tag: "No", standardButton: Dialog.No, signalName: "rejected" }, + { tag: "NoToAll", standardButton: Dialog.NoToAll, signalName: "rejected" }, + { tag: "Abort", standardButton: Dialog.Abort, signalName: "rejected" }, + { tag: "Retry", standardButton: Dialog.Retry, signalName: "accepted" }, + { tag: "Ignore", standardButton: Dialog.Ignore, signalName: "accepted" } + ] + } + + function test_signals(data) { + var control = createTemporaryObject(dialog, testCase) + verify(control) + + control.standardButtons = data.standardButton + var button = control.standardButton(data.standardButton) + verify(button) + + var buttonSpy = signalSpy.createObject(control.contentItem, {target: control, signalName: data.signalName}) + verify(buttonSpy.valid) + + button.clicked() + compare(buttonSpy.count, 1) + } + + Component { + id: qtbug85884 + ApplicationWindow { + property alias focusItemActiveFocus: item.activeFocus + property alias focusDialogVisible: dialog.visible + function closeAndOpen() { + dialog.close() + dialog.open() + dialog.close() + } + visible: true + Item { + id: item + focus: true + } + Dialog { + id: dialog + focus: true + visible: false + onActiveFocusChanged: { + if (!activeFocus) + visible = false + } + enter: Transition { + NumberAnimation { property: "opacity"; from: 0.0; to: 1.0; duration: 10 } + } + exit: Transition { + NumberAnimation { property: "opacity"; from: 1.0; to: 0.0; duration: 10 } + } + } + } + } + + function test_focusLeavingDialog(data) { + if (Qt.platform.pluginName === "offscreen") + skip("QTBUG-89909") + + var window = createTemporaryObject(qtbug85884, testCase) + verify(window) + tryCompare(window, "focusItemActiveFocus", true) + + window.focusDialogVisible = true + tryCompare(window, "focusDialogVisible", true) + tryCompare(window, "focusItemActiveFocus", false) + + window.focusDialogVisible = false + tryCompare(window, "focusDialogVisible", false) + tryCompare(window, "focusItemActiveFocus", true) + + window.focusDialogVisible = true + tryCompare(window, "focusDialogVisible", true) + tryCompare(window, "focusItemActiveFocus", false) + window.closeAndOpen() + tryCompare(window, "focusDialogVisible", false) + tryCompare(window, "focusItemActiveFocus", true) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_dialogbuttonbox.qml b/tests/auto/quickcontrols2/controls/data/tst_dialogbuttonbox.qml new file mode 100644 index 0000000000..706bf10dc1 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_dialogbuttonbox.qml @@ -0,0 +1,447 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 600 + height: 400 + visible: true + when: windowShown + name: "DialogButtonBox" + + Component { + id: buttonBox + DialogButtonBox { } + } + + Component { + id: button + Button { } + } + + Component { + id: signalSpy + SignalSpy { } + } + + function test_defaults() { + var control = createTemporaryObject(buttonBox, testCase) + verify(control) + compare(control.count, 0) + verify(control.delegate) + compare(control.standardButtons, 0) + } + + function test_standardButtons() { + var control = createTemporaryObject(buttonBox, testCase) + verify(control) + compare(control.count, 0) + + control.standardButtons = DialogButtonBox.Ok + compare(control.count, 1) + var okButton = control.itemAt(0) + verify(okButton) + compare(okButton.text.toUpperCase(), "OK") + + control.standardButtons = DialogButtonBox.Cancel + compare(control.count, 1) + var cancelButton = control.itemAt(0) + verify(cancelButton) + compare(cancelButton.text.toUpperCase(), "CANCEL") + + control.standardButtons = DialogButtonBox.Ok | DialogButtonBox.Cancel + compare(control.count, 2) + if (control.itemAt(0).text.toUpperCase() === "OK") { + okButton = control.itemAt(0) + cancelButton = control.itemAt(1) + } else { + okButton = control.itemAt(1) + cancelButton = control.itemAt(0) + } + verify(okButton) + verify(cancelButton) + compare(okButton.text.toUpperCase(), "OK") + compare(cancelButton.text.toUpperCase(), "CANCEL") + compare(control.standardButton(DialogButtonBox.Ok), okButton) + compare(control.standardButton(DialogButtonBox.Cancel), cancelButton) + + control.standardButtons = 0 + compare(control.count, 0) + + compare(control.standardButton(DialogButtonBox.Ok), null) + compare(control.standardButton(DialogButtonBox.Cancel), null) + } + + function test_attached() { + var control = createTemporaryObject(buttonBox, testCase) + verify(control) + + control.standardButtons = DialogButtonBox.Ok + var okButton = control.itemAt(0) + compare(okButton.DialogButtonBox.buttonBox, control) + compare(okButton.DialogButtonBox.buttonRole, DialogButtonBox.AcceptRole) + + var saveButton = button.createObject(control, {text: "Save"}) + compare(saveButton.DialogButtonBox.buttonBox, control) + compare(saveButton.DialogButtonBox.buttonRole, DialogButtonBox.InvalidRole) + saveButton.DialogButtonBox.buttonRole = DialogButtonBox.AcceptRole + compare(saveButton.DialogButtonBox.buttonRole, DialogButtonBox.AcceptRole) + + var closeButton = createTemporaryObject(button, null, {text: "Save"}) + compare(closeButton.DialogButtonBox.buttonBox, null) + compare(closeButton.DialogButtonBox.buttonRole, DialogButtonBox.InvalidRole) + closeButton.DialogButtonBox.buttonRole = DialogButtonBox.DestructiveRole + compare(closeButton.DialogButtonBox.buttonRole, DialogButtonBox.DestructiveRole) + control.addItem(closeButton) + compare(closeButton.DialogButtonBox.buttonBox, control) + + control.contentModel.clear() + compare(okButton.DialogButtonBox.buttonBox, null) + compare(saveButton.DialogButtonBox.buttonBox, null) + compare(closeButton.DialogButtonBox.buttonBox, null) + } + + function test_signals_data() { + return [ + { tag: "Ok", standardButton: DialogButtonBox.Ok, buttonRole: DialogButtonBox.AcceptRole, signalName: "accepted" }, + { tag: "Open", standardButton: DialogButtonBox.Open, buttonRole: DialogButtonBox.AcceptRole, signalName: "accepted" }, + { tag: "Save", standardButton: DialogButtonBox.Save, buttonRole: DialogButtonBox.AcceptRole, signalName: "accepted" }, + { tag: "Cancel", standardButton: DialogButtonBox.Cancel, buttonRole: DialogButtonBox.RejectRole, signalName: "rejected" }, + { tag: "Close", standardButton: DialogButtonBox.Close, buttonRole: DialogButtonBox.RejectRole, signalName: "rejected" }, + { tag: "Discard", standardButton: DialogButtonBox.Discard, buttonRole: DialogButtonBox.DestructiveRole, signalName: "discarded" }, + { tag: "Apply", standardButton: DialogButtonBox.Apply, buttonRole: DialogButtonBox.ApplyRole, signalName: "applied" }, + { tag: "Reset", standardButton: DialogButtonBox.Reset, buttonRole: DialogButtonBox.ResetRole, signalName: "reset" }, + { tag: "RestoreDefaults", standardButton: DialogButtonBox.RestoreDefaults, buttonRole: DialogButtonBox.ResetRole, signalName: "reset" }, + { tag: "Help", standardButton: DialogButtonBox.Help, buttonRole: DialogButtonBox.HelpRole, signalName: "helpRequested" }, + { tag: "SaveAll", standardButton: DialogButtonBox.SaveAll, buttonRole: DialogButtonBox.AcceptRole, signalName: "accepted" }, + { tag: "Yes", standardButton: DialogButtonBox.Yes, buttonRole: DialogButtonBox.YesRole, signalName: "accepted" }, + { tag: "YesToAll", standardButton: DialogButtonBox.YesToAll, buttonRole: DialogButtonBox.YesRole, signalName: "accepted" }, + { tag: "No", standardButton: DialogButtonBox.No, buttonRole: DialogButtonBox.NoRole, signalName: "rejected" }, + { tag: "NoToAll", standardButton: DialogButtonBox.NoToAll, buttonRole: DialogButtonBox.NoRole, signalName: "rejected" }, + { tag: "Abort", standardButton: DialogButtonBox.Abort, buttonRole: DialogButtonBox.RejectRole, signalName: "rejected" }, + { tag: "Retry", standardButton: DialogButtonBox.Retry, buttonRole: DialogButtonBox.AcceptRole, signalName: "accepted" }, + { tag: "Ignore", standardButton: DialogButtonBox.Ignore, buttonRole: DialogButtonBox.AcceptRole, signalName: "accepted" } + ] + } + + function test_signals(data) { + var control = createTemporaryObject(buttonBox, testCase) + verify(control) + + control.standardButtons = data.standardButton + compare(control.count, 1) + var button = control.itemAt(0) + verify(button) + compare(button.DialogButtonBox.buttonRole, data.buttonRole) + + var clickedSpy = signalSpy.createObject(control, {target: control, signalName: "clicked"}) + verify(clickedSpy.valid) + var roleSpy = signalSpy.createObject(control, {target: control, signalName: data.signalName}) + verify(roleSpy.valid) + + button.clicked() + compare(clickedSpy.count, 1) + compare(clickedSpy.signalArguments[0][0], button) + compare(roleSpy.count, 1) + } + + function test_buttonLayout_data() { + return [ + { tag: "WinLayout", buttonLayout: DialogButtonBox.WinLayout, button1Role: DialogButtonBox.AcceptRole, button2Role: DialogButtonBox.RejectRole }, + { tag: "MacLayout", buttonLayout: DialogButtonBox.MacLayout, button1Role: DialogButtonBox.RejectRole, button2Role: DialogButtonBox.AcceptRole }, + { tag: "KdeLayout", buttonLayout: DialogButtonBox.KdeLayout, button1Role: DialogButtonBox.AcceptRole, button2Role: DialogButtonBox.RejectRole }, + { tag: "GnomeLayout", buttonLayout: DialogButtonBox.GnomeLayout, button1Role: DialogButtonBox.RejectRole, button2Role: DialogButtonBox.AcceptRole }, + { tag: "AndroidLayout", buttonLayout: DialogButtonBox.AndroidLayout, button1Role: DialogButtonBox.RejectRole, button2Role: DialogButtonBox.AcceptRole } + ] + } + + function test_buttonLayout(data) { + var control = createTemporaryObject(buttonBox, testCase, {buttonLayout: data.buttonLayout, standardButtons: DialogButtonBox.Ok|DialogButtonBox.Cancel}) + verify(control) + + compare(control.count, 2) + + var button1 = control.itemAt(0) + verify(button1) + compare(button1.DialogButtonBox.buttonRole, data.button1Role) + + var button2 = control.itemAt(1) + verify(button2) + compare(button2.DialogButtonBox.buttonRole, data.button2Role) + } + + function test_implicitSize_data() { + return [ + { tag: "Ok", standardButtons: DialogButtonBox.Ok }, + { tag: "Yes|No", standardButtons: DialogButtonBox.Yes | DialogButtonBox.No } + ] + } + + // QTBUG-59719 + function test_implicitSize(data) { + var control = createTemporaryObject(buttonBox, testCase, {standardButtons: data.standardButtons}) + verify(control) + + var listView = control.contentItem + verify(listView && listView.hasOwnProperty("contentWidth")) + waitForRendering(listView) + + var implicitContentWidth = control.leftPadding + control.rightPadding + for (var i = 0; i < listView.contentItem.children.length; ++i) { + var button = listView.contentItem.children[i] + if (!button.hasOwnProperty("text")) + continue + implicitContentWidth += button.implicitWidth + } + + verify(implicitContentWidth > control.leftPadding + control.rightPadding) + verify(control.implicitWidth >= implicitContentWidth, qsTr("implicit width (%1) is less than content width (%2)").arg(control.implicitWidth).arg(implicitContentWidth)) + } + + Component { + id: okCancelBox + DialogButtonBox { + Button { + text: qsTr("OK") + } + Button { + text: qsTr("Cancel") + } + } + } + + function test_buttonSize() { + var control = createTemporaryObject(okCancelBox, testCase) + verify(control) + + var okButton = control.itemAt(0) + verify(okButton) + verify(okButton.width > 0) + + var cancelButton = control.itemAt(1) + verify(cancelButton) + verify(cancelButton.width > 0) + + compare(okButton.width + cancelButton.width, control.availableWidth - control.spacing) + } + + function test_oneButtonInFixedWidthBox() { + var control = createTemporaryObject(buttonBox, testCase, + { width: 400, standardButtons: Dialog.Close }) + verify(control) + + var listView = control.contentItem + waitForRendering(listView) + + var button = control.itemAt(0) + verify(button) + + // The button should never go outside of the box. + tryVerify(function() { return button.mapToItem(control, 0, 0).x >= 0 }, + 1000, "Expected left edge of button to be within left edge of DialogButtonBox (i.e. greater than or equal to 0)" + + ", but it's " + button.mapToItem(control, 0, 0).x) + tryVerify(function() { return button.mapToItem(control, 0, 0).x + button.width <= control.width }, + 1000, "Expected right edge of button to be within right edge of DialogButtonBox (i.e. less than or equal to " + + control.width + "), but it's " + (button.mapToItem(control, 0, 0).x + button.width)) + } + + Component { + id: dialogComponent + // Based on the Basic style, where a single button fills + // half the dialog's width and is aligned to the right. + Dialog { + id: control + standardButtons: Dialog.Ok + visible: true + + footer: DialogButtonBox { + id: box + visible: count > 0 + alignment: count === 1 ? Qt.AlignRight : undefined + + implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, + (count === 1 ? implicitContentWidth * 2 : implicitContentWidth) + leftPadding + rightPadding) + implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, + implicitContentHeight + topPadding + bottomPadding) + contentWidth: contentItem.contentWidth + + delegate: Button { + width: box.count === 1 ? box.availableWidth / 2 : undefined + } + } + } + } + + // QTBUG-73860 + function test_oneButtonAlignedRightInImplicitWidthBox() { + var dialog = createTemporaryObject(dialogComponent, testCase) + verify(dialog) + + var box = dialog.footer + var listView = box.contentItem + waitForRendering(listView) + + var button = box.itemAt(0) + verify(button) + + // The button should never go outside of the box. + tryVerify(function() { return button.mapToItem(box, 0, 0).x >= 0 }, + 1000, "Expected left edge of button to be within left edge of DialogButtonBox (i.e. greater than or equal to 0)" + + ", but it's " + button.mapToItem(box, 0, 0).x) + tryVerify(function() { return button.mapToItem(box, 0, 0).x + button.width <= box.width }, + 1000, "Expected right edge of button to be within right edge of DialogButtonBox (i.e. less than or equal to " + + box.width + "), but it's " + (button.mapToItem(box, 0, 0).x + button.width)) + compare(box.width, dialog.width) + // There's a single button and we align it to the right. + compare(box.contentItem.width, button.width) + compare(box.contentItem.x, box.width - box.rightPadding - box.contentItem.width) + } + + Component { + id: customButtonBox + + DialogButtonBox { + objectName: "customButtonBox" + alignment: Qt.AlignRight + + property alias okButton: okButton + + Button { + id: okButton + text: "OK" + + DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + } + } + } + + Component { + id: customButtonBoxTwoButtons + + DialogButtonBox { + objectName: "customButtonBoxTwoButtons" + alignment: Qt.AlignRight + + property alias okButton: okButton + + Button { + id: okButton + text: "OK" + + DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + } + Button { + text: "Cancel" + + DialogButtonBox.buttonRole: DialogButtonBox.RejectRole + } + } + } + + function test_changeCustomButtonText_data() { + return [ + { tag: "oneButton", component: customButtonBox }, + { tag: "twoButtons", component: customButtonBoxTwoButtons }, + ] + } + + // QTBUG-72886 + function test_changeCustomButtonText(data) { + var control = createTemporaryObject(customButtonBox, testCase, {}) + verify(control) + + var listView = control.contentItem + waitForRendering(listView) + + var button = control.okButton + verify(button) + button.text = "some longer text"; + + // The button should never go outside of the box. + tryVerify(function() { return button.mapToItem(control, 0, 0).x >= 0 }, + 1000, "Expected left edge of button to be within left edge of DialogButtonBox (i.e. greater than or equal to 0)" + + ", but it's " + button.mapToItem(control, 0, 0).x) + tryVerify(function() { return button.mapToItem(control, 0, 0).x + button.width <= control.width }, + 1000, "Expected right edge of button to be within right edge of DialogButtonBox (i.e. less than or equal to " + + control.width + "), but it's " + (button.mapToItem(control, 0, 0).x + button.width)) + } + + Component { + id: noRolesDialog + + Dialog { + footer: DialogButtonBox { + Button { text: "A" } + Button { text: "B" } + Button { text: "C" } + } + } + } + + function test_orderWithNoRoles() { + for (var i = 0; i < 10; ++i) { + var control = createTemporaryObject(noRolesDialog, testCase) + verify(control) + + control.open() + tryCompare(control, "opened", true) + var footer = control.footer + verify(footer) + waitForRendering(footer) + compare(footer.itemAt(0).text, "A") + compare(footer.itemAt(1).text, "B") + compare(footer.itemAt(2).text, "C") + } + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_frame.qml b/tests/auto/quickcontrols2/controls/data/tst_frame.qml new file mode 100644 index 0000000000..95c691441f --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_frame.qml @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "Frame" + + Component { + id: frame + Frame { } + } + + Component { + id: oneChildFrame + Frame { + Item { + implicitWidth: 100 + implicitHeight: 30 + } + } + } + + Component { + id: twoChildrenFrame + Frame { + Item { + implicitWidth: 100 + implicitHeight: 30 + } + Item { + implicitWidth: 200 + implicitHeight: 60 + } + } + } + + Component { + id: contentFrame + Frame { + contentItem: Item { + implicitWidth: 100 + implicitHeight: 30 + } + } + } + + function test_empty() { + var control = createTemporaryObject(frame, testCase) + verify(control) + + verify(control.contentItem) + compare(control.contentWidth, 0) + compare(control.contentHeight, 0) + compare(control.implicitContentWidth, 0) + compare(control.implicitContentHeight, 0) + } + + function test_oneChild() { + var control = createTemporaryObject(oneChildFrame, testCase) + verify(control) + + compare(control.contentWidth, 100) + compare(control.contentHeight, 30) + compare(control.implicitContentWidth, 100) + compare(control.implicitContentHeight, 30) + verify(control.implicitWidth > 100) + verify(control.implicitHeight > 30) + } + + function test_twoChildren() { + var control = createTemporaryObject(twoChildrenFrame, testCase) + verify(control) + + compare(control.contentWidth, 0) + compare(control.contentHeight, 0) + compare(control.implicitContentWidth, 0) + compare(control.implicitContentHeight, 0) + verify(control.implicitWidth > 0) + verify(control.implicitHeight > 0) + } + + function test_contentItem() { + var control = createTemporaryObject(contentFrame, testCase) + verify(control) + + compare(control.contentWidth, 100) + compare(control.contentHeight, 30) + compare(control.implicitContentWidth, 100) + compare(control.implicitContentHeight, 30) + verify(control.implicitWidth > 100) + verify(control.implicitHeight > 30) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_groupbox.qml b/tests/auto/quickcontrols2/controls/data/tst_groupbox.qml new file mode 100644 index 0000000000..ed7148978a --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_groupbox.qml @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "GroupBox" + + Component { + id: groupBox + GroupBox { } + } + + Component { + id: oneChildBox + GroupBox { + Item { + implicitWidth: 100 + implicitHeight: 30 + } + } + } + + Component { + id: twoChildrenBox + GroupBox { + Item { + implicitWidth: 100 + implicitHeight: 30 + } + Item { + implicitWidth: 200 + implicitHeight: 60 + } + } + } + + Component { + id: contentBox + GroupBox { + contentItem: Item { + implicitWidth: 100 + implicitHeight: 30 + } + } + } + + function test_empty() { + var control = createTemporaryObject(groupBox, testCase) + verify(control) + + verify(control.contentItem) + compare(control.contentWidth, 0) + compare(control.contentHeight, 0) + compare(control.implicitContentWidth, 0) + compare(control.implicitContentHeight, 0) + } + + function test_oneChild() { + var control = createTemporaryObject(oneChildBox, testCase) + verify(control) + + compare(control.contentWidth, 100) + compare(control.contentHeight, 30) + compare(control.implicitContentWidth, 100) + compare(control.implicitContentHeight, 30) + verify(control.implicitWidth > 100) + verify(control.implicitHeight > 30) + } + + function test_twoChildren() { + var control = createTemporaryObject(twoChildrenBox, testCase) + verify(control) + + compare(control.contentWidth, 0) + compare(control.contentHeight, 0) + compare(control.implicitContentWidth, 0) + compare(control.implicitContentHeight, 0) + verify(control.implicitWidth > 0) + verify(control.implicitHeight > 0) + } + + function test_contentItem() { + var control = createTemporaryObject(contentBox, testCase) + verify(control) + + compare(control.contentWidth, 100) + compare(control.contentHeight, 30) + compare(control.implicitContentWidth, 100) + compare(control.implicitContentHeight, 30) + verify(control.implicitWidth > 100) + verify(control.implicitHeight > 30) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_itemdelegate.qml b/tests/auto/quickcontrols2/controls/data/tst_itemdelegate.qml new file mode 100644 index 0000000000..7fa4e9e493 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_itemdelegate.qml @@ -0,0 +1,162 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 200 + height: 200 + visible: true + when: windowShown + name: "ItemDelegate" + + Component { + id: itemDelegate + ItemDelegate { } + } + + function test_baseline() { + var control = createTemporaryObject(itemDelegate, testCase) + verify(control) + compare(control.baselineOffset, control.contentItem.y + control.contentItem.baselineOffset) + } + + function test_highlighted() { + var control = createTemporaryObject(itemDelegate, testCase) + verify(control) + verify(!control.highlighted) + + control.highlighted = true + verify(control.highlighted) + } + + function test_spacing() { + var control = createTemporaryObject(itemDelegate, testCase, { text: "Some long, long, long text" }) + verify(control) + verify(control.contentItem.implicitWidth + control.leftPadding + control.rightPadding > control.background.implicitWidth) + + var textLabel = findChild(control.contentItem, "label") + verify(textLabel) + + // The implicitWidth of the IconLabel that all buttons use as their contentItem + // should be equal to the implicitWidth of the Text while no icon is set. + compare(control.contentItem.implicitWidth, textLabel.implicitWidth) + + // That means that spacing shouldn't affect it. + control.spacing += 100 + compare(control.contentItem.implicitWidth, textLabel.implicitWidth) + + // The implicitWidth of the ItemDelegate itself should, therefore, also never include spacing while no icon is set. + compare(control.implicitWidth, textLabel.implicitWidth + control.leftPadding + control.rightPadding) + } + + function test_display_data() { + return [ + { "tag": "IconOnly", display: ItemDelegate.IconOnly }, + { "tag": "TextOnly", display: ItemDelegate.TextOnly }, + { "tag": "TextUnderIcon", display: ItemDelegate.TextUnderIcon }, + { "tag": "TextBesideIcon", display: ItemDelegate.TextBesideIcon }, + { "tag": "IconOnly, mirrored", display: ItemDelegate.IconOnly, mirrored: true }, + { "tag": "TextOnly, mirrored", display: ItemDelegate.TextOnly, mirrored: true }, + { "tag": "TextUnderIcon, mirrored", display: ItemDelegate.TextUnderIcon, mirrored: true }, + { "tag": "TextBesideIcon, mirrored", display: ItemDelegate.TextBesideIcon, mirrored: true } + ] + } + + function test_display(data) { + var control = createTemporaryObject(itemDelegate, testCase, { + text: "ItemDelegate", + display: data.display, + width: 400, + "icon.source": "qrc:/qt-project.org/imports/QtQuick/Controls/Basic/images/check.png", + "LayoutMirroring.enabled": !!data.mirrored + }) + verify(control) + compare(control.icon.source, "qrc:/qt-project.org/imports/QtQuick/Controls/Basic/images/check.png") + + var iconImage = findChild(control.contentItem, "image") + var textLabel = findChild(control.contentItem, "label") + + switch (control.display) { + case ItemDelegate.IconOnly: + verify(iconImage) + verify(!textLabel) + compare(iconImage.x, (control.availableWidth - iconImage.width) / 2) + compare(iconImage.y, (control.availableHeight - iconImage.height) / 2) + break; + case ItemDelegate.TextOnly: + verify(!iconImage) + verify(textLabel) + compare(textLabel.x, control.mirrored ? control.availableWidth - textLabel.width : 0) + compare(textLabel.y, (control.availableHeight - textLabel.height) / 2) + break; + case ItemDelegate.TextUnderIcon: + verify(iconImage) + verify(textLabel) + compare(iconImage.x, (control.availableWidth - iconImage.width) / 2) + compare(textLabel.x, (control.availableWidth - textLabel.width) / 2) + verify(iconImage.y < textLabel.y) + break; + case ItemDelegate.TextBesideIcon: + verify(iconImage) + verify(textLabel) + if (control.mirrored) + verify(textLabel.x < iconImage.x) + else + verify(iconImage.x < textLabel.x) + compare(iconImage.y, (control.availableHeight - iconImage.height) / 2) + compare(textLabel.y, (control.availableHeight - textLabel.height) / 2) + break; + } + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_label.qml b/tests/auto/quickcontrols2/controls/data/tst_label.qml new file mode 100644 index 0000000000..d6efb1373a --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_label.qml @@ -0,0 +1,294 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "Label" + + Component { + id: label + Label { } + } + + Component { + id: backgroundLabel + Label { + background: Rectangle { } + } + } + + Component { + id: rectangle + Rectangle { } + } + + Component { + id: signalSpy + SignalSpy { } + } + + function test_creation() { + var control = createTemporaryObject(label, testCase) + verify(control) + } + + function test_font_explicit_attributes_data() { + return [ + {tag: "bold", value: true}, + {tag: "capitalization", value: Font.Capitalize}, + {tag: "family", value: "Courier"}, + {tag: "italic", value: true}, + {tag: "strikeout", value: true}, + {tag: "underline", value: true}, + {tag: "weight", value: Font.Black}, + {tag: "wordSpacing", value: 55} + ] + } + + function test_font_explicit_attributes(data) { + var control = createTemporaryObject(label, testCase) + verify(control) + + var child = label.createObject(control) + verify(child) + + var controlSpy = signalSpy.createObject(control, {target: control, signalName: "fontChanged"}) + verify(controlSpy.valid) + + var childSpy = signalSpy.createObject(child, {target: child, signalName: "fontChanged"}) + verify(childSpy.valid) + + var defaultValue = control.font[data.tag] + child.font[data.tag] = defaultValue + + compare(child.font[data.tag], defaultValue) + compare(childSpy.count, 0) + + control.font[data.tag] = data.value + + compare(control.font[data.tag], data.value) + compare(controlSpy.count, 1) + + compare(child.font[data.tag], defaultValue) + compare(childSpy.count, 0) + } + + function test_background() { + var control = createTemporaryObject(backgroundLabel, testCase, {text: "Label"}) + verify(control) + + compare(control.background.width, control.width) + compare(control.background.height, control.height) + + control.background = rectangle.createObject(control) + compare(control.background.width, control.width) + compare(control.background.height, control.height) + + // change implicit size (QTBUG-66455) + control.background.implicitWidth = 160 + control.background.implicitHeight = 120 + compare(control.background.width, control.width) + compare(control.background.height, control.height) + } + + function test_inset() { + var control = createTemporaryObject(label, testCase, {background: rectangle.createObject(control)}) + verify(control) + + var topInsetSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "topInsetChanged"}) + verify(topInsetSpy.valid) + + var leftInsetSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "leftInsetChanged"}) + verify(leftInsetSpy.valid) + + var rightInsetSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "rightInsetChanged"}) + verify(rightInsetSpy.valid) + + var bottomInsetSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "bottomInsetChanged"}) + verify(bottomInsetSpy.valid) + + var topInsetChanges = 0 + var leftInsetChanges = 0 + var rightInsetChanges = 0 + var bottomInsetChanges = 0 + + compare(control.topInset, 0) + compare(control.leftInset, 0) + compare(control.rightInset, 0) + compare(control.bottomInset, 0) + + control.width = 100 + control.height = 100 + compare(control.background.x, 0) + compare(control.background.y, 0) + compare(control.background.width, 100) + compare(control.background.height, 100) + + control.topInset = 10 + compare(control.topInset, 10) + compare(control.leftInset, 0) + compare(control.rightInset, 0) + compare(control.bottomInset, 0) + compare(topInsetSpy.count, ++topInsetChanges) + compare(leftInsetSpy.count, leftInsetChanges) + compare(rightInsetSpy.count, rightInsetChanges) + compare(bottomInsetSpy.count, bottomInsetChanges) + compare(control.background.x, 0) + compare(control.background.y, 10) + compare(control.background.width, 100) + compare(control.background.height, 90) + + control.leftInset = 20 + compare(control.topInset, 10) + compare(control.leftInset, 20) + compare(control.rightInset, 0) + compare(control.bottomInset, 0) + compare(topInsetSpy.count, topInsetChanges) + compare(leftInsetSpy.count, ++leftInsetChanges) + compare(rightInsetSpy.count, rightInsetChanges) + compare(bottomInsetSpy.count, bottomInsetChanges) + compare(control.background.x, 20) + compare(control.background.y, 10) + compare(control.background.width, 80) + compare(control.background.height, 90) + + control.rightInset = 30 + compare(control.topInset, 10) + compare(control.leftInset, 20) + compare(control.rightInset, 30) + compare(control.bottomInset, 0) + compare(topInsetSpy.count, topInsetChanges) + compare(leftInsetSpy.count, leftInsetChanges) + compare(rightInsetSpy.count, ++rightInsetChanges) + compare(bottomInsetSpy.count, bottomInsetChanges) + compare(control.background.x, 20) + compare(control.background.y, 10) + compare(control.background.width, 50) + compare(control.background.height, 90) + + control.bottomInset = 40 + compare(control.topInset, 10) + compare(control.leftInset, 20) + compare(control.rightInset, 30) + compare(control.bottomInset, 40) + compare(topInsetSpy.count, topInsetChanges) + compare(leftInsetSpy.count, leftInsetChanges) + compare(rightInsetSpy.count, rightInsetChanges) + compare(bottomInsetSpy.count, ++bottomInsetChanges) + compare(control.background.x, 20) + compare(control.background.y, 10) + compare(control.background.width, 50) + compare(control.background.height, 50) + + control.topInset = undefined + compare(control.topInset, 0) + compare(control.leftInset, 20) + compare(control.rightInset, 30) + compare(control.bottomInset, 40) + compare(topInsetSpy.count, ++topInsetChanges) + compare(leftInsetSpy.count, leftInsetChanges) + compare(rightInsetSpy.count, rightInsetChanges) + compare(bottomInsetSpy.count, bottomInsetChanges) + compare(control.background.x, 20) + compare(control.background.y, 0) + compare(control.background.width, 50) + compare(control.background.height, 60) + + control.leftInset = undefined + compare(control.topInset, 0) + compare(control.leftInset, 0) + compare(control.rightInset, 30) + compare(control.bottomInset, 40) + compare(topInsetSpy.count, topInsetChanges) + compare(leftInsetSpy.count, ++leftInsetChanges) + compare(rightInsetSpy.count, rightInsetChanges) + compare(bottomInsetSpy.count, bottomInsetChanges) + compare(control.background.x, 0) + compare(control.background.y, 0) + compare(control.background.width, 70) + compare(control.background.height, 60) + + control.rightInset = undefined + compare(control.topInset, 0) + compare(control.leftInset, 0) + compare(control.rightInset, 0) + compare(control.bottomInset, 40) + compare(topInsetSpy.count, topInsetChanges) + compare(leftInsetSpy.count, leftInsetChanges) + compare(rightInsetSpy.count, ++rightInsetChanges) + compare(bottomInsetSpy.count, bottomInsetChanges) + compare(control.background.x, 0) + compare(control.background.y, 0) + compare(control.background.width, 100) + compare(control.background.height, 60) + + control.bottomInset = undefined + compare(control.topInset, 0) + compare(control.leftInset, 0) + compare(control.rightInset, 0) + compare(control.bottomInset, 0) + compare(topInsetSpy.count, topInsetChanges) + compare(leftInsetSpy.count, leftInsetChanges) + compare(rightInsetSpy.count, rightInsetChanges) + compare(bottomInsetSpy.count, ++bottomInsetChanges) + compare(control.background.x, 0) + compare(control.background.y, 0) + compare(control.background.width, 100) + compare(control.background.height, 100) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_menuitem.qml b/tests/auto/quickcontrols2/controls/data/tst_menuitem.qml new file mode 100644 index 0000000000..713c030efd --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_menuitem.qml @@ -0,0 +1,193 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 200 + height: 200 + visible: true + when: windowShown + name: "MenuItem" + + Component { + id: menuItem + MenuItem { } + } + + Component { + id: menu + Menu { } + } + + function test_baseline() { + var control = createTemporaryObject(menuItem, testCase) + verify(control) + compare(control.baselineOffset, control.contentItem.y + control.contentItem.baselineOffset) + } + + function test_checkable() { + var control = createTemporaryObject(menuItem, testCase) + verify(control) + verify(control.hasOwnProperty("checkable")) + verify(!control.checkable) + + mouseClick(control) + verify(!control.checked) + + control.checkable = true + mouseClick(control) + verify(control.checked) + + mouseClick(control) + verify(!control.checked) + } + + function test_highlighted() { + var control = createTemporaryObject(menuItem, testCase) + verify(control) + verify(!control.highlighted) + + control.highlighted = true + verify(control.highlighted) + } + + function test_display_data() { + return [ + { "tag": "IconOnly", display: MenuItem.IconOnly }, + { "tag": "TextOnly", display: MenuItem.TextOnly }, + { "tag": "TextUnderIcon", display: MenuItem.TextUnderIcon }, + { "tag": "TextBesideIcon", display: MenuItem.TextBesideIcon }, + { "tag": "IconOnly, mirrored", display: MenuItem.IconOnly, mirrored: true }, + { "tag": "TextOnly, mirrored", display: MenuItem.TextOnly, mirrored: true }, + { "tag": "TextUnderIcon, mirrored", display: MenuItem.TextUnderIcon, mirrored: true }, + { "tag": "TextBesideIcon, mirrored", display: MenuItem.TextBesideIcon, mirrored: true } + ] + } + + function test_display(data) { + var control = createTemporaryObject(menuItem, testCase, { + text: "MenuItem", + display: data.display, + "icon.source": "qrc:/qt-project.org/imports/QtQuick/Controls/Basic/images/check.png", + "LayoutMirroring.enabled": !!data.mirrored + }) + verify(control) + compare(control.icon.source, "qrc:/qt-project.org/imports/QtQuick/Controls/Basic/images/check.png") + + var padding = data.mirrored ? control.contentItem.rightPadding : control.contentItem.leftPadding + var iconImage = findChild(control.contentItem, "image") + var textLabel = findChild(control.contentItem, "label") + + switch (control.display) { + case MenuItem.IconOnly: + verify(iconImage) + verify(!textLabel) + compare(iconImage.x, control.mirrored ? control.availableWidth - iconImage.width - padding : padding) + compare(iconImage.y, (control.availableHeight - iconImage.height) / 2) + break; + case MenuItem.TextOnly: + verify(!iconImage) + verify(textLabel) + compare(textLabel.x, control.mirrored ? control.availableWidth - textLabel.width - padding : padding) + compare(textLabel.y, (control.availableHeight - textLabel.height) / 2) + break; + case MenuItem.TextUnderIcon: + verify(iconImage) + verify(textLabel) + compare(iconImage.x, control.mirrored ? control.availableWidth - iconImage.width - (textLabel.width - iconImage.width) / 2 - padding : (textLabel.width - iconImage.width) / 2 + padding) + compare(textLabel.x, control.mirrored ? control.availableWidth - textLabel.width - padding : padding) + verify(iconImage.y < textLabel.y) + break; + case MenuItem.TextBesideIcon: + verify(iconImage) + verify(textLabel) + if (control.mirrored) + verify(textLabel.x < iconImage.x) + else + verify(iconImage.x < textLabel.x) + compare(iconImage.y, (control.availableHeight - iconImage.height) / 2) + compare(textLabel.y, (control.availableHeight - textLabel.height) / 2) + break; + } + } + + function test_menu() { + var control = createTemporaryObject(menu, testCase) + verify(control) + + var item1 = createTemporaryObject(menuItem, testCase) + verify(item1) + compare(item1.menu, null) + + var item2 = createTemporaryObject(menuItem, testCase) + verify(item2) + compare(item2.menu, null) + + control.addItem(item1) + compare(item1.menu, control) + compare(item2.menu, null) + + control.insertItem(1, item2) + compare(item1.menu, control) + compare(item2.menu, control) + + control.removeItem(control.itemAt(1)) + compare(item1.menu, control) + compare(item2.menu, null) + + control.removeItem(control.itemAt(0)) + compare(item1.menu, null) + compare(item2.menu, null) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_page.qml b/tests/auto/quickcontrols2/controls/data/tst_page.qml new file mode 100644 index 0000000000..ae78a12563 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_page.qml @@ -0,0 +1,315 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "Page" + + Component { + id: page + Page { } + } + + Component { + id: oneChildPage + Page { + Item { + implicitWidth: 100 + implicitHeight: 30 + } + } + } + + Component { + id: twoChildrenPage + Page { + Item { + implicitWidth: 100 + implicitHeight: 30 + } + Item { + implicitWidth: 200 + implicitHeight: 60 + } + } + } + + Component { + id: contentPage + Page { + contentItem: Item { + implicitWidth: 100 + implicitHeight: 30 + } + } + } + + Component { + id: headerFooterPage + Page { + header: ToolBar { } + footer: ToolBar { } + contentItem: Item { + implicitWidth: 100 + implicitHeight: 30 + } + } + } + + Component { + id: toolBar + ToolBar { } + } + + function test_defaults() { + var control = createTemporaryObject(page, testCase) + verify(control) + + verify(control.contentItem) + compare(control.header, null) + compare(control.footer, null) + } + + function test_empty() { + var control = createTemporaryObject(page, testCase) + verify(control) + + verify(control.contentItem) + compare(control.contentWidth, 0) + compare(control.contentHeight, 0) + compare(control.implicitContentWidth, 0) + compare(control.implicitContentHeight, 0) + } + + function test_oneChild() { + var control = createTemporaryObject(oneChildPage, testCase) + verify(control) + + compare(control.contentWidth, 100) + compare(control.contentHeight, 30) + compare(control.implicitContentWidth, 100) + compare(control.implicitContentHeight, 30) + compare(control.implicitWidth, 100 + control.leftPadding + control.rightPadding) + compare(control.implicitHeight, 30 + control.topPadding + control.bottomPadding) + } + + function test_twoChildren() { + var control = createTemporaryObject(twoChildrenPage, testCase) + verify(control) + + compare(control.contentWidth, 0) + compare(control.contentHeight, 0) + compare(control.implicitContentWidth, 0) + compare(control.implicitContentHeight, 0) + compare(control.implicitWidth, Math.max(control.leftPadding + control.rightPadding, + control.background ? control.background.implicitWidth : 0)) + compare(control.implicitHeight, Math.max(control.topPadding + control.bottomPadding, + control.background ? control.background.implicitHeight : 0)) + } + + function test_contentItem() { + var control = createTemporaryObject(contentPage, testCase) + verify(control) + + compare(control.contentWidth, 100) + compare(control.contentHeight, 30) + compare(control.implicitContentWidth, 100) + compare(control.implicitContentHeight, 30) + compare(control.implicitWidth, 100 + control.leftPadding + control.rightPadding) + compare(control.implicitHeight, 30 + control.topPadding + control.bottomPadding) + } + + function test_layout() { + var control = createTemporaryObject(page, testCase, {width: 100, height: 100}) + verify(control) + + compare(control.width, 100) + compare(control.height, 100) + compare(control.contentItem.width, control.width) + compare(control.contentItem.height, control.height) + + control.header = toolBar.createObject(control) + compare(control.header.width, control.width) + verify(control.header.height > 0) + compare(control.contentItem.width, control.width) + compare(control.contentItem.height, control.height - control.header.height) + + control.footer = toolBar.createObject(control) + compare(control.footer.width, control.width) + verify(control.footer.height > 0) + compare(control.contentItem.width, control.width) + compare(control.contentItem.height, control.height - control.header.height - control.footer.height) + + control.topPadding = 9 + control.leftPadding = 2 + control.rightPadding = 6 + control.bottomPadding = 7 + + compare(control.header.x, 0) + compare(control.header.y, 0) + compare(control.header.width, control.width) + verify(control.header.height > 0) + + compare(control.footer.x, 0) + compare(control.footer.y, control.height - control.footer.height) + compare(control.footer.width, control.width) + verify(control.footer.height > 0) + + compare(control.contentItem.x, control.leftPadding) + compare(control.contentItem.y, control.topPadding + control.header.height) + compare(control.contentItem.width, control.availableWidth) + compare(control.contentItem.height, control.availableHeight - control.header.height - control.footer.height) + + control.header.visible = false + compare(control.contentItem.x, control.leftPadding) + compare(control.contentItem.y, control.topPadding) + compare(control.contentItem.width, control.availableWidth) + compare(control.contentItem.height, control.availableHeight - control.footer.height) + + control.footer.visible = false + compare(control.contentItem.x, control.leftPadding) + compare(control.contentItem.y, control.topPadding) + compare(control.contentItem.width, control.availableWidth) + compare(control.contentItem.height, control.availableHeight) + + control.contentItem.implicitWidth = 50 + control.contentItem.implicitHeight = 60 + compare(control.implicitWidth, control.contentItem.implicitWidth + control.leftPadding + control.rightPadding) + compare(control.implicitHeight, control.contentItem.implicitHeight + control.topPadding + control.bottomPadding) + + control.header.visible = true + compare(control.implicitHeight, control.contentItem.implicitHeight + control.topPadding + control.bottomPadding + + control.header.implicitHeight + control.spacing) + + control.footer.visible = true + compare(control.implicitHeight, control.contentItem.implicitHeight + control.topPadding + control.bottomPadding + + control.header.implicitHeight + control.footer.implicitHeight + 2 * control.spacing) + + control.header.implicitWidth = 150 + compare(control.implicitWidth, control.header.implicitWidth) + + control.footer.implicitWidth = 160 + compare(control.implicitWidth, control.footer.implicitWidth) + + control.contentItem.implicitWidth = 170 + compare(control.implicitWidth, control.contentItem.implicitWidth + control.leftPadding + control.rightPadding) + } + + function test_spacing_data() { + return [ + { tag: "content", header: false, content: true, footer: false }, + { tag: "header,content", header: true, content: true, footer: false }, + { tag: "content,footer", header: false, content: true, footer: true }, + { tag: "header,content,footer", header: true, content: true, footer: true }, + { tag: "header,footer", header: true, content: false, footer: true }, + { tag: "header", header: true, content: false, footer: false }, + { tag: "footer", header: false, content: false, footer: true }, + ] + } + + function test_spacing(data) { + var control = createTemporaryObject(page, testCase, {spacing: 20, width: 100, height: 100}) + verify(control) + + control.contentItem.visible = data.content + control.header = toolBar.createObject(control.contentItem, {visible: data.header}) + control.footer = toolBar.createObject(control.contentItem, {visible: data.footer}) + + compare(control.header.x, 0) + compare(control.header.y, 0) + compare(control.header.width, control.width) + verify(control.header.height > 0) + + compare(control.footer.x, 0) + compare(control.footer.y, control.height - control.footer.height) + compare(control.footer.width, control.width) + verify(control.footer.height > 0) + + compare(control.contentItem.x, control.leftPadding) + compare(control.contentItem.y, control.topPadding + (data.header ? control.header.height + control.spacing : 0)) + compare(control.contentItem.width, control.availableWidth) + compare(control.contentItem.height, control.availableHeight + - (data.header ? control.header.height + control.spacing : 0) + - (data.footer ? control.footer.height + control.spacing : 0)) + } + + function test_headerFooter() { + var control = createTemporaryObject(headerFooterPage, testCase, {width: 100, height: 100}) + verify(control) + + compare(control.width, 100) + compare(control.height, 100) + + verify(control.header) + compare(control.header.x, 0) + compare(control.header.y, 0) + compare(control.header.width, control.width) + verify(control.header.height > 0) + + verify(control.footer) + compare(control.footer.x, 0) + compare(control.footer.y, control.height - control.footer.height) + compare(control.footer.width, control.width) + verify(control.footer.height > 0) + + compare(control.contentItem.x, 0) + compare(control.contentItem.y, control.header.height) + compare(control.contentItem.width, control.width) + compare(control.contentItem.height, control.height - control.header.height - control.footer.height) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_pageindicator.qml b/tests/auto/quickcontrols2/controls/data/tst_pageindicator.qml new file mode 100644 index 0000000000..765c52a77a --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_pageindicator.qml @@ -0,0 +1,183 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "PageIndicator" + + Component { + id: pageIndicator + PageIndicator { } + } + + Component { + id: mouseArea + MouseArea { } + } + + function test_count() { + var control = createTemporaryObject(pageIndicator, testCase) + verify(control) + + compare(control.count, 0) + control.count = 3 + compare(control.count, 3) + } + + function test_currentIndex() { + var control = createTemporaryObject(pageIndicator, testCase) + verify(control) + + compare(control.currentIndex, 0) + control.currentIndex = 5 + compare(control.currentIndex, 5) + } + + function test_interactive_data() { + return [ + { tag: "mouse", touch: false }, + { tag: "touch", touch: true } + ] + } + + function test_interactive(data) { + var control = createTemporaryObject(pageIndicator, testCase, {count: 5, spacing: 10, topPadding: 10, leftPadding: 10, rightPadding: 10, bottomPadding: 10}) + verify(control) + + verify(!control.interactive) + compare(control.currentIndex, 0) + + var touch = touchEvent(control) + + if (data.touch) + touch.press(0, control).commit().release(0, control).commit() + else + mouseClick(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.currentIndex, 0) + + control.interactive = true + verify(control.interactive) + + if (data.touch) + touch.press(0, control).commit().release(0, control).commit() + else + mouseClick(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.currentIndex, 2) + + // test also clicking outside delegates => the nearest should be selected + for (var i = 0; i < control.count; ++i) { + var child = control.contentItem.children[i] + + var points = [ + Qt.point(child.width / 2, -2), // top + Qt.point(-2, child.height / 2), // left + Qt.point(child.width + 2, child.height / 2), // right + Qt.point(child.width / 2, child.height + 2), // bottom + + Qt.point(-2, -2), // top-left + Qt.point(child.width + 2, -2), // top-right + Qt.point(-2, child.height + 2), // bottom-left + Qt.point(child.width + 2, child.height + 2), // bottom-right + ] + + for (var j = 0; j < points.length; ++j) { + control.currentIndex = -1 + compare(control.currentIndex, -1) + + var point = points[j] + var pos = control.mapFromItem(child, x, y) + if (data.touch) + touch.press(0, control, pos.x, pos.y).commit().release(0, control, pos.x, pos.y).commit() + else + mouseClick(control, pos.x, pos.y, Qt.LeftButton) + compare(control.currentIndex, i) + } + } + } + + function test_mouseArea_data() { + return [ + { tag: "interactive", interactive: true }, + { tag: "non-interactive", interactive: false } + ] + } + + // QTBUG-61785 + function test_mouseArea(data) { + var ma = createTemporaryObject(mouseArea, testCase, {width: testCase.width, height: testCase.height}) + verify(ma) + + var control = pageIndicator.createObject(ma, {count: 5, interactive: data.interactive, width: testCase.width, height: testCase.height}) + verify(control) + + compare(control.interactive, data.interactive) + + mousePress(control) + compare(ma.pressed, !data.interactive) + + mouseRelease(control) + verify(!ma.pressed) + + var touch = touchEvent(control) + touch.press(0, control).commit() + compare(ma.pressed, !data.interactive) + + touch.release(0, control).commit() + verify(!ma.pressed) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_pane.qml b/tests/auto/quickcontrols2/controls/data/tst_pane.qml new file mode 100644 index 0000000000..d44d7424a6 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_pane.qml @@ -0,0 +1,195 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "Pane" + + Component { + id: pane + Pane { } + } + + Component { + id: oneChildPane + Pane { + Item { + implicitWidth: 100 + implicitHeight: 30 + } + } + } + + Component { + id: twoChildrenPane + Pane { + Item { + implicitWidth: 100 + implicitHeight: 30 + } + Item { + implicitWidth: 200 + implicitHeight: 60 + } + } + } + + Component { + id: contentPane + Pane { + contentItem: Item { + implicitWidth: 100 + implicitHeight: 30 + } + } + } + + Component { + id: pressPane + MouseArea { + width: 200 + height: 200 + property int pressCount + onPressed: ++pressCount + Pane { + anchors.fill: parent + } + } + } + + function test_empty() { + var control = createTemporaryObject(pane, testCase) + verify(control) + + verify(control.contentItem) + compare(control.contentWidth, 0) + compare(control.contentHeight, 0) + compare(control.implicitContentWidth, 0) + compare(control.implicitContentHeight, 0) + } + + function test_oneChild() { + var control = createTemporaryObject(oneChildPane, testCase) + verify(control) + + compare(control.contentWidth, 100) + compare(control.contentHeight, 30) + compare(control.implicitContentWidth, 100) + compare(control.implicitContentHeight, 30) + verify(control.implicitWidth > 100) + verify(control.implicitHeight > 30) + + compare(control.contentChildren.length, 1) + control.contentChildren[0].implicitWidth = 200 + control.contentChildren[0].implicitHeight = 40 + + compare(control.contentWidth, 200) + compare(control.contentHeight, 40) + compare(control.implicitContentWidth, 200) + compare(control.implicitContentHeight, 40) + verify(control.implicitWidth > 200) + verify(control.implicitHeight > 40) + } + + function test_twoChildren() { + var control = createTemporaryObject(twoChildrenPane, testCase) + verify(control) + + compare(control.contentWidth, 0) + compare(control.contentHeight, 0) + compare(control.implicitContentWidth, 0) + compare(control.implicitContentHeight, 0) + verify(control.implicitWidth > 0) + verify(control.implicitHeight > 0) + } + + function test_contentItem() { + var control = createTemporaryObject(contentPane, testCase) + verify(control) + + compare(control.contentWidth, 100) + compare(control.contentHeight, 30) + compare(control.implicitContentWidth, 100) + compare(control.implicitContentHeight, 30) + verify(control.implicitWidth > 100) + verify(control.implicitHeight > 30) + } + + function test_implicitContentItem() { + var control = createTemporaryObject(pane, testCase, {width: 100, height: 100}) + verify(control) + + compare(control.width, 100) + compare(control.height, 100) + compare(control.contentItem.width, control.availableWidth) + compare(control.contentItem.height, control.availableHeight) + } + + function test_press() { + var control = createTemporaryObject(pressPane, testCase) + verify(control) + + compare(control.pressCount, 0) + mouseClick(control) + compare(control.pressCount, 0) + + control.children[0].enabled = false + mouseClick(control) + compare(control.pressCount, 1) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_popup.qml b/tests/auto/quickcontrols2/controls/data/tst_popup.qml new file mode 100644 index 0000000000..145f555218 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_popup.qml @@ -0,0 +1,1393 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls +import QtQuick.Templates as T + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "Popup" + + ApplicationWindow { + id: applicationWindow + width: 480 + height: 360 + } + + Component { + id: popupTemplate + T.Popup { } + } + + Component { + id: popupControl + Popup { } + } + + Component { + id: rect + Rectangle { } + } + + Component { + id: signalSpy + SignalSpy { } + } + + function test_padding() { + var control = createTemporaryObject(popupTemplate, testCase) + verify(control) + + var paddingSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "paddingChanged"}) + verify(paddingSpy.valid) + + var topPaddingSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "topPaddingChanged"}) + verify(topPaddingSpy.valid) + + var leftPaddingSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "leftPaddingChanged"}) + verify(leftPaddingSpy.valid) + + var rightPaddingSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "rightPaddingChanged"}) + verify(rightPaddingSpy.valid) + + var bottomPaddingSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "bottomPaddingChanged"}) + verify(bottomPaddingSpy.valid) + + var paddingChanges = 0 + var topPaddingChanges = 0 + var leftPaddingChanges = 0 + var rightPaddingChanges = 0 + var bottomPaddingChanges = 0 + + compare(control.padding, 0) + compare(control.topPadding, 0) + compare(control.leftPadding, 0) + compare(control.rightPadding, 0) + compare(control.bottomPadding, 0) + compare(control.availableWidth, 0) + compare(control.availableHeight, 0) + + control.width = 100 + control.height = 100 + + control.padding = 10 + compare(control.padding, 10) + compare(control.topPadding, 10) + compare(control.leftPadding, 10) + compare(control.rightPadding, 10) + compare(control.bottomPadding, 10) + compare(paddingSpy.count, ++paddingChanges) + compare(topPaddingSpy.count, ++topPaddingChanges) + compare(leftPaddingSpy.count, ++leftPaddingChanges) + compare(rightPaddingSpy.count, ++rightPaddingChanges) + compare(bottomPaddingSpy.count, ++bottomPaddingChanges) + + control.topPadding = 20 + compare(control.padding, 10) + compare(control.topPadding, 20) + compare(control.leftPadding, 10) + compare(control.rightPadding, 10) + compare(control.bottomPadding, 10) + compare(paddingSpy.count, paddingChanges) + compare(topPaddingSpy.count, ++topPaddingChanges) + compare(leftPaddingSpy.count, leftPaddingChanges) + compare(rightPaddingSpy.count, rightPaddingChanges) + compare(bottomPaddingSpy.count, bottomPaddingChanges) + + control.leftPadding = 30 + compare(control.padding, 10) + compare(control.topPadding, 20) + compare(control.leftPadding, 30) + compare(control.rightPadding, 10) + compare(control.bottomPadding, 10) + compare(paddingSpy.count, paddingChanges) + compare(topPaddingSpy.count, topPaddingChanges) + compare(leftPaddingSpy.count, ++leftPaddingChanges) + compare(rightPaddingSpy.count, rightPaddingChanges) + compare(bottomPaddingSpy.count, bottomPaddingChanges) + + control.rightPadding = 40 + compare(control.padding, 10) + compare(control.topPadding, 20) + compare(control.leftPadding, 30) + compare(control.rightPadding, 40) + compare(control.bottomPadding, 10) + compare(paddingSpy.count, paddingChanges) + compare(topPaddingSpy.count, topPaddingChanges) + compare(leftPaddingSpy.count, leftPaddingChanges) + compare(rightPaddingSpy.count, ++rightPaddingChanges) + compare(bottomPaddingSpy.count, bottomPaddingChanges) + + control.bottomPadding = 50 + compare(control.padding, 10) + compare(control.topPadding, 20) + compare(control.leftPadding, 30) + compare(control.rightPadding, 40) + compare(control.bottomPadding, 50) + compare(paddingSpy.count, paddingChanges) + compare(topPaddingSpy.count, topPaddingChanges) + compare(leftPaddingSpy.count, leftPaddingChanges) + compare(rightPaddingSpy.count, rightPaddingChanges) + compare(bottomPaddingSpy.count, ++bottomPaddingChanges) + + control.padding = 60 + compare(control.padding, 60) + compare(control.topPadding, 20) + compare(control.leftPadding, 30) + compare(control.rightPadding, 40) + compare(control.bottomPadding, 50) + compare(paddingSpy.count, ++paddingChanges) + compare(topPaddingSpy.count, topPaddingChanges) + compare(leftPaddingSpy.count, leftPaddingChanges) + compare(rightPaddingSpy.count, rightPaddingChanges) + compare(bottomPaddingSpy.count, bottomPaddingChanges) + } + + function test_availableSize() { + var control = createTemporaryObject(popupTemplate, testCase) + verify(control) + + var availableWidthSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "availableWidthChanged"}) + verify(availableWidthSpy.valid) + + var availableHeightSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "availableHeightChanged"}) + verify(availableHeightSpy.valid) + + var availableWidthChanges = 0 + var availableHeightChanges = 0 + + control.width = 100 + compare(control.availableWidth, 100) + compare(availableWidthSpy.count, ++availableWidthChanges) + compare(availableHeightSpy.count, availableHeightChanges) + + control.height = 100 + compare(control.availableHeight, 100) + compare(availableWidthSpy.count, availableWidthChanges) + compare(availableHeightSpy.count, ++availableHeightChanges) + + control.padding = 10 + compare(control.availableWidth, 80) + compare(control.availableHeight, 80) + compare(availableWidthSpy.count, ++availableWidthChanges) + compare(availableHeightSpy.count, ++availableHeightChanges) + + control.topPadding = 20 + compare(control.availableWidth, 80) + compare(control.availableHeight, 70) + compare(availableWidthSpy.count, availableWidthChanges) + compare(availableHeightSpy.count, ++availableHeightChanges) + + control.leftPadding = 30 + compare(control.availableWidth, 60) + compare(control.availableHeight, 70) + compare(availableWidthSpy.count, ++availableWidthChanges) + compare(availableHeightSpy.count, availableHeightChanges) + + control.rightPadding = 40 + compare(control.availableWidth, 30) + compare(control.availableHeight, 70) + compare(availableWidthSpy.count, ++availableWidthChanges) + compare(availableHeightSpy.count, availableHeightChanges) + + control.bottomPadding = 50 + compare(control.availableWidth, 30) + compare(control.availableHeight, 30) + compare(availableWidthSpy.count, availableWidthChanges) + compare(availableHeightSpy.count, ++availableHeightChanges) + + control.padding = 60 + compare(control.availableWidth, 30) + compare(control.availableHeight, 30) + compare(availableWidthSpy.count, availableWidthChanges) + compare(availableHeightSpy.count, availableHeightChanges) + + control.width = 0 + compare(control.availableWidth, 0) + compare(availableWidthSpy.count, ++availableWidthChanges) + compare(availableHeightSpy.count, availableHeightChanges) + + control.height = 0 + compare(control.availableHeight, 0) + compare(availableWidthSpy.count, availableWidthChanges) + compare(availableHeightSpy.count, ++availableHeightChanges) + } + + function test_position() { + var control = createTemporaryObject(popupControl, testCase, {visible: true, leftMargin: 10, topMargin: 20, width: 100, height: 100}) + verify(control) + verify(control.visible) + + var xSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "xChanged"}) + verify(xSpy.valid) + + var ySpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "yChanged"}) + verify(ySpy.valid) + + // moving outside margins does not trigger change notifiers + control.x = -100 + compare(control.x, 10) + compare(control.y, 20) + compare(xSpy.count, 0) + compare(ySpy.count, 0) + + control.y = -200 + compare(control.x, 10) + compare(control.y, 20) + compare(xSpy.count, 0) + compare(ySpy.count, 0) + + // moving within margins triggers change notifiers + control.x = 30 + compare(control.x, 30) + compare(control.y, 20) + compare(xSpy.count, 1) + compare(ySpy.count, 0) + + control.y = 40 + compare(control.x, 30) + compare(control.y, 40) + compare(xSpy.count, 1) + compare(ySpy.count, 1) + + // re-parent and reset the position + control.parent = createTemporaryObject(rect, testCase, {color: "red", width: 100, height: 100}) + control.x = 0 + control.y = 0 + compare(xSpy.count, 2) + compare(ySpy.count, 2) + + // moving parent outside margins triggers change notifiers + control.parent.x = -50 + compare(control.x, 50 + control.leftMargin) + compare(xSpy.count, 3) + compare(ySpy.count, 2) + + control.parent.y = -60 + compare(control.y, 60 + control.topMargin) + compare(xSpy.count, 3) + compare(ySpy.count, 3) + } + + function test_resetSize() { + var control = createTemporaryObject(popupControl, testCase, {visible: true, margins: 0}) + verify(control) + + control.scale = 1.0 + control.width = control.implicitWidth = testCase.width + 10 + control.height = control.implicitHeight = testCase.height + 10 + + compare(control.width, testCase.width + 10) + compare(control.height, testCase.height + 10) + + control.width = undefined + control.height = undefined + compare(control.width, testCase.width) + compare(control.height, testCase.height) + } + + function test_negativeMargins() { + var control = createTemporaryObject(popupControl, testCase, {implicitWidth: testCase.width, implicitHeight: testCase.height}) + verify(control) + + control.open() + verify(control.visible) + + compare(control.x, 0) + compare(control.y, 0) + + compare(control.margins, -1) + compare(control.topMargin, -1) + compare(control.leftMargin, -1) + compare(control.rightMargin, -1) + compare(control.bottomMargin, -1) + + control.x = -10 + control.y = -10 + compare(control.x, 0) + compare(control.y, 0) + } + + function test_margins() { + var control = createTemporaryObject(popupTemplate, testCase, {width: 100, height: 100}) + verify(control) + + control.open() + verify(control.visible) + + control.margins = 10 + compare(control.margins, 10) + compare(control.topMargin, 10) + compare(control.leftMargin, 10) + compare(control.rightMargin, 10) + compare(control.bottomMargin, 10) + compare(control.contentItem.parent.x, 10) + compare(control.contentItem.parent.y, 10) + + control.topMargin = 20 + compare(control.margins, 10) + compare(control.topMargin, 20) + compare(control.leftMargin, 10) + compare(control.rightMargin, 10) + compare(control.bottomMargin, 10) + compare(control.contentItem.parent.x, 10) + compare(control.contentItem.parent.y, 20) + + control.leftMargin = 20 + compare(control.margins, 10) + compare(control.topMargin, 20) + compare(control.leftMargin, 20) + compare(control.rightMargin, 10) + compare(control.bottomMargin, 10) + compare(control.contentItem.parent.x, 20) + compare(control.contentItem.parent.y, 20) + + control.x = testCase.width + control.y = testCase.height + compare(control.contentItem.parent.x, testCase.width - control.width - 10) + compare(control.contentItem.parent.y, testCase.height - control.height - 10) + + control.rightMargin = 20 + compare(control.margins, 10) + compare(control.topMargin, 20) + compare(control.leftMargin, 20) + compare(control.rightMargin, 20) + compare(control.bottomMargin, 10) + compare(control.contentItem.parent.x, testCase.width - control.width - 20) + compare(control.contentItem.parent.y, testCase.height - control.height - 10) + + control.bottomMargin = 20 + compare(control.margins, 10) + compare(control.topMargin, 20) + compare(control.leftMargin, 20) + compare(control.rightMargin, 20) + compare(control.bottomMargin, 20) + compare(control.contentItem.parent.x, testCase.width - control.width - 20) + compare(control.contentItem.parent.y, testCase.height - control.height - 20) + + control.margins = undefined + compare(control.margins, -1) + + control.bottomMargin = undefined + compare(control.bottomMargin, -1) + compare(control.contentItem.parent.x, testCase.width - control.width - 20) + compare(control.contentItem.parent.y, testCase.height) + + control.rightMargin = undefined + compare(control.rightMargin, -1) + compare(control.contentItem.parent.x, testCase.width) + compare(control.contentItem.parent.y, testCase.height) + + control.x = -testCase.width + control.y = -testCase.height + compare(control.contentItem.parent.x, 20) + compare(control.contentItem.parent.y, 20) + + control.topMargin = undefined + compare(control.topMargin, -1) + compare(control.contentItem.parent.x, 20) + compare(control.contentItem.parent.y, -testCase.height) + + control.leftMargin = undefined + compare(control.leftMargin, -1) + compare(control.contentItem.parent.x, -testCase.width) + compare(control.contentItem.parent.y, -testCase.height) + } + + function test_background() { + var control = createTemporaryObject(popupTemplate, testCase) + verify(control) + + control.background = rect.createObject(testCase) + + // background has no x or width set, so its width follows control's width + control.width = 320 + compare(control.background.width, control.width) + + // background has no y or height set, so its height follows control's height + compare(control.background.height, control.height) + control.height = 240 + + // has width => width does not follow + control.background.width /= 2 + control.width += 20 + verify(control.background.width !== control.width) + + // reset width => width follows again + control.background.width = undefined + control.width += 20 + compare(control.background.width, control.width) + + // has x => width does not follow + control.background.x = 10 + control.width += 20 + verify(control.background.width !== control.width) + + // has height => height does not follow + control.background.height /= 2 + control.height -= 20 + verify(control.background.height !== control.height) + + // reset height => height follows again + control.background.height = undefined + control.height -= 20 + compare(control.background.height, control.height) + + // has y => height does not follow + control.background.y = 10 + control.height -= 20 + verify(control.background.height !== control.height) + } + + function getChild(control, objname, idx) { + var index = idx + for (var i = index+1; i < control.children.length; i++) + { + if (control.children[i].objectName === objname) { + index = i + break + } + } + return index + } + + Component { + id: component + ApplicationWindow { + id: _window + width: 400 + height: 400 + visible: true + font.pixelSize: 40 + property alias pane: _pane + property alias popup: _popup + property SignalSpy fontspy: SignalSpy { target: _window; signalName: "fontChanged" } + Pane { + id: _pane + property alias button: _button + font.pixelSize: 30 + property SignalSpy fontspy: SignalSpy { target: _pane; signalName: "fontChanged" } + Column { + Button { + id: _button + text: "Button" + font.pixelSize: 20 + property SignalSpy fontspy: SignalSpy { target: _button; signalName: "fontChanged" } + Popup { + id: _popup + property alias button: _button2 + property alias listview: _listview + y: _button.height + implicitHeight: Math.min(396, _listview.contentHeight) + property SignalSpy fontspy: SignalSpy { target: _popup; signalName: "fontChanged" } + contentItem: Column { + Button { + id: _button2 + text: "Button" + property SignalSpy fontspy: SignalSpy { target: _button2; signalName: "fontChanged" } + } + ListView { + id: _listview + height: _button.height * 20 + model: 2 + delegate: Button { + id: _button3 + objectName: "delegate" + width: _button.width + height: _button.height + text: "N: " + index + checkable: true + autoExclusive: true + property SignalSpy fontspy: SignalSpy { target: _button3; signalName: "fontChanged" } + } + } + } + } + } + } + } + } + } + + function test_font() { // QTBUG_50984, QTBUG-51696 + var window = createTemporaryObject(component, testCase) + verify(window) + + compare(window.font.pixelSize, 40) + compare(window.pane.font.pixelSize, 30) + compare(window.pane.button.font.pixelSize, 20) + compare(window.popup.font.pixelSize, 40) + compare(window.popup.button.font.pixelSize, 40) + + var idx1 = getChild(window.popup.listview.contentItem, "delegate", -1) + compare(window.popup.listview.contentItem.children[idx1].font.pixelSize, 40) + var idx2 = getChild(window.popup.listview.contentItem, "delegate", idx1) + compare(window.popup.listview.contentItem.children[idx2].font.pixelSize, 40) + + window.pane.button.font.pixelSize = 30 + compare(window.font.pixelSize, 40) + compare(window.fontspy.count, 0) + compare(window.pane.font.pixelSize, 30) + compare(window.pane.fontspy.count, 0) + compare(window.pane.button.font.pixelSize, 30) + compare(window.pane.button.fontspy.count, 1) + compare(window.popup.font.pixelSize, 40) + compare(window.popup.fontspy.count, 0) + compare(window.popup.button.font.pixelSize, 40) + compare(window.popup.button.fontspy.count, 0) + compare(window.popup.listview.contentItem.children[idx1].font.pixelSize, 40) + compare(window.popup.listview.contentItem.children[idx1].fontspy.count, 0) + compare(window.popup.listview.contentItem.children[idx2].font.pixelSize, 40) + compare(window.popup.listview.contentItem.children[idx2].fontspy.count, 0) + + window.font.pixelSize = 50 + compare(window.font.pixelSize, 50) + compare(window.fontspy.count, 1) + compare(window.pane.font.pixelSize, 30) + compare(window.pane.fontspy.count, 0) + compare(window.pane.button.font.pixelSize, 30) + compare(window.pane.button.fontspy.count, 1) + compare(window.popup.font.pixelSize, 50) + compare(window.popup.fontspy.count, 1) + compare(window.popup.button.font.pixelSize, 50) + compare(window.popup.button.fontspy.count, 1) + compare(window.popup.listview.contentItem.children[idx1].font.pixelSize, 50) + compare(window.popup.listview.contentItem.children[idx1].fontspy.count, 1) + compare(window.popup.listview.contentItem.children[idx2].font.pixelSize, 50) + compare(window.popup.listview.contentItem.children[idx2].fontspy.count, 1) + + window.popup.button.font.pixelSize = 10 + compare(window.font.pixelSize, 50) + compare(window.fontspy.count, 1) + compare(window.pane.font.pixelSize, 30) + compare(window.pane.fontspy.count, 0) + compare(window.pane.button.font.pixelSize, 30) + compare(window.pane.button.fontspy.count, 1) + compare(window.popup.font.pixelSize, 50) + compare(window.popup.fontspy.count, 1) + compare(window.popup.button.font.pixelSize, 10) + compare(window.popup.button.fontspy.count, 2) + compare(window.popup.listview.contentItem.children[idx1].font.pixelSize, 50) + compare(window.popup.listview.contentItem.children[idx1].fontspy.count, 1) + compare(window.popup.listview.contentItem.children[idx2].font.pixelSize, 50) + compare(window.popup.listview.contentItem.children[idx2].fontspy.count, 1) + + window.popup.font.pixelSize = 60 + compare(window.font.pixelSize, 50) + compare(window.fontspy.count, 1) + compare(window.pane.font.pixelSize, 30) + compare(window.pane.fontspy.count, 0) + compare(window.pane.button.font.pixelSize, 30) + compare(window.pane.button.fontspy.count, 1) + compare(window.popup.font.pixelSize, 60) + compare(window.popup.fontspy.count, 2) + compare(window.popup.button.font.pixelSize, 10) + compare(window.popup.button.fontspy.count, 2) + compare(window.popup.listview.contentItem.children[idx1].font.pixelSize, 60) + compare(window.popup.listview.contentItem.children[idx1].fontspy.count, 2) + compare(window.popup.listview.contentItem.children[idx2].font.pixelSize, 60) + compare(window.popup.listview.contentItem.children[idx2].fontspy.count, 2) + } + + Component { + id: localeComponent + Pane { + property alias button: _button + property alias popup: _popup + locale: Qt.locale("en_US") + Column { + Button { + id: _button + text: "Button" + locale: Qt.locale("nb_NO") + Popup { + id: _popup + property alias button1: _button1 + property alias button2: _button2 + y: _button.height + locale: Qt.locale("fi_FI") + implicitHeight: Math.min(396, _column.contentHeight) + contentItem: Column { + id: _column + Button { + id: _button1 + text: "Button 1" + objectName: "1" + } + Button { + id: _button2 + text: "Button 2" + locale: Qt.locale("nb_NO") + objectName: "2" + } + } + } + } + } + } + } + + function test_locale() { // QTBUG_50984 + // test looking up natural locale from ancestors + var control = createTemporaryObject(localeComponent, applicationWindow.contentItem) + verify(control) + + compare(control.locale.name, "en_US") + compare(control.button.locale.name, "nb_NO") + compare(control.popup.locale.name, "fi_FI") + compare(control.popup.button1.locale.name, "fi_FI") + compare(control.popup.button2.locale.name, "nb_NO") + + control.ApplicationWindow.window.locale = undefined + } + + Component { + id: localeChangeComponent + Pane { + id: _pane + property alias button: _button + property alias popup: _popup + property SignalSpy localespy: SignalSpy { + target: _pane + signalName: "localeChanged" + } + property SignalSpy mirrorspy: SignalSpy { + target: _pane + signalName: "mirroredChanged" + } + Column { + Button { + id: _button + text: "Button" + property SignalSpy localespy: SignalSpy { + target: _button + signalName: "localeChanged" + } + property SignalSpy mirrorspy: SignalSpy { + target: _button + signalName: "mirroredChanged" + } + Popup { + id: _popup + property alias button1: _button1 + property alias button2: _button2 + y: _button.height + implicitHeight: Math.min(396, _column.contentHeight) + property SignalSpy localespy: SignalSpy { + target: _popup + signalName: "localeChanged" + } + contentItem: Column { + id: _column + Button { + id: _button1 + text: "Button 1" + property SignalSpy localespy: SignalSpy { + target: _button1 + signalName: "localeChanged" + } + property SignalSpy mirrorspy: SignalSpy { + target: _button1 + signalName: "mirroredChanged" + } + } + Button { + id: _button2 + text: "Button 2" + property SignalSpy localespy: SignalSpy { + target: _button2 + signalName: "localeChanged" + } + property SignalSpy mirrorspy: SignalSpy { + target: _button2 + signalName: "mirroredChanged" + } + } + } + } + } + } + } + } + + function test_locale_changes() { // QTBUG_50984 + // test default locale and locale inheritance + var control = createTemporaryObject(localeChangeComponent, applicationWindow.contentItem) + verify(control) + + var defaultLocale = Qt.locale() + compare(control.ApplicationWindow.window.locale.name, defaultLocale.name) + compare(control.locale.name, defaultLocale.name) + compare(control.button.locale.name, defaultLocale.name) + compare(control.popup.locale.name, defaultLocale.name) + compare(control.popup.button1.locale.name, defaultLocale.name) + compare(control.popup.button2.locale.name, defaultLocale.name) + + control.ApplicationWindow.window.locale = Qt.locale("nb_NO") + compare(control.ApplicationWindow.window.locale.name, "nb_NO") + compare(control.locale.name, "nb_NO") + compare(control.button.locale.name, "nb_NO") + compare(control.popup.locale.name, "nb_NO") + compare(control.popup.button1.locale.name, "nb_NO") + compare(control.popup.button2.locale.name, "nb_NO") + compare(control.localespy.count, 1) + compare(control.button.localespy.count, 1) + compare(control.popup.localespy.count, 1) + compare(control.popup.button1.localespy.count, 1) + compare(control.popup.button2.localespy.count, 1) + + control.ApplicationWindow.window.locale = undefined + compare(control.ApplicationWindow.window.locale.name, defaultLocale.name) + compare(control.locale.name, defaultLocale.name) + compare(control.button.locale.name, defaultLocale.name) + compare(control.popup.locale.name, defaultLocale.name) + compare(control.popup.button1.locale.name, defaultLocale.name) + compare(control.popup.button2.locale.name, defaultLocale.name) + compare(control.localespy.count, 2) + compare(control.button.localespy.count, 2) + compare(control.popup.localespy.count, 2) + compare(control.popup.button1.localespy.count, 2) + compare(control.popup.button2.localespy.count, 2) + + control.locale = Qt.locale("ar_EG") + compare(control.ApplicationWindow.window.locale.name, defaultLocale.name) + compare(control.locale.name, "ar_EG") + compare(control.button.locale.name, "ar_EG") + compare(control.popup.locale.name, defaultLocale.name) + compare(control.popup.button1.locale.name, defaultLocale.name) + compare(control.popup.button2.locale.name, defaultLocale.name) + compare(control.localespy.count, 3) + compare(control.mirrorspy.count, 0) + compare(control.button.localespy.count, 3) + compare(control.button.mirrorspy.count, 0) + compare(control.popup.localespy.count, 2) + compare(control.popup.button1.localespy.count, 2) + compare(control.popup.button2.localespy.count, 2) + + control.ApplicationWindow.window.locale = Qt.locale("ar_EG") + compare(control.ApplicationWindow.window.locale.name, "ar_EG") + compare(control.locale.name, "ar_EG") + compare(control.button.locale.name, "ar_EG") + compare(control.popup.locale.name, "ar_EG") + compare(control.popup.button1.locale.name, "ar_EG") + compare(control.popup.button2.locale.name, "ar_EG") + compare(control.localespy.count, 3) + compare(control.mirrorspy.count, 0) + compare(control.button.localespy.count, 3) + compare(control.button.mirrorspy.count, 0) + compare(control.popup.localespy.count, 3) + compare(control.popup.button1.localespy.count, 3) + compare(control.popup.button1.mirrorspy.count, 0) + compare(control.popup.button2.localespy.count, 3) + compare(control.popup.button2.mirrorspy.count, 0) + + control.button.locale = Qt.locale("nb_NO") + compare(control.ApplicationWindow.window.locale.name, "ar_EG") + compare(control.locale.name, "ar_EG") + compare(control.button.locale.name, "nb_NO") + compare(control.popup.locale.name, "ar_EG") + compare(control.popup.button1.locale.name, "ar_EG") + compare(control.popup.button2.locale.name, "ar_EG") + compare(control.localespy.count, 3) + compare(control.mirrorspy.count, 0) + compare(control.button.localespy.count, 4) + compare(control.button.mirrorspy.count, 0) + compare(control.popup.localespy.count, 3) + compare(control.popup.button1.localespy.count, 3) + compare(control.popup.button2.localespy.count, 3) + + control.locale = undefined + compare(control.ApplicationWindow.window.locale.name, "ar_EG") + compare(control.locale.name, "ar_EG") + compare(control.button.locale.name, "nb_NO") + compare(control.popup.locale.name, "ar_EG") + compare(control.popup.button1.locale.name, "ar_EG") + compare(control.popup.button2.locale.name, "ar_EG") + compare(control.localespy.count, 3) + compare(control.mirrorspy.count, 0) + compare(control.button.localespy.count, 4) + compare(control.button.mirrorspy.count, 0) + compare(control.popup.localespy.count, 3) + compare(control.popup.button1.localespy.count, 3) + compare(control.popup.button2.localespy.count, 3) + + control.popup.button1.locale = Qt.locale("nb_NO") + compare(control.ApplicationWindow.window.locale.name, "ar_EG") + compare(control.locale.name, "ar_EG") + compare(control.button.locale.name, "nb_NO") + compare(control.popup.locale.name, "ar_EG") + compare(control.popup.button1.locale.name, "nb_NO") + compare(control.popup.button2.locale.name, "ar_EG") + compare(control.localespy.count, 3) + compare(control.mirrorspy.count, 0) + compare(control.button.localespy.count, 4) + compare(control.button.mirrorspy.count, 0) + compare(control.popup.localespy.count, 3) + compare(control.popup.button1.localespy.count, 4) + compare(control.popup.button1.mirrorspy.count, 0) + compare(control.popup.button2.localespy.count, 3) + compare(control.popup.button2.mirrorspy.count, 0) + + control.popup.locale = Qt.locale("fi_FI") + compare(control.ApplicationWindow.window.locale.name, "ar_EG") + compare(control.locale.name, "ar_EG") + compare(control.button.locale.name, "nb_NO") + compare(control.popup.locale.name, "fi_FI") + compare(control.popup.button1.locale.name, "nb_NO") + compare(control.popup.button2.locale.name, "fi_FI") + compare(control.localespy.count, 3) + compare(control.mirrorspy.count, 0) + compare(control.button.localespy.count, 4) + compare(control.button.mirrorspy.count, 0) + compare(control.popup.localespy.count, 4) + compare(control.popup.button1.localespy.count, 4) + compare(control.popup.button1.mirrorspy.count, 0) + compare(control.popup.button2.localespy.count, 4) + compare(control.popup.button2.mirrorspy.count, 0) + + control.ApplicationWindow.window.locale = undefined + compare(control.ApplicationWindow.window.locale.name, defaultLocale.name) + compare(control.locale.name, defaultLocale.name) + compare(control.button.locale.name, "nb_NO") + compare(control.popup.locale.name, "fi_FI") + compare(control.popup.button1.locale.name, "nb_NO") + compare(control.popup.button2.locale.name, "fi_FI") + compare(control.localespy.count, 4) + compare(control.mirrorspy.count, 0) + compare(control.button.localespy.count, 4) + compare(control.button.mirrorspy.count, 0) + compare(control.popup.localespy.count, 4) + compare(control.popup.button1.localespy.count, 4) + compare(control.popup.button1.mirrorspy.count, 0) + compare(control.popup.button2.localespy.count, 4) + compare(control.popup.button2.mirrorspy.count, 0) + + control.popup.locale = undefined + compare(control.ApplicationWindow.window.locale.name, defaultLocale.name) + compare(control.locale.name, defaultLocale.name) + compare(control.button.locale.name, "nb_NO") + compare(control.popup.locale.name, defaultLocale.name) + compare(control.popup.button1.locale.name, "nb_NO") + compare(control.popup.button2.locale.name, defaultLocale.name) + compare(control.localespy.count, 4) + compare(control.mirrorspy.count, 0) + compare(control.button.localespy.count, 4) + compare(control.button.mirrorspy.count, 0) + compare(control.popup.localespy.count, 5) + compare(control.popup.button1.localespy.count, 4) + compare(control.popup.button1.mirrorspy.count, 0) + compare(control.popup.button2.localespy.count, 5) + compare(control.popup.button2.mirrorspy.count, 0) + } + + function test_size() { + var control = createTemporaryObject(popupControl, testCase) + verify(control) + + var openedSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "opened"}) + verify(openedSpy.valid) + + control.open() + openedSpy.wait() + compare(openedSpy.count, 1) + verify(control.visible) + + // remove the background so that it won't affect the implicit size of the popup, + // so the implicit sizes tested below are entirely based on the content size + control.background = null + + // implicit size of the content + control.contentItem.implicitWidth = 10 + compare(control.implicitWidth, 10 + control.leftPadding + control.rightPadding) + compare(control.width, control.implicitWidth) + compare(control.contentItem.width, control.width - control.leftPadding - control.rightPadding) + + control.contentItem.implicitHeight = 20 + compare(control.implicitHeight, 20 + control.topPadding + control.bottomPadding) + compare(control.height, control.implicitHeight) + compare(control.contentItem.height, control.height - control.topPadding - control.bottomPadding) + + // implicit size of the popup + control.implicitWidth = 30 + compare(control.implicitWidth, 30) + compare(control.width, 30) + compare(control.contentItem.width, control.width - control.leftPadding - control.rightPadding) + + control.implicitHeight = 40 + compare(control.implicitHeight, 40) + compare(control.height, 40) + compare(control.contentItem.height, control.height - control.topPadding - control.bottomPadding) + + // set explicit size + control.width = 50 + compare(control.implicitWidth, 30) + compare(control.width, 50) + compare(control.contentItem.width, control.width - control.leftPadding - control.rightPadding) + + control.height = 60 + compare(control.implicitHeight, 40) + compare(control.height, 60) + compare(control.contentItem.height, control.height - control.topPadding - control.bottomPadding) + + // reset explicit size + control.width = undefined + compare(control.implicitWidth, 30) + compare(control.width, 30) + compare(control.contentItem.width, control.width - control.leftPadding - control.rightPadding) + + control.height = undefined + compare(control.implicitHeight, 40) + compare(control.height, 40) + compare(control.contentItem.height, control.height - control.topPadding - control.bottomPadding) + } + + function test_visible() { + var control = createTemporaryObject(popupTemplate, testCase, {visible: true}) + verify(control) + + // QTBUG-51989 + tryCompare(control, "visible", true) + + // QTBUG-55347 + control.parent = null + verify(!control.visible) + } + + Component { + id: overlayTest + ApplicationWindow { + property alias firstDrawer: firstDrawer + property alias secondDrawer: secondDrawer + property alias modalPopup: modalPopup + property alias modelessPopup: modelessPopup + property alias plainPopup: plainPopup + property alias modalPopupWithoutDim: modalPopupWithoutDim + visible: true + Drawer { + z: 0 + id: firstDrawer + } + Drawer { + z: 1 + id: secondDrawer + } + Popup { + id: modalPopup + z: 2 + modal: true + exit: Transition { PauseAnimation { duration: 200 } } + } + Popup { + id: modelessPopup + z: 3 + dim: true + exit: Transition { PauseAnimation { duration: 200 } } + } + Popup { + id: plainPopup + z: 4 + enter: Transition { PauseAnimation { duration: 200 } } + exit: Transition { PauseAnimation { duration: 200 } } + } + Popup { + id: modalPopupWithoutDim + z: 5 + dim: false + modal: true + exit: Transition { PauseAnimation { duration: 200 } } + } + } + } + + function indexOf(array, item) { + for (var idx = 0; idx < array.length; ++idx) { + if (item === array[idx]) + return idx; + } + return -1 + } + + function findOverlay(window, popup) { + var item = popup.contentItem.parent + var idx = indexOf(window.Overlay.overlay.children, item) + return window.Overlay.overlay.children[idx - 1] + } + + function test_overlay() { + var window = createTemporaryObject(overlayTest, testCase) + verify(window) + + window.requestActivate() + tryCompare(window, "active", true) + + compare(window.Overlay.overlay.children.length, 0) + + var firstOverlay = findOverlay(window, window.firstDrawer) + verify(!firstOverlay) + window.firstDrawer.open() + compare(window.Overlay.overlay.children.length, 2) // 1 drawer + 1 overlay + firstOverlay = findOverlay(window, window.firstDrawer) + verify(firstOverlay) + compare(firstOverlay.z, window.firstDrawer.z) + compare(indexOf(window.Overlay.overlay.children, firstOverlay), + indexOf(window.Overlay.overlay.children, window.firstDrawer.contentItem.parent) - 1) + tryCompare(firstOverlay, "opacity", 1.0) + + var secondOverlay = findOverlay(window, window.secondDrawer) + verify(!secondOverlay) + window.secondDrawer.open() + compare(window.Overlay.overlay.children.length, 4) // 2 drawers + 2 overlays + secondOverlay = findOverlay(window, window.secondDrawer) + verify(secondOverlay) + compare(secondOverlay.z, window.secondDrawer.z) + compare(indexOf(window.Overlay.overlay.children, secondOverlay), + indexOf(window.Overlay.overlay.children, window.secondDrawer.contentItem.parent) - 1) + tryCompare(secondOverlay, "opacity", 1.0) + + window.firstDrawer.close() + tryCompare(window.firstDrawer, "visible", false) + firstOverlay = findOverlay(window, window.firstDrawer) + verify(!firstOverlay) + compare(window.Overlay.overlay.children.length, 2) // 1 drawer + 1 overlay + + window.secondDrawer.close() + tryCompare(window.secondDrawer, "visible", false) + secondOverlay = findOverlay(window, window.secondDrawer) + verify(!secondOverlay) + compare(window.Overlay.overlay.children.length, 0) + + var modalOverlay = findOverlay(window, window.modalPopup) + verify(!modalOverlay) + window.modalPopup.open() + modalOverlay = findOverlay(window, window.modalPopup) + verify(modalOverlay) + compare(modalOverlay.z, window.modalPopup.z) + compare(window.modalPopup.visible, true) + tryCompare(modalOverlay, "opacity", 1.0) + compare(window.Overlay.overlay.children.length, 2) // 1 popup + 1 overlay + + var modelessOverlay = findOverlay(window, window.modelessPopup) + verify(!modelessOverlay) + window.modelessPopup.open() + modelessOverlay = findOverlay(window, window.modelessPopup) + verify(modelessOverlay) + compare(modelessOverlay.z, window.modelessPopup.z) + compare(window.modelessPopup.visible, true) + tryCompare(modelessOverlay, "opacity", 1.0) + compare(window.Overlay.overlay.children.length, 4) // 2 popups + 2 overlays + + window.modelessPopup.close() + tryCompare(window.modelessPopup, "visible", false) + modelessOverlay = findOverlay(window, window.modelessPopup) + verify(!modelessOverlay) + compare(window.Overlay.overlay.children.length, 2) // 1 popup + 1 overlay + + compare(window.modalPopup.visible, true) + compare(modalOverlay.opacity, 1.0) + + window.modalPopup.close() + tryCompare(window.modalPopup, "visible", false) + modalOverlay = findOverlay(window, window.modalPopup) + verify(!modalOverlay) + compare(window.Overlay.overlay.children.length, 0) + + window.plainPopup.open() + tryCompare(window.plainPopup, "visible", true) + compare(window.Overlay.overlay.children.length, 1) // only popup added, no overlays involved + + window.plainPopup.modal = true + compare(window.Overlay.overlay.children.length, 2) // overlay added + + window.plainPopup.close() + tryCompare(window.plainPopup, "visible", false) + compare(window.Overlay.overlay.children.length, 0) // popup + overlay removed + + window.modalPopupWithoutDim.open() + tryCompare(window.modalPopupWithoutDim, "visible", true) + compare(window.Overlay.overlay.children.length, 1) // only popup added, no overlays involved + + window.modalPopupWithoutDim.dim = true + compare(window.Overlay.overlay.children.length, 2) // overlay added + + window.modalPopupWithoutDim.close() + tryCompare(window.modalPopupWithoutDim, "visible", false) + compare(window.Overlay.overlay.children.length, 0) // popup + overlay removed + } + + function test_attached_applicationwindow() { + var control = createTemporaryObject(popupControl, applicationWindow.contentItem) + verify(control) + + var child = rect.createObject(control.contentItem) + + compare(control.ApplicationWindow.window, applicationWindow) + compare(control.contentItem.ApplicationWindow.window, applicationWindow) + compare(child.ApplicationWindow.window, applicationWindow) + + control.parent = null + compare(control.ApplicationWindow.window, null) + compare(control.contentItem.ApplicationWindow.window, null) + compare(child.ApplicationWindow.window, null) + } + + Component { + id: pausePopup + Popup { + enter: Transition { PauseAnimation { duration: 200 } } + exit: Transition { PauseAnimation { duration: 200 } } + } + } + + function test_openedClosed() { + var control = createTemporaryObject(pausePopup, testCase) + verify(control) + + var openedSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "opened"}) + verify(openedSpy.valid) + var closedSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "closed"}) + verify(closedSpy.valid) + var openedChangeSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "openedChanged"}) + verify(openedChangeSpy.valid) + + control.open() + compare(control.visible, true) + compare(control.opened, false) + compare(openedChangeSpy.count, 0) + compare(openedSpy.count, 0) + tryCompare(openedSpy, "count", 1) + compare(control.opened, true) + compare(openedChangeSpy.count, 1) + compare(closedSpy.count, 0) + + control.close() + compare(control.visible, true) + compare(control.opened, false) + compare(openedChangeSpy.count, 2) + compare(openedSpy.count, 1) + compare(closedSpy.count, 0) + tryCompare(closedSpy, "count", 1) + compare(control.opened, false) + compare(openedChangeSpy.count, 2) + compare(control.visible, false) + } + + Component { + id: xyBindingLoop + ApplicationWindow { + id: window + width: 360 + height: 360 + visible: true + property alias popup: popup + + Popup { + id: popup + visible: true + x: (parent.width - width) / 2 + y: (parent.height - height) / 2 + Label { + text: "Content" + anchors.fill: parent + } + } + } + } + + function test_xyBindingLoop() { + var window = createTemporaryObject(xyBindingLoop, testCase) + var control = window.popup + waitForRendering(control.contentItem) + compare(control.x, (control.parent.width - control.width) / 2) + compare(control.y, (control.parent.height - control.height) / 2) + } + + function test_windowParent() { + var control = createTemporaryObject(popupControl, applicationWindow, {width: 100, height: 100}) + verify(control) + + control.open() + verify(control.visible) + } + + function test_deferredBackgroundSize() { + var control = createTemporaryObject(popupControl, testCase, {width: 200, height: 100}) + verify(control) + + compare(control.background.width, 200 + (control.background.leftInset || 0) + (control.background.rightInset || 0)) + compare(control.background.height, 100 + (control.background.topInset || 0) + (control.background.bottomInset || 0)) + } + + function test_anchors() { + var control = createTemporaryObject(popupControl, applicationWindow.contentItem.Overlay.overlay, + { visible: true, width: 100, height: 100 }) + verify(control) + verify(control.visible) + // If there is a transition then make sure it is finished + if (control.enter !== null) + tryCompare(control.enter, "running", false) + compare(control.parent, control.Overlay.overlay) + compare(control.x, 0) + compare(control.y, 0) + + var overlay = control.Overlay.overlay + verify(overlay) + + var centerInSpy = createTemporaryObject(signalSpy, testCase, { target: control.anchors, signalName: "centerInChanged" }) + verify(centerInSpy.valid) + + applicationWindow.visible = true + verify(waitForRendering(applicationWindow.contentItem)) + verify(overlay.width > 0) + verify(overlay.height > 0) + + // Center the popup in the window via the overlay. + control.anchors.centerIn = Qt.binding(function() { return control.parent; }) + compare(centerInSpy.count, 1) + compare(control.x, (overlay.width - (control.width * control.scale)) / 2) + compare(control.y, (overlay.height - (control.width * control.scale)) / 2) + + // Ensure that it warns when trying to set it to an item that's not its parent. + var anotherItem = createTemporaryObject(rect, applicationWindow.contentItem, { x: 100, y: 100, width: 50, height: 50 }) + verify(anotherItem) + + ignoreWarning(Qt.resolvedUrl("tst_popup.qml") + ":77:9: QML Popup: Popup can only be centered within its immediate parent or Overlay.overlay") + control.anchors.centerIn = anotherItem + // The property will change, because we can't be sure that the parent + // in QQuickPopupAnchors::setCenterIn() is the final parent, as some reparenting can happen. + // We still expect the warning from QQuickPopupPositioner::reposition() though. + compare(centerInSpy.count, 2) + compare(control.anchors.centerIn, anotherItem) + + // The binding to the popup's parent was broken above, so restore it. + control.anchors.centerIn = Qt.binding(function() { return control.parent; }) + compare(centerInSpy.count, 3) + + // Change the popup's parent and ensure that it's anchored accordingly. + control.parent = Qt.binding(function() { return anotherItem; }) + compare(control.parent, anotherItem) + compare(control.anchors.centerIn, anotherItem) + compare(centerInSpy.count, 4) + compare(control.x, (anotherItem.width - (control.width * control.scale)) / 2) + compare(control.y, (anotherItem.height - (control.height * control.scale)) / 2) + + // Check that anchors.centerIn beats x and y coordinates as it does in QQuickItem. + control.x = 33; + control.y = 44; + compare(control.x, (anotherItem.width - (control.width * control.scale)) / 2) + compare(control.y, (anotherItem.height - (control.height * control.scale)) / 2) + + // Check that the popup's x and y coordinates are restored when it's no longer centered. + control.anchors.centerIn = undefined + compare(centerInSpy.count, 5) + compare(control.x, 33) + compare(control.y, 44) + + // Test centering in the overlay while having a different parent (anotherItem). + control.anchors.centerIn = overlay + compare(centerInSpy.count, 6) + compare(control.x, (overlay.width - (control.width * control.scale)) / 2) + compare(control.y, (overlay.height - (control.height * control.scale)) / 2) + + // TODO: do this properly by creating a component or something + applicationWindow.visible = false + } + + Component { + id: shortcutWindowComponent + ApplicationWindow { + id: window + width: 360 + height: 360 + visible: true + + property alias popup: popup + property alias shortcut: shortcut + + Popup { + id: popup + + Shortcut { + id: shortcut + sequence: "A" + onActivated: popup.visible = !popup.visible + } + } + } + } + + function test_shortcut() { + // Tests that a Shortcut with Qt.WindowShortcut context + // that is declared within a Popup is activated. + var window = createTemporaryObject(shortcutWindowComponent, testCase) + var control = window.popup + + window.requestActivate() + tryCompare(window, "active", true) + + var shortcutActivatedSpy = createTemporaryObject(signalSpy, testCase, + { target: window.shortcut, signalName: "activated"} ) + verify(shortcutActivatedSpy.valid) + + waitForRendering(window.contentItem) + keyClick(Qt.Key_A) + compare(shortcutActivatedSpy.count, 1) + tryCompare(control, "visible", true) + + keyClick(Qt.Key_A) + compare(shortcutActivatedSpy.count, 2) + tryCompare(control, "visible", false) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_progressbar.qml b/tests/auto/quickcontrols2/controls/data/tst_progressbar.qml new file mode 100644 index 0000000000..2d26440927 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_progressbar.qml @@ -0,0 +1,196 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "ProgressBar" + + Component { + id: progressBar + ProgressBar { } + } + + function test_value() { + var control = createTemporaryObject(progressBar, testCase) + verify(control) + + compare(control.value, 0.0) + control.value = 0.5 + compare(control.value, 0.5) + control.value = 1.0 + compare(control.value, 1.0) + control.value = -1.0 + compare(control.value, 0.0) + control.value = 2.0 + compare(control.value, 1.0) + } + + function test_range() { + var control = createTemporaryObject(progressBar, testCase, {from: 0, to: 100, value: 50}) + verify(control) + + compare(control.from, 0) + compare(control.to, 100) + compare(control.value, 50) + compare(control.position, 0.5) + + control.value = 1000 + compare(control.value, 100) + compare(control.position, 1) + + control.value = -1 + compare(control.value, 0) + compare(control.position, 0) + + control.from = 25 + compare(control.from, 25) + compare(control.value, 25) + compare(control.position, 0) + + control.to = 75 + compare(control.to, 75) + compare(control.value, 25) + compare(control.position, 0) + + control.value = 50 + compare(control.value, 50) + compare(control.position, 0.5) + } + + function test_inverted() { + var control = createTemporaryObject(progressBar, testCase, {from: 1.0, to: -1.0}) + verify(control) + + compare(control.from, 1.0) + compare(control.to, -1.0) + compare(control.value, 0.0) + compare(control.position, 0.5) + + control.value = 2.0 + compare(control.value, 1.0) + compare(control.position, 0.0) + + control.value = -2.0 + compare(control.value, -1.0) + compare(control.position, 1.0) + + control.value = 0.0 + compare(control.value, 0.0) + compare(control.position, 0.5) + } + + function test_position() { + var control = createTemporaryObject(progressBar, testCase) + verify(control) + + compare(control.value, 0) + compare(control.position, 0) + + control.value = 0.25 + compare(control.value, 0.25) + compare(control.position, 0.25) + + control.value = 0.75 + compare(control.value, 0.75) + compare(control.position, 0.75) + } + + function test_visualPosition() { + var control = createTemporaryObject(progressBar, testCase) + verify(control) + + compare(control.value, 0) + compare(control.visualPosition, 0) + + control.value = 0.25 + compare(control.value, 0.25) + compare(control.visualPosition, 0.25) + + // RTL locale + control.locale = Qt.locale("ar_EG") + compare(control.visualPosition, 0.25) + + // RTL locale + LayoutMirroring + control.LayoutMirroring.enabled = true + compare(control.visualPosition, 0.75) + + // LTR locale + LayoutMirroring + control.locale = Qt.locale("en_US") + compare(control.visualPosition, 0.75) + + // LTR locale + control.LayoutMirroring.enabled = false + compare(control.visualPosition, 0.25) + + // LayoutMirroring + control.LayoutMirroring.enabled = true + compare(control.visualPosition, 0.75) + } + + function test_indeterminate() { + var control = createTemporaryObject(progressBar, testCase) + verify(control) + compare(control.indeterminate, false) + + wait(100) + control.indeterminate = true + wait(100) + // Shouldn't crash... + control.indeterminate = false + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_radiobutton.qml b/tests/auto/quickcontrols2/controls/data/tst_radiobutton.qml new file mode 100644 index 0000000000..973e56a360 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_radiobutton.qml @@ -0,0 +1,380 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 200 + height: 200 + visible: true + when: windowShown + name: "RadioButton" + + Component { + id: radioButton + RadioButton { } + } + + Component { + id: signalSequenceSpy + SignalSequenceSpy { + signals: ["pressed", "released", "canceled", "clicked", "toggled", "pressedChanged", "checkedChanged"] + } + } + + function test_text() { + var control = createTemporaryObject(radioButton, testCase) + verify(control) + + compare(control.text, "") + control.text = "RadioButton" + compare(control.text, "RadioButton") + control.text = "" + compare(control.text, "") + } + + function test_checked() { + var control = createTemporaryObject(radioButton, testCase) + verify(control) + + var sequenceSpy = signalSequenceSpy.createObject(control, {target: control}) + + sequenceSpy.expectedSequence = [] // No change expected + compare(control.checked, false) + verify(sequenceSpy.success) + + sequenceSpy.expectedSequence = ["checkedChanged"] + control.checked = true + compare(control.checked, true) + verify(sequenceSpy.success) + + sequenceSpy.reset() + control.checked = false + compare(control.checked, false) + verify(sequenceSpy.success) + } + + function test_mouse() { + var control = createTemporaryObject(radioButton, testCase) + verify(control) + + var sequenceSpy = signalSequenceSpy.createObject(control, {target: control}) + + // check + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], + "pressed"] + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.pressed, true) + verify(sequenceSpy.success) + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": false }], + ["checkedChanged", { "pressed": false, "checked": true }], + "toggled", + "released", + "clicked"] + mouseRelease(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.checked, true) + compare(control.pressed, false) + verify(sequenceSpy.success) + + // attempt uncheck + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], + "pressed"] + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.pressed, true) + verify(sequenceSpy.success) + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": true }], + "released", + "clicked"] + mouseRelease(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.checked, true) + compare(control.pressed, false) + verify(sequenceSpy.success) + + // release outside + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], + "pressed"] + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.pressed, true) + verify(sequenceSpy.success) + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": true }]] + mouseMove(control, control.width * 2, control.height * 2, 0) + compare(control.pressed, false) + sequenceSpy.expectedSequence = [["canceled", { "pressed": false, "checked": true }]] + mouseRelease(control, control.width * 2, control.height * 2, Qt.LeftButton) + compare(control.checked, true) + compare(control.pressed, false) + verify(sequenceSpy.success) + + // right button + sequenceSpy.expectedSequence = [] + mousePress(control, control.width / 2, control.height / 2, Qt.RightButton) + compare(control.pressed, false) + verify(sequenceSpy.success) + mouseRelease(control, control.width / 2, control.height / 2, Qt.RightButton) + compare(control.checked, true) + compare(control.pressed, false) + verify(sequenceSpy.success) + } + + function test_touch() { + var control = createTemporaryObject(radioButton, testCase) + verify(control) + + var touch = touchEvent(control) + + var sequenceSpy = signalSequenceSpy.createObject(control, {target: control}) + + // check + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], + "pressed"] + touch.press(0, control, control.width / 2, control.height / 2).commit() + compare(control.pressed, true) + verify(sequenceSpy.success) + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": false }], + ["checkedChanged", { "pressed": false, "checked": true }], + "toggled", + "released", + "clicked"] + touch.release(0, control, control.width / 2, control.height / 2).commit() + compare(control.checked, true) + compare(control.pressed, false) + verify(sequenceSpy.success) + + // attempt uncheck + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], + "pressed"] + // Don't want to double-click. + wait(Qt.styleHints.mouseDoubleClickInterval + 50) + touch.press(0, control, control.width / 2, control.height / 2).commit() + compare(control.pressed, true) + verify(sequenceSpy.success) + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": true }], + "released", + "clicked"] + touch.release(0, control, control.width / 2, control.height / 2).commit() + compare(control.checked, true) + compare(control.pressed, false) + verify(sequenceSpy.success) + + // release outside + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], + "pressed"] + wait(Qt.styleHints.mouseDoubleClickInterval + 50) + touch.press(0, control, control.width / 2, control.height / 2).commit() + compare(control.pressed, true) + verify(sequenceSpy.success) + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": true }]] + touch.move(0, control, control.width * 2, control.height * 2).commit() + compare(control.pressed, false) + sequenceSpy.expectedSequence = [["canceled", { "pressed": false, "checked": true }]] + touch.release(0, control, control.width * 2, control.height * 2).commit() + compare(control.checked, true) + compare(control.pressed, false) + verify(sequenceSpy.success) + } + + function test_keys() { + var control = createTemporaryObject(radioButton, testCase) + verify(control) + + var sequenceSpy = signalSequenceSpy.createObject(control, {target: control}) + + sequenceSpy.expectedSequence = [] + control.forceActiveFocus() + verify(control.activeFocus) + verify(sequenceSpy.success) + + // check + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], + "pressed", + ["pressedChanged", { "pressed": false, "checked": false }], + ["checkedChanged", { "pressed": false, "checked": true }], + "toggled", + "released", + "clicked"] + keyClick(Qt.Key_Space) + compare(control.checked, true) + verify(sequenceSpy.success) + + // attempt uncheck + sequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], + "pressed", + ["pressedChanged", { "pressed": false, "checked": true }], + "released", + "clicked"] + keyClick(Qt.Key_Space) + compare(control.checked, true) + verify(sequenceSpy.success) + + // no change + sequenceSpy.expectedSequence = [] + var keys = [Qt.Key_Enter, Qt.Key_Return, Qt.Key_Escape, Qt.Key_Tab] + for (var i = 0; i < keys.length; ++i) { + sequenceSpy.reset() + keyClick(keys[i]) + compare(control.checked, true) + verify(sequenceSpy.success) + } + } + + Component { + id: twoRadioButtons + Item { + property RadioButton rb1: RadioButton { id: rb1 } + property RadioButton rb2: RadioButton { id: rb2; checked: rb1.checked; enabled: false } + } + } + + function test_binding() { + var container = createTemporaryObject(twoRadioButtons, testCase) + verify(container) + + compare(container.rb1.checked, false) + compare(container.rb2.checked, false) + + container.rb1.checked = true + compare(container.rb1.checked, true) + compare(container.rb2.checked, true) + + container.rb1.checked = false + compare(container.rb1.checked, false) + compare(container.rb2.checked, false) + } + + Component { + id: radioButtonGroup + Column { + // auto-exclusive buttons behave as if they were in their own exclusive group + RadioButton { } + RadioButton { } + + // explicitly grouped buttons are only exclusive with each other, not with + // auto-exclusive buttons, and the autoExclusive property is ignored + ButtonGroup { id: eg } + RadioButton { ButtonGroup.group: eg } + RadioButton { ButtonGroup.group: eg; autoExclusive: false } + + ButtonGroup { id: eg2 } + RadioButton { id: rb1; Component.onCompleted: eg2.addButton(rb1) } + RadioButton { id: rb2; Component.onCompleted: eg2.addButton(rb2) } + + // non-exclusive buttons don't affect the others + RadioButton { autoExclusive: false } + RadioButton { autoExclusive: false } + } + } + + function test_autoExclusive() { + var container = createTemporaryObject(radioButtonGroup, testCase) + compare(container.children.length, 8) + + var checkStates = [false, false, false, false, false, false, false, false] + for (var i = 0; i < 8; ++i) + compare(container.children[i].checked, checkStates[i]) + + container.children[0].checked = true + checkStates[0] = true + for (i = 0; i < 8; ++i) + compare(container.children[i].checked, checkStates[i]) + + container.children[1].checked = true + checkStates[0] = false + checkStates[1] = true + for (i = 0; i < 8; ++i) + compare(container.children[i].checked, checkStates[i]) + + container.children[2].checked = true + checkStates[2] = true + for (i = 0; i < 8; ++i) + compare(container.children[i].checked, checkStates[i]) + + container.children[3].checked = true + checkStates[2] = false + checkStates[3] = true + for (i = 0; i < 8; ++i) + compare(container.children[i].checked, checkStates[i]) + + container.children[4].checked = true + checkStates[4] = true + for (i = 0; i < 8; ++i) + compare(container.children[i].checked, checkStates[i]) + + container.children[5].checked = true + checkStates[4] = false + checkStates[5] = true + for (i = 0; i < 8; ++i) + compare(container.children[i].checked, checkStates[i]) + + container.children[6].checked = true + checkStates[6] = true + for (i = 0; i < 8; ++i) + compare(container.children[i].checked, checkStates[i]) + + container.children[7].checked = true + checkStates[7] = true + for (i = 0; i < 8; ++i) + compare(container.children[i].checked, checkStates[i]) + + container.children[0].checked = true + checkStates[0] = true + checkStates[1] = false + for (i = 0; i < 8; ++i) + compare(container.children[i].checked, checkStates[i]) + } + + function test_baseline() { + var control = createTemporaryObject(radioButton, testCase) + verify(control) + compare(control.baselineOffset, control.contentItem.y + control.contentItem.baselineOffset) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_radiodelegate.qml b/tests/auto/quickcontrols2/controls/data/tst_radiodelegate.qml new file mode 100644 index 0000000000..87984b2c15 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_radiodelegate.qml @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 200 + height: 200 + visible: true + when: windowShown + name: "RadioDelegate" + + Component { + id: radioDelegate + RadioDelegate {} + } + + // TODO: data-fy tst_radiobutton (rename to tst_radio?) so we don't duplicate its tests here? + + function test_defaults() { + var control = createTemporaryObject(radioDelegate, testCase); + verify(control); + verify(!control.checked); + } + + function test_checked() { + var control = createTemporaryObject(radioDelegate, testCase); + verify(control); + + mouseClick(control); + verify(control.checked); + + mouseClick(control); + verify(control.checked); + } + + function test_baseline() { + var control = createTemporaryObject(radioDelegate, testCase); + verify(control); + compare(control.baselineOffset, control.contentItem.y + control.contentItem.baselineOffset); + } + + function test_spacing() { + var control = createTemporaryObject(radioDelegate, testCase, { text: "Some long, long, long text" }) + verify(control) + verify(control.contentItem.implicitWidth + control.leftPadding + control.rightPadding > control.background.implicitWidth) + + var textLabel = findChild(control.contentItem, "label") + verify(textLabel) + + // The implicitWidth of the IconLabel that all buttons use as their contentItem should be + // equal to the implicitWidth of the Text and the radio indicator + spacing while no icon is set. + compare(control.contentItem.implicitWidth, textLabel.implicitWidth + control.indicator.width + control.spacing) + + control.spacing += 100 + compare(control.contentItem.implicitWidth, textLabel.implicitWidth + control.indicator.width + control.spacing) + + compare(control.implicitWidth, textLabel.implicitWidth + control.indicator.width + control.spacing + control.leftPadding + control.rightPadding) + } + + function test_display_data() { + return [ + { "tag": "IconOnly", display: RadioDelegate.IconOnly }, + { "tag": "TextOnly", display: RadioDelegate.TextOnly }, + { "tag": "TextUnderIcon", display: RadioDelegate.TextUnderIcon }, + { "tag": "TextBesideIcon", display: RadioDelegate.TextBesideIcon }, + { "tag": "IconOnly, mirrored", display: RadioDelegate.IconOnly, mirrored: true }, + { "tag": "TextOnly, mirrored", display: RadioDelegate.TextOnly, mirrored: true }, + { "tag": "TextUnderIcon, mirrored", display: RadioDelegate.TextUnderIcon, mirrored: true }, + { "tag": "TextBesideIcon, mirrored", display: RadioDelegate.TextBesideIcon, mirrored: true } + ] + } + + function test_display(data) { + var control = createTemporaryObject(radioDelegate, testCase, { + text: "RadioDelegate", + display: data.display, + width: 400, + "icon.source": "qrc:/qt-project.org/imports/QtQuick/Controls/Basic/images/check.png", + "LayoutMirroring.enabled": !!data.mirrored + }) + verify(control) + compare(control.icon.source, "qrc:/qt-project.org/imports/QtQuick/Controls/Basic/images/check.png") + + var iconImage = findChild(control.contentItem, "image") + var textLabel = findChild(control.contentItem, "label") + + var availableWidth = control.availableWidth - control.indicator.width - control.spacing + var indicatorOffset = control.mirrored ? control.indicator.width + control.spacing : 0 + + switch (control.display) { + case RadioDelegate.IconOnly: + verify(iconImage) + verify(!textLabel) + compare(iconImage.x, indicatorOffset + (availableWidth - iconImage.width) / 2) + compare(iconImage.y, (control.availableHeight - iconImage.height) / 2) + break; + case RadioDelegate.TextOnly: + verify(!iconImage) + verify(textLabel) + compare(textLabel.x, control.mirrored ? control.availableWidth - textLabel.width : 0) + compare(textLabel.y, (control.availableHeight - textLabel.height) / 2) + break; + case RadioDelegate.TextUnderIcon: + verify(iconImage) + verify(textLabel) + compare(iconImage.x, indicatorOffset + (availableWidth - iconImage.width) / 2) + compare(textLabel.x, indicatorOffset + (availableWidth - textLabel.width) / 2) + verify(iconImage.y < textLabel.y) + break; + case RadioDelegate.TextBesideIcon: + verify(iconImage) + verify(textLabel) + if (control.mirrored) + verify(textLabel.x < iconImage.x) + else + verify(iconImage.x < textLabel.x) + compare(iconImage.y, (control.availableHeight - iconImage.height) / 2) + compare(textLabel.y, (control.availableHeight - textLabel.height) / 2) + break; + } + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_rangeslider.qml b/tests/auto/quickcontrols2/controls/data/tst_rangeslider.qml new file mode 100644 index 0000000000..5e4d715034 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_rangeslider.qml @@ -0,0 +1,1083 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "RangeSlider" + + Component { + id: signalSpy + SignalSpy { } + } + + Component { + id: sliderComponent + RangeSlider { + id: slider + + Component.onCompleted: { + first.handle.objectName = "firstHandle" + second.handle.objectName = "secondHandle" + } + + Text { + text: "1" + parent: slider.first.handle + anchors.centerIn: parent + } + + Text { + text: "2" + parent: slider.second.handle + anchors.centerIn: parent + } + } + } + + function test_defaults() { + var control = createTemporaryObject(sliderComponent, testCase) + verify(control) + + compare(control.stepSize, 0) + compare(control.snapMode, RangeSlider.NoSnap) + compare(control.orientation, Qt.Horizontal) + compare(control.horizontal, true) + compare(control.vertical, false) + } + + function test_values() { + var control = createTemporaryObject(sliderComponent, testCase) + verify(control) + + compare(control.first.value, 0.0) + compare(control.second.value, 1.0) + control.first.value = 0.5 + compare(control.first.value, 0.5) + control.first.value = 1.0 + compare(control.first.value, 1.0) + control.first.value = -1.0 + compare(control.first.value, 0.0) + control.first.value = 2.0 + compare(control.first.value, 1.0) + + control.first.value = 0 + compare(control.first.value, 0.0) + control.second.value = 0.5 + compare(control.second.value, 0.5) + control.first.value = 1 + compare(control.first.value, 0.5) + control.second.value = 0 + compare(control.second.value, 0.5) + } + + function test_range() { + var control = createTemporaryObject(sliderComponent, testCase, { from: 0, to: 100, "first.value": 50, "second.value": 100 }) + verify(control) + + compare(control.from, 0) + compare(control.to, 100) + compare(control.first.value, 50) + compare(control.second.value, 100) + compare(control.first.position, 0.5) + compare(control.second.position, 1.0) + + control.first.value = 1000 + compare(control.first.value, 100) + compare(control.first.position, 1.0) + + control.first.value = -1 + compare(control.first.value, 0) + compare(control.first.position, 0) + + control.from = 25 + compare(control.from, 25) + compare(control.first.value, 25) + compare(control.first.position, 0) + + control.to = 75 + compare(control.to, 75) + compare(control.second.value, 75) + compare(control.second.position, 1.0) + + control.first.value = 50 + compare(control.first.value, 50) + compare(control.first.position, 0.5) + } + + function test_setValues() { + var control = createTemporaryObject(sliderComponent, testCase) + verify(control) + + compare(control.from, 0) + compare(control.to, 1) + compare(control.first.value, 0) + compare(control.second.value, 1) + compare(control.first.position, 0.0) + compare(control.second.position, 1.0) + + control.setValues(100, 200) + compare(control.first.value, 1) + compare(control.second.value, 1) + compare(control.first.position, 1.0) + compare(control.second.position, 1.0) + + control.to = 300; + control.setValues(100, 200) + compare(control.first.value, 100) + compare(control.second.value, 200) + compare(control.first.position, 0.333333) + compare(control.second.position, 0.666666) + } + + function test_inverted() { + var control = createTemporaryObject(sliderComponent, testCase, { from: 1.0, to: -1.0 }) + verify(control) + + compare(control.from, 1.0) + compare(control.to, -1.0) + compare(control.first.value, 0.0) + compare(control.first.position, 0.5) + compare(control.second.value, 0.0); + compare(control.second.position, 0.5); + + control.first.value = 2.0 + compare(control.first.value, 1.0) + compare(control.first.position, 0.0) + compare(control.second.value, 0.0); + compare(control.second.position, 0.5); + + control.first.value = -2.0 + compare(control.first.value, 0.0) + compare(control.first.position, 0.5) + compare(control.second.value, 0.0); + compare(control.second.position, 0.5); + + control.first.value = 0.0 + compare(control.first.value, 0.0) + compare(control.first.position, 0.5) + compare(control.second.value, 0.0); + compare(control.second.position, 0.5); + } + + function test_visualPosition() { + var control = createTemporaryObject(sliderComponent, testCase) + verify(control) + + compare(control.first.value, 0.0) + compare(control.first.position, 0.0) + compare(control.first.visualPosition, 0.0) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + compare(control.second.visualPosition, 1.0) + + control.first.value = 0.25 + compare(control.first.value, 0.25) + compare(control.first.position, 0.25) + compare(control.first.visualPosition, 0.25) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + compare(control.second.visualPosition, 1.0) + + // RTL locale + control.locale = Qt.locale("ar_EG") + compare(control.first.visualPosition, 0.25) + compare(control.second.visualPosition, 1.0) + + // RTL locale + LayoutMirroring + control.LayoutMirroring.enabled = true + compare(control.first.visualPosition, 0.75) + compare(control.second.visualPosition, 0.0) + + // LTR locale + LayoutMirroring + control.locale = Qt.locale("en_US") + compare(control.first.visualPosition, 0.75) + compare(control.second.visualPosition, 0.0) + + // LTR locale + control.LayoutMirroring.enabled = false + compare(control.first.visualPosition, 0.25) + compare(control.second.visualPosition, 1.0) + + // LayoutMirroring + control.LayoutMirroring.enabled = true + compare(control.first.visualPosition, 0.75) + compare(control.second.visualPosition, 0.0) + } + + function test_orientation() { + var control = createTemporaryObject(sliderComponent, testCase) + verify(control) + + compare(control.orientation, Qt.Horizontal) + compare(control.horizontal, true) + compare(control.vertical, false) + verify(control.width > control.height) + + control.orientation = Qt.Vertical + compare(control.orientation, Qt.Vertical) + compare(control.horizontal, false) + compare(control.vertical, true) + verify(control.width < control.height) + } + + function test_mouse_data() { + return [ + { tag: "horizontal", orientation: Qt.Horizontal, live: false }, + { tag: "vertical", orientation: Qt.Vertical, live: false }, + { tag: "horizontal:live", orientation: Qt.Horizontal, live: true }, + { tag: "vertical:live", orientation: Qt.Vertical, live: true } + ] + } + + function test_mouse(data) { + var control = createTemporaryObject(sliderComponent, testCase, { orientation: data.orientation, live: data.live }) + verify(control) + + var firstPressedSpy = signalSpy.createObject(control, {target: control.first, signalName: "pressedChanged"}) + verify(firstPressedSpy.valid) + + var firstMovedSpy = signalSpy.createObject(control, {target: control.first, signalName: "moved"}) + verify(firstMovedSpy.valid) + + var secondPressedSpy = signalSpy.createObject(control, {target: control.second, signalName: "pressedChanged"}) + verify(secondPressedSpy.valid) + + var secondMovedSpy = signalSpy.createObject(control, {target: control.second, signalName: "moved"}) + verify(secondMovedSpy.valid) + + // Press and release the first handle without moving it. + mousePress(control, control.leftPadding, control.height - control.bottomPadding - 1, Qt.LeftButton) + compare(firstPressedSpy.count, 1) + compare(firstMovedSpy.count, 0) + compare(secondPressedSpy.count, 0) + compare(secondMovedSpy.count, 0) + compare(control.first.pressed, true) + compare(control.first.value, 0.0) + compare(control.first.position, 0.0) + compare(control.second.pressed, false) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + + mouseRelease(control, control.leftPadding, control.height - control.bottomPadding - 1, Qt.LeftButton) + compare(firstPressedSpy.count, 2) + compare(firstMovedSpy.count, 0) + compare(secondPressedSpy.count, 0) + compare(secondMovedSpy.count, 0) + compare(control.first.pressed, false) + compare(control.first.value, 0.0) + compare(control.first.position, 0.0) + compare(control.second.pressed, false) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + + // Press and release the second handle without moving it. + mousePress(control, control.width - control.rightPadding - 1, control.topPadding, Qt.LeftButton) + compare(firstPressedSpy.count, 2) + compare(firstMovedSpy.count, 0) + compare(secondPressedSpy.count, 1) + compare(secondMovedSpy.count, 0) + compare(control.first.pressed, false) + compare(control.first.value, 0.0) + compare(control.first.position, 0.0) + compare(control.second.pressed, true) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + + mouseRelease(control, control.width - control.rightPadding - 1, control.topPadding, Qt.LeftButton) + compare(firstPressedSpy.count, 2) + compare(firstMovedSpy.count, 0) + compare(secondPressedSpy.count, 2) + compare(secondMovedSpy.count, 0) + compare(control.first.pressed, false) + compare(control.first.value, 0.0) + compare(control.first.position, 0.0) + compare(control.second.pressed, false) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + + // Press and release on the bottom left corner of the control without moving the handle. + mousePress(control, 0, control.height - 1, Qt.LeftButton) + compare(firstPressedSpy.count, 3) + compare(firstMovedSpy.count, 0) + compare(secondPressedSpy.count, 2) + compare(secondMovedSpy.count, 0) + compare(control.first.pressed, true) + compare(control.first.value, 0.0) + compare(control.first.position, 0.0) + compare(control.second.pressed, false) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + + mouseRelease(control, 0, control.height - 1, Qt.LeftButton) + compare(firstPressedSpy.count, 4) + compare(firstMovedSpy.count, 0) + compare(secondPressedSpy.count, 2) + compare(secondMovedSpy.count, 0) + compare(control.first.pressed, false) + compare(control.first.value, 0.0) + compare(control.first.position, 0.0) + compare(control.second.pressed, false) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + + // Drag the first handle. + mousePress(control, control.leftPadding, control.height - control.bottomPadding - 1, Qt.LeftButton) + compare(firstPressedSpy.count, 5) + compare(firstMovedSpy.count, 0) + compare(secondPressedSpy.count, 2) + compare(secondMovedSpy.count, 0) + compare(control.first.pressed, true) + compare(control.first.value, 0.0) + compare(control.first.position, 0.0) + compare(control.second.pressed, false) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + + var horizontal = control.orientation === Qt.Horizontal + var toX = horizontal ? control.width * 0.5 : control.first.handle.x + var toY = horizontal ? control.first.handle.y : control.height * 0.5 + mouseMove(control, toX, toY) + compare(firstPressedSpy.count, 5) + compare(firstMovedSpy.count, 1) + compare(secondPressedSpy.count, 2) + compare(secondMovedSpy.count, 0) + compare(control.first.pressed, true) + compare(control.first.value, data.live ? 0.5 : 0.0) + compare(control.first.position, 0.5) + compare(control.first.visualPosition, 0.5) + compare(control.second.pressed, false) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + compare(control.second.visualPosition, horizontal ? 1.0 : 0.0) + + mouseRelease(control, toX, toY, Qt.LeftButton) + compare(firstPressedSpy.count, 6) + compare(firstMovedSpy.count, 1) + compare(secondPressedSpy.count, 2) + compare(secondMovedSpy.count, 0) + compare(control.first.pressed, false) + compare(control.first.value, 0.5) + compare(control.first.position, 0.5) + compare(control.first.visualPosition, 0.5) + compare(control.second.pressed, false) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + compare(control.second.visualPosition, horizontal ? 1.0 : 0.0) + } + + function test_touch_data() { + return [ + { tag: "horizontal", orientation: Qt.Horizontal, live: false }, + { tag: "vertical", orientation: Qt.Vertical, live: false }, + { tag: "horizontal:live", orientation: Qt.Horizontal, live: true }, + { tag: "vertical:live", orientation: Qt.Vertical, live: true } + ] + } + + function test_touch(data) { + var control = createTemporaryObject(sliderComponent, testCase, { orientation: data.orientation, live: data.live }) + verify(control) + + var firstPressedSpy = signalSpy.createObject(control, {target: control.first, signalName: "pressedChanged"}) + verify(firstPressedSpy.valid) + + var firstMovedSpy = signalSpy.createObject(control, {target: control.first, signalName: "moved"}) + verify(firstMovedSpy.valid) + + var secondPressedSpy = signalSpy.createObject(control, {target: control.second, signalName: "pressedChanged"}) + verify(secondPressedSpy.valid) + + var secondMovedSpy = signalSpy.createObject(control, {target: control.second, signalName: "moved"}) + verify(secondMovedSpy.valid) + + // Press and release the first handle without moving it. + var touch = touchEvent(control) + touch.press(0, control, control.width * 0.25, control.height * 0.75).commit() + compare(firstPressedSpy.count, 1) + compare(firstMovedSpy.count, 0) + compare(secondPressedSpy.count, 0) + compare(secondMovedSpy.count, 0) + compare(control.first.pressed, true) + compare(control.first.value, 0.0) + compare(control.first.position, 0.0) + compare(control.second.pressed, false) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + + touch.release(0, control, control.width * 0.25, control.height * 0.75).commit() + compare(firstPressedSpy.count, 2) + compare(firstMovedSpy.count, 0) + compare(secondPressedSpy.count, 0) + compare(secondMovedSpy.count, 0) + compare(control.first.pressed, false) + compare(control.first.value, 0.0) + compare(control.first.position, 0.0) + compare(control.second.pressed, false) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + + // Press and release the second handle without moving it. + touch.press(0, control, control.width * 0.75, control.height * 0.25).commit() + compare(firstPressedSpy.count, 2) + compare(secondPressedSpy.count, 1) + compare(control.first.pressed, false) + compare(control.first.value, 0.0) + compare(control.first.position, 0.0) + compare(control.second.pressed, true) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + + touch.release(0, control, control.width * 0.75, control.height * 0.25).commit() + compare(firstPressedSpy.count, 2) + compare(secondPressedSpy.count, 2) + compare(control.first.pressed, false) + compare(control.first.value, 0.0) + compare(control.first.position, 0.0) + compare(control.second.pressed, false) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + + // Press and release on the bottom left corner of the control without moving the handle. + touch.press(0, control, 0, control.height - 1).commit() + compare(firstPressedSpy.count, 3) + compare(secondPressedSpy.count, 2) + compare(control.first.pressed, true) + compare(control.first.value, 0.0) + compare(control.first.position, 0.0) + compare(control.second.pressed, false) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + + touch.release(0, control, 0, control.height - 1).commit() + compare(firstPressedSpy.count, 4) + compare(secondPressedSpy.count, 2) + compare(control.first.pressed, false) + compare(control.first.value, 0.0) + compare(control.first.position, 0.0) + compare(control.second.pressed, false) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + + touch.press(0, control, control.first.handle.x, control.first.handle.y).commit() + compare(firstPressedSpy.count, 5) + compare(secondPressedSpy.count, 2) + compare(control.first.pressed, true) + compare(control.first.value, 0.0) + compare(control.first.position, 0.0) + compare(control.second.pressed, false) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + + // Drag the first handle. + var horizontal = control.orientation === Qt.Horizontal + var toX = horizontal ? control.width * 0.5 : control.first.handle.x + var toY = horizontal ? control.first.handle.y : control.height * 0.5 + touch.move(0, control, toX, toY).commit() + compare(firstPressedSpy.count, 5) + compare(secondPressedSpy.count, 2) + compare(control.first.pressed, true) + compare(control.first.value, data.live ? 0.5 : 0.0) + compare(control.first.position, 0.5) + compare(control.first.visualPosition, 0.5) + compare(control.second.pressed, false) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + compare(control.second.visualPosition, horizontal ? 1.0 : 0.0) + + touch.release(0, control, toX, toY).commit() + compare(firstPressedSpy.count, 6) + compare(secondPressedSpy.count, 2) + compare(control.first.pressed, false) + compare(control.first.value, 0.5) + compare(control.first.position, 0.5) + compare(control.first.visualPosition, 0.5) + compare(control.second.pressed, false) + compare(control.second.value, 1.0) + compare(control.second.position, 1.0) + compare(control.second.visualPosition, horizontal ? 1.0 : 0.0) + } + + function test_multiTouch() { + var control1 = createTemporaryObject(sliderComponent, testCase) + verify(control1) + + // press and move the first handle of the first slider + var touch = touchEvent(control1) + touch.press(0, control1, 0, 0).commit().move(0, control1, control1.width / 2, control1.height / 2).commit() + compare(control1.first.pressed, true) + compare(control1.first.position, 0.5) + compare(control1.second.pressed, false) + compare(control1.second.position, 1.0) + + // press and move the second handle of the first slider + touch.stationary(0).press(1, control1, control1.width - 1, control1.height - 1).commit() + touch.stationary(0).move(1, control1, control1.width / 2, control1.height / 2).commit() + compare(control1.first.pressed, true) + compare(control1.first.position, 0.5) + compare(control1.second.pressed, true) + compare(control1.second.position, 0.5) + + var control2 = createTemporaryObject(sliderComponent, testCase, {y: control1.height}) + verify(control2) + + // press and move the first handle of the second slider + touch.stationary(0).stationary(1).press(2, control2, 0, 0).commit() + touch.stationary(0).stationary(1).move(2, control2, control2.width / 2, control2.height / 2).commit() + compare(control1.first.pressed, true) + compare(control1.first.position, 0.5) + compare(control1.second.pressed, true) + compare(control1.second.position, 0.5) + compare(control2.first.pressed, true) + compare(control2.first.position, 0.5) + compare(control2.second.pressed, false) + compare(control2.second.position, 1.0) + + // press and move the second handle of the second slider + touch.stationary(0).stationary(1).stationary(2).press(3, control2, control2.width - 1, control2.height - 1).commit() + touch.stationary(0).stationary(1).stationary(2).move(3, control2, control2.width / 2, control2.height / 2).commit() + compare(control1.first.pressed, true) + compare(control1.first.position, 0.5) + compare(control1.second.pressed, true) + compare(control1.second.position, 0.5) + compare(control2.first.pressed, true) + compare(control2.first.position, 0.5) + compare(control2.second.pressed, true) + compare(control2.second.position, 0.5) + + // release the both handles of the both sliders + touch.release(0, control1).release(1, control1).release(2, control2).release(3, control2).commit() + compare(control1.first.pressed, false) + compare(control1.first.position, 0.5) + compare(control1.second.pressed, false) + compare(control1.second.position, 0.5) + compare(control2.first.pressed, false) + compare(control2.first.position, 0.5) + compare(control2.second.pressed, false) + compare(control2.second.position, 0.5) + } + + function test_overlappingHandles() { + var control = createTemporaryObject(sliderComponent, testCase) + verify(control) + + // By default, we force the second handle to be after the first in + // terms of stacking order *and* z value. + compare(control.second.handle.z, 1) + compare(control.first.handle.z, 0) + control.first.value = 0 + control.second.value = 0 + + // Both are at the same position, so it doesn't matter whose coordinates we use. + mousePress(control, control.first.handle.x, control.first.handle.y, Qt.LeftButton) + verify(control.second.pressed) + compare(control.second.handle.z, 1) + compare(control.first.handle.z, 0) + + // Move the second handle out of the way. + mouseMove(control, control.width, control.first.handle.y) + mouseRelease(control, control.width, control.first.handle.y, Qt.LeftButton) + verify(!control.second.pressed) + compare(control.second.value, 1.0) + compare(control.second.handle.z, 1) + compare(control.first.handle.z, 0) + + // Move the first handle on top of the second. + mousePress(control, control.first.handle.x, control.first.handle.y, Qt.LeftButton) + verify(control.first.pressed) + compare(control.first.handle.z, 1) + compare(control.second.handle.z, 0) + + mouseMove(control, control.width, control.first.handle.y) + mouseRelease(control, control.width, control.first.handle.y, Qt.LeftButton) + verify(!control.first.pressed) + compare(control.first.handle.z, 1) + compare(control.second.handle.z, 0) + + // The most recently pressed handle (the first) should have the higher z value. + mousePress(control, control.first.handle.x, control.first.handle.y, Qt.LeftButton) + verify(control.first.pressed) + compare(control.first.handle.z, 1) + compare(control.second.handle.z, 0) + + mouseRelease(control, control.first.handle.x, control.first.handle.y, Qt.LeftButton) + verify(!control.first.pressed) + compare(control.first.handle.z, 1) + compare(control.second.handle.z, 0) + } + + function test_keys_data() { + return [ + { tag: "horizontal", orientation: Qt.Horizontal, decrease: Qt.Key_Left, increase: Qt.Key_Right }, + { tag: "vertical", orientation: Qt.Vertical, decrease: Qt.Key_Down, increase: Qt.Key_Up } + ] + } + + function test_keys(data) { + var control = createTemporaryObject(sliderComponent, testCase, { orientation: data.orientation }) + verify(control) + + var pressedCount = 0 + + var firstPressedSpy = signalSpy.createObject(control, {target: control.first, signalName: "pressedChanged"}) + verify(firstPressedSpy.valid) + + control.first.handle.forceActiveFocus() + verify(control.first.handle.activeFocus) + + control.first.value = 0.5 + + for (var d1 = 1; d1 <= 10; ++d1) { + keyPress(data.decrease) + compare(control.first.pressed, true) + compare(firstPressedSpy.count, ++pressedCount) + + compare(control.first.value, Math.max(0.0, 0.5 - d1 * 0.1)) + compare(control.first.value, control.first.position) + + keyRelease(data.decrease) + compare(control.first.pressed, false) + compare(firstPressedSpy.count, ++pressedCount) + } + + for (var i1 = 1; i1 <= 20; ++i1) { + keyPress(data.increase) + compare(control.first.pressed, true) + compare(firstPressedSpy.count, ++pressedCount) + + compare(control.first.value, Math.min(1.0, 0.0 + i1 * 0.1)) + compare(control.first.value, control.first.position) + + keyRelease(data.increase) + compare(control.first.pressed, false) + compare(firstPressedSpy.count, ++pressedCount) + } + + control.first.value = 0; + control.stepSize = 0.25 + + pressedCount = 0; + var secondPressedSpy = signalSpy.createObject(control, {target: control.second, signalName: "pressedChanged"}) + verify(secondPressedSpy.valid) + + control.second.handle.forceActiveFocus() + verify(control.second.handle.activeFocus) + + for (var d2 = 1; d2 <= 10; ++d2) { + keyPress(data.decrease) + compare(control.second.pressed, true) + compare(secondPressedSpy.count, ++pressedCount) + + compare(control.second.value, Math.max(0.0, 1.0 - d2 * 0.25)) + compare(control.second.value, control.second.position) + + keyRelease(data.decrease) + compare(control.second.pressed, false) + compare(secondPressedSpy.count, ++pressedCount) + } + + for (var i2 = 1; i2 <= 10; ++i2) { + keyPress(data.increase) + compare(control.second.pressed, true) + compare(secondPressedSpy.count, ++pressedCount) + + compare(control.second.value, Math.min(1.0, 0.0 + i2 * 0.25)) + compare(control.second.value, control.second.position) + + keyRelease(data.increase) + compare(control.second.pressed, false) + compare(secondPressedSpy.count, ++pressedCount) + } + } + + function test_padding() { + // test with "unbalanced" paddings (left padding != right padding) to ensure + // that the slider position calculation is done taking padding into account + // ==> the position is _not_ 0.5 in the middle of the control + var control = createTemporaryObject(sliderComponent, testCase, { leftPadding: 10, rightPadding: 20, live: false }) + verify(control) + + var firstPressedSpy = signalSpy.createObject(control, {target: control.first, signalName: "pressedChanged"}) + verify(firstPressedSpy.valid) + + mousePress(control, control.first.handle.x, control.first.handle.y, Qt.LeftButton) + compare(firstPressedSpy.count, 1) + compare(control.first.pressed, true) + compare(control.first.value, 0.0) + compare(control.first.position, 0.0) + compare(control.first.visualPosition, 0.0) + + mouseMove(control, control.leftPadding + control.availableWidth * 0.5, control.height * 0.5, 0) + compare(firstPressedSpy.count, 1) + compare(control.first.pressed, true) + compare(control.first.value, 0.0) + compare(control.first.position, 0.5) + compare(control.first.visualPosition, 0.5) + + mouseMove(control, control.width * 0.5, control.height * 0.5, 0) + compare(firstPressedSpy.count, 1) + compare(control.first.pressed, true) + compare(control.first.value, 0.0) + verify(control.first.position > 0.5) + verify(control.first.visualPosition > 0.5) + + mouseRelease(control, control.leftPadding + control.availableWidth * 0.5, control.height * 0.5, Qt.LeftButton) + compare(firstPressedSpy.count, 2) + compare(control.first.pressed, false) + compare(control.first.value, 0.5) + compare(control.first.position, 0.5) + compare(control.first.visualPosition, 0.5) + + // RTL + control.first.value = 0 + control.locale = Qt.locale("ar_EG") + + mousePress(control, control.first.handle.x, control.first.handle.y, Qt.LeftButton) + compare(firstPressedSpy.count, 3) + compare(control.first.pressed, true) + compare(control.first.value, 0.0) + compare(control.first.position, 0.0) + compare(control.first.visualPosition, 0.0) + + mouseMove(control, control.leftPadding + control.availableWidth * 0.5, control.height * 0.5, 0) + compare(firstPressedSpy.count, 3) + compare(control.first.pressed, true) + compare(control.first.value, 0.0) + compare(control.first.position, 0.5) + compare(control.first.visualPosition, 0.5) + + mouseMove(control, control.width * 0.5, control.height * 0.5, 0) + compare(firstPressedSpy.count, 3) + compare(control.first.pressed, true) + compare(control.first.value, 0.0) + verify(control.first.position > 0.5) + verify(control.first.visualPosition > 0.5) + + mouseRelease(control, control.leftPadding + control.availableWidth * 0.5, control.height * 0.5, Qt.LeftButton) + compare(firstPressedSpy.count, 4) + compare(control.first.pressed, false) + compare(control.first.value, 0.5) + compare(control.first.position, 0.5) + compare(control.first.visualPosition, 0.5) + } + + function test_snapMode_data(immediate) { + return [ + { tag: "NoSnap", snapMode: RangeSlider.NoSnap, from: 0, to: 2, values: [0, 0, 0.25], positions: [0, 0.1, 0.1] }, + { tag: "SnapAlways (0..2)", snapMode: RangeSlider.SnapAlways, from: 0, to: 2, values: [0.0, 0.0, 0.2], positions: [0.0, 0.1, 0.1] }, + { tag: "SnapAlways (1..3)", snapMode: RangeSlider.SnapAlways, from: 1, to: 3, values: [1.0, 1.0, 1.2], positions: [0.0, 0.1, 0.1] }, + { tag: "SnapAlways (-1..1)", snapMode: RangeSlider.SnapAlways, from: -1, to: 1, values: [0.0, 0.0, -0.8], positions: [immediate ? 0.0 : 0.5, 0.1, 0.1] }, + { tag: "SnapAlways (1..-1)", snapMode: RangeSlider.SnapAlways, from: 1, to: -1, values: [0.0, 0.0, 0.8], positions: [immediate ? 0.0 : 0.5, 0.1, 0.1] }, + { tag: "SnapOnRelease (0..2)", snapMode: RangeSlider.SnapOnRelease, from: 0, to: 2, values: [0.0, 0.0, 0.2], positions: [0.0, 0.1, 0.1] }, + { tag: "SnapOnRelease (1..3)", snapMode: RangeSlider.SnapOnRelease, from: 1, to: 3, values: [1.0, 1.0, 1.2], positions: [0.0, 0.1, 0.1] }, + { tag: "SnapOnRelease (-1..1)", snapMode: RangeSlider.SnapOnRelease, from: -1, to: 1, values: [0.0, 0.0, -0.8], positions: [immediate ? 0.0 : 0.5, 0.1, 0.1] }, + { tag: "SnapOnRelease (1..-1)", snapMode: RangeSlider.SnapOnRelease, from: 1, to: -1, values: [0.0, 0.0, 0.8], positions: [immediate ? 0.0 : 0.5, 0.1, 0.1] } + ] + } + + function test_snapMode_mouse_data() { + return test_snapMode_data(true) + } + + function test_snapMode_mouse(data) { + var control = createTemporaryObject(sliderComponent, testCase, {snapMode: data.snapMode, from: data.from, to: data.to, stepSize: 0.2, live: false}) + verify(control) + + control.first.value = 0 + control.second.value = data.to + + var fuzz = 0.05 + + mousePress(control, control.leftPadding) + compare(control.first.pressed, true) + compare(control.first.value, data.values[0]) + compare(control.first.position, data.positions[0]) + + mouseMove(control, control.leftPadding + 0.15 * (control.availableWidth + control.first.handle.width / 2)) + compare(control.first.pressed, true) + fuzzyCompare(control.first.value, data.values[1], fuzz) + fuzzyCompare(control.first.position, data.positions[1], fuzz) + + mouseRelease(control, control.leftPadding + 0.15 * (control.availableWidth + control.first.handle.width / 2)) + compare(control.first.pressed, false) + fuzzyCompare(control.first.value, data.values[2], fuzz) + fuzzyCompare(control.first.position, data.positions[2], fuzz) + } + + function test_snapMode_touch_data() { + return test_snapMode_data(false) + } + + function test_snapMode_touch(data) { + var control = createTemporaryObject(sliderComponent, testCase, {snapMode: data.snapMode, from: data.from, to: data.to, stepSize: 0.2, live: false}) + verify(control) + + control.first.value = 0 + control.second.value = data.to + + var fuzz = 0.05 + + var touch = touchEvent(control) + touch.press(0, control, control.first.handle.x, control.first.handle.y).commit() + compare(control.first.pressed, true) + compare(control.first.value, data.values[0]) + compare(control.first.position, data.positions[0]) + + touch.move(0, control, control.leftPadding + 0.15 * (control.availableWidth + control.first.handle.width / 2)).commit() + compare(control.first.pressed, true) + fuzzyCompare(control.first.value, data.values[1], fuzz) + fuzzyCompare(control.first.position, data.positions[1], fuzz) + + touch.release(0, control, control.leftPadding + 0.15 * (control.availableWidth + control.first.handle.width / 2)).commit() + compare(control.first.pressed, false) + fuzzyCompare(control.first.value, data.values[2], fuzz) + fuzzyCompare(control.first.position, data.positions[2], fuzz) + } + + function test_focus() { + var control = createTemporaryObject(sliderComponent, testCase) + verify(control) + + compare(control.activeFocus, false) + + // focus is forwarded to the first handle + control.forceActiveFocus() + compare(control.activeFocus, true) + compare(control.first.handle.activeFocus, true) + compare(control.second.handle.activeFocus, false) + + // move focus to the second handle + control.second.handle.forceActiveFocus() + compare(control.activeFocus, true) + compare(control.first.handle.activeFocus, false) + compare(control.second.handle.activeFocus, true) + + // clear focus + control.focus = false + compare(control.activeFocus, false) + compare(control.first.handle.activeFocus, false) + compare(control.second.handle.activeFocus, false) + + // focus is forwarded to the second handle (where it previously was in the focus scope) + control.forceActiveFocus() + compare(control.activeFocus, true) + compare(control.first.handle.activeFocus, false) + compare(control.second.handle.activeFocus, true) + } + + function test_hover_data() { + return [ + { tag: "first:true", node: "first", hoverEnabled: true }, + { tag: "first:false", node: "first", hoverEnabled: false }, + { tag: "second:true", node: "second", hoverEnabled: true }, + { tag: "second:false", node: "second", hoverEnabled: false } + ] + } + + function test_hover(data) { + var control = createTemporaryObject(sliderComponent, testCase, {hoverEnabled: data.hoverEnabled}) + verify(control) + + var node = control[data.node] + compare(control.hovered, false) + compare(node.hovered, false) + + mouseMove(control, node.handle.x + node.handle.width / 2, node.handle.y + node.handle.height / 2) + compare(control.hovered, data.hoverEnabled) + compare(node.hovered, data.hoverEnabled && node.handle.enabled) + + mouseMove(control, node.handle.x - 1, node.handle.y - 1) + compare(node.hovered, false) + } + + function test_nullHandles() { + var control = createTemporaryObject(sliderComponent, testCase, {"second.value": 1}) + verify(control) + + verify(control.first.handle) + verify(control.second.handle) + + control.first.handle = null + control.second.handle = null + + mousePress(control, control.leftPadding, control.height / 2) + verify(control.first.pressed, true) + compare(control.second.pressed, false) + + mouseRelease(control, control.leftPadding, control.height / 2) + compare(control.first.pressed, false) + compare(control.second.pressed, false) + + mousePress(control, control.width - control.rightPadding - 1, control.height / 2) + compare(control.first.pressed, false) + compare(control.second.pressed, true) + + mouseRelease(control, control.width - control.rightPadding - 1, control.height / 2) + compare(control.first.pressed, false) + compare(control.second.pressed, false) + } + + function test_touchDragThreshold_data() { + var d1 = 3; var d2 = 7; + return [ + { tag: "horizontal", orientation: Qt.Horizontal, dx1: d1, dy1: 0, dx2: d2, dy2: 0 }, + { tag: "vertical", orientation: Qt.Vertical, dx1: 0, dy1: -d1, dx2: 0, dy2: -d2 }, + { tag: "horizontal2", orientation: Qt.Horizontal, dx1: -d1, dy1: 0, dx2: -d2, dy2: 0 }, + { tag: "vertical2", orientation: Qt.Vertical, dx1: 0, dy1: d1, dx2: 0, dy2: d2 }, + ] + } + + function test_touchDragThreshold(data) { + var control = createTemporaryObject(sliderComponent, testCase, {touchDragThreshold: 10, live: true, orientation: data.orientation, "first.value": 0, "second.value": 1}) + verify(control) + compare(control.touchDragThreshold, 10) + + var valueChangedCount = 0 + var valueChangedSpy = signalSpy.createObject(control, {target: control, signalName: "touchDragThresholdChanged"}) + verify(valueChangedSpy.valid) + + control.touchDragThreshold = undefined + compare(control.touchDragThreshold, -1) // reset to -1 + compare(valueChangedSpy.count, ++valueChangedCount) + + var t = 5 + control.touchDragThreshold = t + compare(control.touchDragThreshold, t) + compare(valueChangedSpy.count, ++valueChangedCount) + + control.touchDragThreshold = t + compare(control.touchDragThreshold, t) + compare(valueChangedSpy.count, valueChangedCount) + + var pressedCount = 0 + var pressedCount2 = 0 + var visualPositionCount = 0 + var visualPositionCount2 = 0 + + var pressedSpy = signalSpy.createObject(control, {target: control.first, signalName: "pressedChanged"}) + verify(pressedSpy.valid) + var pressedSpy2 = signalSpy.createObject(control, {target: control.second, signalName: "pressedChanged"}) + verify(pressedSpy2.valid) + + var visualPositionSpy = signalSpy.createObject(control, {target: control.first, signalName: "visualPositionChanged"}) + verify(visualPositionSpy.valid) + var visualPositionSpy2 = signalSpy.createObject(control, {target: control.second, signalName: "visualPositionChanged"}) + verify(visualPositionSpy2.valid) + + var touch = touchEvent(control) + control.first.value = 0.4 + control.second.value = 1 + var x0 = control.first.handle.x + control.first.handle.width * 0.5 + var y0 = control.first.handle.y + control.first.handle.height * 0.5 + touch.press(0, control, x0, y0).commit() + compare(pressedSpy.count, ++pressedCount) + compare(control.first.pressed, true) + compare(visualPositionSpy.count, ++visualPositionCount) + + touch.move(0, control, x0 + data.dx1, y0 + data.dy1).commit() + compare(pressedSpy.count, pressedCount) + compare(control.first.pressed, true) + compare(visualPositionSpy.count, visualPositionCount) + + touch.move(0, control, x0 + data.dx2, y0 + data.dy2).commit() + compare(pressedSpy.count, pressedCount) + compare(control.first.pressed, true) + compare(visualPositionSpy.count, ++visualPositionCount) + + touch.release(0, control, x0 + data.dx2, y0 + data.dy2).commit() + + control.first.value = 0 + control.second.value = 0.6 + x0 = control.second.handle.x + control.second.handle.width * 0.5 + y0 = control.second.handle.y + control.second.handle.height * 0.5 + touch.press(0, control, x0, y0).commit() + compare(pressedSpy2.count, ++pressedCount2) + compare(control.second.pressed, true) + compare(visualPositionSpy2.count, ++visualPositionCount2) + + touch.move(0, control, x0 + data.dx1, y0 + data.dy1).commit() + compare(pressedSpy2.count, pressedCount2) + compare(control.second.pressed, true) + compare(visualPositionSpy2.count, visualPositionCount2) + + touch.move(0, control, x0 + data.dx2, y0 + data.dy2).commit() + compare(pressedSpy2.count, pressedCount2) + compare(control.second.pressed, true) + compare(visualPositionSpy2.count, ++visualPositionCount2) + touch.release(0, control, x0 + data.dx2, y0 + data.dy2).commit() + } + + function test_valueAt_data() { + return [ + { tag: "0.0..1.0", properties: { from: 0.0, to: 1.0 }, values: [0.0, 0.2, 0.5, 1.0] }, + { tag: "0..100", properties: { from: 0, to: 100 }, values: [0, 20, 50, 100] }, + { tag: "100..-100", properties: { from: 100, to: -100 }, values: [100, 60, 0, -100] }, + { tag: "-7..7", properties: { from: -7, to: 7, stepSize: 1.0 }, values: [-7.0, -4.0, 0.0, 7.0] }, + { tag: "-3..7", properties: { from: -3, to: 7, stepSize: 5.0 }, values: [-3.0, -3.0, 2.0, 7.0] }, + ] + } + + function test_valueAt(data) { + var control = createTemporaryObject(sliderComponent, testCase, data.properties) + verify(control) + + compare(control.valueAt(0.0), data.values[0]) + compare(control.valueAt(0.2), data.values[1]) + compare(control.valueAt(0.5), data.values[2]) + compare(control.valueAt(1.0), data.values[3]) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_roundbutton.qml b/tests/auto/quickcontrols2/controls/data/tst_roundbutton.qml new file mode 100644 index 0000000000..d7a8832ca2 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_roundbutton.qml @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 200 + height: 200 + visible: true + when: windowShown + name: "RoundButton" + + Component { + id: roundButton + RoundButton { } + } + + function test_radius() { + var control = createTemporaryObject(roundButton, testCase); + verify(control); + + var implicitRadius = control.radius; + compare(implicitRadius, Math.min(control.width, control.height) / 2); + + control.radius = 10; + compare(control.radius, 10); + + control.radius = undefined; + compare(control.radius, implicitRadius); + + control.width = -1; + compare(control.radius, 0); + + control.width = 10; + compare(control.radius, 5); + } + + function test_spacing() { + var control = createTemporaryObject(roundButton, testCase, { text: "Some long, long, long text" }) + verify(control) + verify(control.contentItem.implicitWidth + control.leftPadding + control.rightPadding > control.background.implicitWidth) + + var textLabel = findChild(control.contentItem, "label") + verify(textLabel) + + // The implicitWidth of the IconLabel that all buttons use as their contentItem + // should be equal to the implicitWidth of the Text while no icon is set. + compare(control.contentItem.implicitWidth, textLabel.implicitWidth) + + // That means that spacing shouldn't affect it. + control.spacing += 100 + compare(control.contentItem.implicitWidth, textLabel.implicitWidth) + + // The implicitWidth of the Button itself should, therefore, also never include spacing while no icon is set. + compare(control.implicitWidth, textLabel.implicitWidth + control.leftPadding + control.rightPadding) + } + + function test_display_data() { + return [ + { "tag": "IconOnly", display: RoundButton.IconOnly }, + { "tag": "TextOnly", display: RoundButton.TextOnly }, + { "tag": "TextUnderIcon", display: RoundButton.TextUnderIcon }, + { "tag": "TextBesideIcon", display: RoundButton.TextBesideIcon }, + { "tag": "IconOnly, mirrored", display: RoundButton.IconOnly, mirrored: true }, + { "tag": "TextOnly, mirrored", display: RoundButton.TextOnly, mirrored: true }, + { "tag": "TextUnderIcon, mirrored", display: RoundButton.TextUnderIcon, mirrored: true }, + { "tag": "TextBesideIcon, mirrored", display: RoundButton.TextBesideIcon, mirrored: true } + ] + } + + function test_display(data) { + var control = createTemporaryObject(roundButton, testCase, { + text: "RoundButton", + display: data.display, + "icon.source": "qrc:/qt-project.org/imports/QtQuick/Controls/Basic/images/check.png", + "LayoutMirroring.enabled": !!data.mirrored + }) + verify(control) + compare(control.icon.source, "qrc:/qt-project.org/imports/QtQuick/Controls/Basic/images/check.png") + + var iconImage = findChild(control.contentItem, "image") + var textLabel = findChild(control.contentItem, "label") + + switch (control.display) { + case RoundButton.IconOnly: + verify(iconImage) + verify(!textLabel) + compare(iconImage.x, (control.availableWidth - iconImage.width) / 2) + compare(iconImage.y, (control.availableHeight - iconImage.height) / 2) + break; + case RoundButton.TextOnly: + verify(!iconImage) + verify(textLabel) + compare(textLabel.x, (control.availableWidth - textLabel.width) / 2) + compare(textLabel.y, (control.availableHeight - textLabel.height) / 2) + break; + case RoundButton.TextUnderIcon: + verify(iconImage) + verify(textLabel) + compare(iconImage.x, (control.availableWidth - iconImage.width) / 2) + compare(textLabel.x, (control.availableWidth - textLabel.width) / 2) + verify(iconImage.y < textLabel.y) + break; + case RoundButton.TextBesideIcon: + verify(iconImage) + verify(textLabel) + if (control.mirrored) + verify(textLabel.x < iconImage.x) + else + verify(iconImage.x < textLabel.x) + compare(iconImage.y, (control.availableHeight - iconImage.height) / 2) + compare(textLabel.y, (control.availableHeight - textLabel.height) / 2) + break; + } + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_scrollbar.qml b/tests/auto/quickcontrols2/controls/data/tst_scrollbar.qml new file mode 100644 index 0000000000..f15983a943 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_scrollbar.qml @@ -0,0 +1,918 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "ScrollBar" + + Component { + id: signalSpy + SignalSpy { } + } + + Component { + id: scrollBar + ScrollBar { + padding: 0 + minimumSize: 0 + } + } + + Component { + id: scrollBarWithDefaultPadding + ScrollBar { + minimumSize: 0 + } + } + + Component { + id: flickable + Flickable { + width: 100 + height: 100 + contentWidth: 200 + contentHeight: 200 + boundsBehavior: Flickable.StopAtBounds + flickableDirection: Flickable.HorizontalAndVerticalFlick + } + } + + function test_attach() { + var container = createTemporaryObject(flickable, testCase) + verify(container) + waitForRendering(container) + + var vertical = scrollBar.createObject() + verify(!vertical.parent) + compare(vertical.size, 0.0) + compare(vertical.position, 0.0) + compare(vertical.active, false) + compare(vertical.orientation, Qt.Vertical) + compare(vertical.x, 0) + compare(vertical.y, 0) + verify(vertical.width > 0) + verify(vertical.height > 0) + + container.ScrollBar.vertical = vertical + compare(vertical.parent, container) + compare(vertical.orientation, Qt.Vertical) + compare(vertical.size, container.visibleArea.heightRatio) + compare(vertical.position, container.visibleArea.yPosition) + compare(vertical.x, container.width - vertical.width) + compare(vertical.y, 0) + verify(vertical.width > 0) + compare(vertical.height, container.height) + // vertical scroll bar follows flickable's width + container.width += 10 + compare(vertical.x, container.width - vertical.width) + vertical.implicitWidth -= 2 + compare(vertical.x, container.width - vertical.width) + // ...unless explicitly positioned + vertical.x = 123 + container.width += 10 + compare(vertical.x, 123) + + var horizontal = createTemporaryObject(scrollBar, null) + verify(!horizontal.parent) + compare(horizontal.size, 0.0) + compare(horizontal.position, 0.0) + compare(horizontal.active, false) + compare(horizontal.orientation, Qt.Vertical) + compare(horizontal.x, 0) + compare(horizontal.y, 0) + verify(horizontal.width > 0) + verify(horizontal.height > 0) + + container.ScrollBar.horizontal = horizontal + compare(horizontal.parent, container) + compare(horizontal.orientation, Qt.Horizontal) + compare(horizontal.size, container.visibleArea.widthRatio) + compare(horizontal.position, container.visibleArea.xPosition) + compare(horizontal.x, 0) + compare(horizontal.y, container.height - horizontal.height) + compare(horizontal.width, container.width) + verify(horizontal.height > 0) + // horizontal scroll bar follows flickable's height + container.height += 10 + compare(horizontal.y, container.height - horizontal.height) + horizontal.implicitHeight -= 2 + compare(horizontal.y, container.height - horizontal.height) + // ...unless explicitly positioned + horizontal.y = 123 + container.height += 10 + compare(horizontal.y, 123) + + var velocity = container.maximumFlickVelocity + + compare(container.flicking, false) + container.flick(-velocity, -velocity) + compare(container.flicking, true) + tryCompare(container, "flicking", false) + + compare(vertical.size, container.visibleArea.heightRatio) + compare(vertical.position, container.visibleArea.yPosition) + compare(horizontal.size, container.visibleArea.widthRatio) + compare(horizontal.position, container.visibleArea.xPosition) + + compare(container.flicking, false) + container.flick(velocity, velocity) + compare(container.flicking, true) + tryCompare(container, "flicking", false) + + compare(vertical.size, container.visibleArea.heightRatio) + compare(vertical.position, container.visibleArea.yPosition) + compare(horizontal.size, container.visibleArea.widthRatio) + compare(horizontal.position, container.visibleArea.xPosition) + + var oldY = vertical.y + var oldHeight = vertical.height + vertical.parent = testCase + vertical.y -= 10 + container.height += 10 + compare(vertical.y, oldY - 10) + compare(vertical.height, oldHeight) + + var oldX = horizontal.x + var oldWidth = horizontal.width + horizontal.parent = testCase + horizontal.x -= 10 + container.width += 10 + compare(horizontal.x, oldX - 10) + compare(horizontal.width, oldWidth) + } + + function test_attachTwice() { + let container = createTemporaryObject(flickable, testCase) + verify(container) + waitForRendering(container) + + container.ScrollBar.vertical = scrollBar.createObject(container, { objectName: "oldVerticalScrollBar" }) + verify(container.ScrollBar.vertical) + let oldVerticalScrollBar = findChild(container, "oldVerticalScrollBar") + verify(oldVerticalScrollBar) + verify(oldVerticalScrollBar.visible) + + container.ScrollBar.horizontal = scrollBar.createObject(container, { objectName: "oldHorizontalScrollBar" }) + verify(container.ScrollBar.horizontal) + let oldHorizontalScrollBar = findChild(container, "oldHorizontalScrollBar") + verify(oldHorizontalScrollBar) + verify(oldHorizontalScrollBar.visible) + + container.ScrollBar.vertical = scrollBar.createObject(container, { objectName: "newVerticalScrollBar" }) + let newVerticalScrollBar = findChild(container, "newVerticalScrollBar") + verify(newVerticalScrollBar) + verify(newVerticalScrollBar.visible) + verify(!oldVerticalScrollBar.visible) + + container.ScrollBar.horizontal = scrollBar.createObject(container, { objectName: "newHorizontalScrollBar" }) + let newHorizontalScrollBar = findChild(container, "newHorizontalScrollBar") + verify(newHorizontalScrollBar) + verify(newHorizontalScrollBar.visible) + verify(!oldHorizontalScrollBar.visible) + } + + function test_mouse_data() { + return [ + { tag: "horizontal", properties: { visible: true, orientation: Qt.Horizontal, width: testCase.width } }, + { tag: "vertical", properties: { visible: true, orientation: Qt.Vertical, height: testCase.height } } + ] + } + + function test_mouse(data) { + var control = createTemporaryObject(scrollBarWithDefaultPadding, testCase, data.properties) + verify(control) + // ### we should consider calling updateGeometry() from QQuickStyleItem::componentComplete() + // to avoid the wait here... + if (control.__decreaseVisual.indicator !== null) + waitForItemPolished(control.__decreaseVisual.indicator) + + var grooveRange = { + start: { + x: control.orientation == Qt.Horizontal ? control.leftPadding : 0, + y: control.orientation == Qt.Vertical ? control.topPadding : 0 + }, + end: { + x: control.orientation == Qt.Horizontal ? control.width - control.rightPadding : 0, + y: control.orientation == Qt.Vertical ? control.height - control.bottomPadding: 0 + }, + width : control.width - control.leftPadding - control.rightPadding, + height: control.height - control.topPadding - control.bottomPadding + } + + var pressedSpy = signalSpy.createObject(control, {target: control, signalName: "pressedChanged"}) + verify(pressedSpy.valid) + + mousePress(control, grooveRange.start.x, grooveRange.start.y, Qt.LeftButton) + compare(pressedSpy.count, 1) + compare(control.pressed, true) + compare(control.position, 0.0) + + mouseMove(control, -control.width, -control.height, 0) + compare(pressedSpy.count, 1) + compare(control.pressed, true) + compare(control.position, 0.0) + + mouseMove(control, control.width * 0.5, control.height * 0.5, 0) + compare(pressedSpy.count, 1) + compare(control.pressed, true) + verify(control.position, 0.5) + + mouseRelease(control, control.width * 0.5, control.height * 0.5, Qt.LeftButton) + compare(pressedSpy.count, 2) + compare(control.pressed, false) + compare(control.position, 0.5) + + mousePress(control, control.width - 1, control.height - 1, Qt.LeftButton) + compare(pressedSpy.count, 3) + compare(control.pressed, true) + // We can't click on right and bottom edge, so click to (width-1, height-1), and move mouse to (width,height) + mouseMove(control, control.width, control.height, 0) + compare(control.position, 1.0) + + mouseMove(control, control.width * 2, control.height * 2, 0) + compare(pressedSpy.count, 3) + compare(control.pressed, true) + compare(control.position, 1.0) + + mouseMove(control, grooveRange.start.x + grooveRange.width * 0.75, grooveRange.start.y + grooveRange.height * 0.75, 0) + compare(pressedSpy.count, 3) + compare(control.pressed, true) + fuzzyCompare(control.position, 0.75, 0.01) + + mouseRelease(control, grooveRange.start.x + grooveRange.width * 0.25, grooveRange.start.y + grooveRange.height * 0.25, Qt.LeftButton) + compare(pressedSpy.count, 4) + compare(control.pressed, false) + fuzzyCompare(control.position, 0.25, 0.01) + + if (control.__decreaseVisual.indicator !== null) { + var p = control.__decreaseVisual.indicator.mapToItem(control, Qt.point(0, 0)) + mousePress(control, p.x, p.y, Qt.LeftButton) + compare(pressedSpy.count, 4) + compare(control.pressed, false) + compare(control.__decreaseVisual.pressed, true) + fuzzyCompare(control.position, 0.15, 0.01) + mouseRelease(control.__decreaseVisual.indicator, 0, 0, Qt.LeftButton) + compare(control.__decreaseVisual.pressed, false) + + p = control.__increaseVisual.indicator.mapToItem(control, Qt.point(0, 0)) + mousePress(control, p.x, p.y, Qt.LeftButton) + compare(pressedSpy.count, 4) + compare(control.pressed, false) + compare(control.__increaseVisual.pressed, true) + fuzzyCompare(control.position, 0.25, 0.01) + mouseRelease(control.__increaseVisual.indicator, 0, 0, Qt.LeftButton) + compare(control.__increaseVisual.pressed, false) + } + } + + function test_touch_data() { + return [ + { tag: "horizontal", properties: { visible: true, orientation: Qt.Horizontal, width: testCase.width } }, + { tag: "vertical", properties: { visible: true, orientation: Qt.Vertical, height: testCase.height } } + ] + } + + function test_touch(data) { + var control = createTemporaryObject(scrollBar, testCase, data.properties) + verify(control) + + var pressedSpy = signalSpy.createObject(control, {target: control, signalName: "pressedChanged"}) + verify(pressedSpy.valid) + + var touch = touchEvent(control) + + touch.press(0, control, 0, 0).commit() + compare(pressedSpy.count, 1) + compare(control.pressed, true) + compare(control.position, 0.0) + + touch.move(0, control, -control.width, -control.height).commit() + compare(pressedSpy.count, 1) + compare(control.pressed, true) + compare(control.position, 0.0) + + touch.move(0, control, control.width * 0.5, control.height * 0.5).commit() + compare(pressedSpy.count, 1) + compare(control.pressed, true) + verify(control.position, 0.5) + + touch.release(0, control, control.width * 0.5, control.height * 0.5).commit() + compare(pressedSpy.count, 2) + compare(control.pressed, false) + compare(control.position, 0.5) + + touch.press(0, control, control.width - 1, control.height - 1).commit() + compare(pressedSpy.count, 3) + compare(control.pressed, true) + compare(control.position, 0.5) + + touch.move(0, control, control.width * 2, control.height * 2).commit() + compare(pressedSpy.count, 3) + compare(control.pressed, true) + compare(control.position, 1.0) + + touch.move(0, control, control.width * 0.75, control.height * 0.75).commit() + compare(pressedSpy.count, 3) + compare(control.pressed, true) + compare(control.position, 0.75) + + touch.release(0, control, control.width * 0.25, control.height * 0.25).commit() + compare(pressedSpy.count, 4) + compare(control.pressed, false) + compare(control.position, 0.25) + } + + function test_multiTouch() { + var control1 = createTemporaryObject(scrollBar, testCase) + verify(control1) + + var pressedCount1 = 0 + var movedCount1 = 0 + + var pressedSpy1 = signalSpy.createObject(control1, {target: control1, signalName: "pressedChanged"}) + verify(pressedSpy1.valid) + + var positionSpy1 = signalSpy.createObject(control1, {target: control1, signalName: "positionChanged"}) + verify(positionSpy1.valid) + + var touch = touchEvent(control1) + touch.press(0, control1, 0, 0).commit().move(0, control1, control1.width, control1.height).commit() + + compare(pressedSpy1.count, ++pressedCount1) + compare(positionSpy1.count, ++movedCount1) + compare(control1.pressed, true) + compare(control1.position, 1.0) + + // second touch point on the same control is ignored + touch.stationary(0).press(1, control1, 0, 0).commit() + touch.stationary(0).move(1, control1).commit() + touch.stationary(0).release(1).commit() + + compare(pressedSpy1.count, pressedCount1) + compare(positionSpy1.count, movedCount1) + compare(control1.pressed, true) + compare(control1.position, 1.0) + + var control2 = createTemporaryObject(scrollBar, testCase, {y: control1.height}) + verify(control2) + + var pressedCount2 = 0 + var movedCount2 = 0 + + var pressedSpy2 = signalSpy.createObject(control2, {target: control2, signalName: "pressedChanged"}) + verify(pressedSpy2.valid) + + var positionSpy2 = signalSpy.createObject(control2, {target: control2, signalName: "positionChanged"}) + verify(positionSpy2.valid) + + // press the second scrollbar + touch.stationary(0).press(2, control2, 0, 0).commit() + + compare(pressedSpy2.count, ++pressedCount2) + compare(positionSpy2.count, movedCount2) + compare(control2.pressed, true) + compare(control2.position, 0.0) + + compare(pressedSpy1.count, pressedCount1) + compare(positionSpy1.count, movedCount1) + compare(control1.pressed, true) + compare(control1.position, 1.0) + + // move both scrollbars + touch.move(0, control1).move(2, control2).commit() + + compare(pressedSpy2.count, pressedCount2) + compare(positionSpy2.count, ++movedCount2) + compare(control2.pressed, true) + compare(control2.position, 0.5) + + compare(pressedSpy1.count, pressedCount1) + compare(positionSpy1.count, ++movedCount1) + compare(control1.pressed, true) + compare(control1.position, 0.5) + + // release both scrollbars + touch.release(0, control1).release(2, control2).commit() + + compare(pressedSpy2.count, ++pressedCount2) + compare(positionSpy2.count, movedCount2) + compare(control2.pressed, false) + compare(control2.position, 0.5) + + compare(pressedSpy1.count, ++pressedCount1) + compare(positionSpy1.count, movedCount1) + compare(control1.pressed, false) + compare(control1.position, 0.5) + } + + function test_increase_decrease_data() { + return [ + { tag: "increase:active", increase: true, active: true }, + { tag: "decrease:active", increase: false, active: true }, + { tag: "increase:inactive", increase: true, active: false }, + { tag: "decrease:inactive", increase: false, active: false } + ] + } + + function test_increase_decrease(data) { + var control = createTemporaryObject(scrollBar, testCase, {position: 0.5, active: data.active}) + verify(control) + + if (data.increase) { + control.increase() + compare(control.position, 0.6) + } else { + control.decrease() + compare(control.position, 0.4) + } + compare(control.active, data.active) + } + + function test_stepSize_data() { + return [ + { tag: "0.0", stepSize: 0.0 }, + { tag: "0.1", stepSize: 0.1 }, + { tag: "0.5", stepSize: 0.5 } + ] + } + + function test_stepSize(data) { + var control = createTemporaryObject(scrollBar, testCase, {stepSize: data.stepSize}) + verify(control) + + compare(control.stepSize, data.stepSize) + compare(control.position, 0.0) + + var count = 10 + if (data.stepSize !== 0.0) + count = 1.0 / data.stepSize + + // increase until 1.0 + for (var i = 1; i <= count; ++i) { + control.increase() + compare(control.position, i / count) + } + control.increase() + compare(control.position, 1.0) + + // decrease until 0.0 + for (var d = count - 1; d >= 0; --d) { + control.decrease() + compare(control.position, d / count) + } + control.decrease() + compare(control.position, 0.0) + } + + function test_padding_data() { + return [ + { tag: "horizontal", properties: { visible: true, orientation: Qt.Horizontal, width: testCase.width, leftPadding: testCase.width * 0.1 } }, + { tag: "vertical", properties: { visible: true, orientation: Qt.Vertical, height: testCase.height, topPadding: testCase.height * 0.1 } } + ] + } + + function test_padding(data) { + var control = createTemporaryObject(scrollBar, testCase, data.properties) + + mousePress(control, control.leftPadding + control.availableWidth * 0.5, control.topPadding + control.availableHeight * 0.5, Qt.LeftButton) + mouseRelease(control, control.leftPadding + control.availableWidth * 0.5, control.topPadding + control.availableHeight * 0.5, Qt.LeftButton) + + compare(control.position, 0.5) + } + + function test_warning() { + ignoreWarning(Qt.resolvedUrl("tst_scrollbar.qml") + ":55:1: QML TestCase: ScrollBar must be attached to a Flickable or ScrollView") + testCase.ScrollBar.vertical = null + } + + function test_mirrored() { + var container = createTemporaryObject(flickable, testCase) + verify(container) + waitForRendering(container) + + container.ScrollBar.vertical = scrollBar.createObject(container) + compare(container.ScrollBar.vertical.x, container.width - container.ScrollBar.vertical.width) + container.ScrollBar.vertical.locale = Qt.locale("ar_EG") + compare(container.ScrollBar.vertical.x, container.width - container.ScrollBar.vertical.width) + } + + function test_hover_data() { + return [ + { tag: "enabled", hoverEnabled: true, interactive: true }, + { tag: "disabled", hoverEnabled: false, interactive: true }, + { tag: "non-interactive", hoverEnabled: true, interactive: false } + ] + } + + function test_hover(data) { + var control = createTemporaryObject(scrollBar, testCase, {hoverEnabled: data.hoverEnabled, interactive: data.interactive}) + verify(control) + + compare(control.hovered, false) + + mouseMove(control, control.width / 2, control.height / 2) + compare(control.hovered, data.hoverEnabled) + compare(control.active, data.hoverEnabled && data.interactive) + + mouseMove(control, -1, -1) + compare(control.hovered, false) + compare(control.active, false) + } + + function test_snapMode_data() { + return [ + { tag: "NoSnap", snapMode: ScrollBar.NoSnap, stepSize: 0.1, size: 0.2, width: 100, steps: 80 }, /* 0.8*100 */ + { tag: "NoSnap2", snapMode: ScrollBar.NoSnap, stepSize: 0.2, size: 0.1, width: 200, steps: 180 }, /* 0.9*200 */ + + { tag: "SnapAlways", snapMode: ScrollBar.SnapAlways, stepSize: 0.1, size: 0.2, width: 100, steps: 10 }, + { tag: "SnapAlways2", snapMode: ScrollBar.SnapAlways, stepSize: 0.2, size: 0.125, width: 200, steps: 5 }, + + { tag: "SnapOnRelease", snapMode: ScrollBar.SnapOnRelease, stepSize: 0.1, size: 0.2, width: 100, steps: 80 }, /* 0.8*100 */ + { tag: "SnapOnRelease2", snapMode: ScrollBar.SnapOnRelease, stepSize: 0.2, size: 0.1, width: 200, steps: 180 }, /* 0.9*200 */ + ] + } + + function test_snapMode_mouse_data() { + return test_snapMode_data() + } + + function test_snapMode_mouse(data) { + var control = createTemporaryObject(scrollBar, testCase, {snapMode: data.snapMode, orientation: Qt.Horizontal, stepSize: data.stepSize, size: data.size, width: data.width}) + verify(control) + + function snappedPosition(pos) { + var effectiveStep = control.stepSize * (1.0 - control.size) + return Math.round(pos / effectiveStep) * effectiveStep + } + + function boundPosition(pos) { + return Math.max(0, Math.min(pos, 1.0 - control.size)) + } + + mousePress(control, 0, 0) + compare(control.position, 0) + + mouseMove(control, control.width * 0.3, 0) + var expectedMovePos = 0.3 + if (control.snapMode === ScrollBar.SnapAlways) { + expectedMovePos = snappedPosition(expectedMovePos) + verify(expectedMovePos !== 0.3) + } + compare(control.position, expectedMovePos) + + mouseRelease(control, control.width * 0.75, 0) + var expectedReleasePos = 0.75 + if (control.snapMode !== ScrollBar.NoSnap) { + expectedReleasePos = snappedPosition(expectedReleasePos) + verify(expectedReleasePos !== 0.75) + } + compare(control.position, expectedReleasePos) + + control.position = 0 + mousePress(control, 0, 0) + + var steps = 0 + var prevPos = 0 + + for (var x = 0; x < control.width; ++x) { + mouseMove(control, x, 0) + expectedMovePos = boundPosition(x / control.width) + if (control.snapMode === ScrollBar.SnapAlways) + expectedMovePos = snappedPosition(expectedMovePos) + compare(control.position, expectedMovePos) + + if (control.position !== prevPos) + ++steps + prevPos = control.position + } + compare(steps, data.steps) + + mouseRelease(control, control.width - 1, 0) + } + + function test_snapMode_touch_data() { + return test_snapMode_data() + } + + function test_snapMode_touch(data) { + var control = createTemporaryObject(scrollBar, testCase, {snapMode: data.snapMode, orientation: Qt.Horizontal, stepSize: data.stepSize, size: data.size, width: data.width}) + verify(control) + + function snappedPosition(pos) { + var effectiveStep = control.stepSize * (1.0 - control.size) + return Math.round(pos / effectiveStep) * effectiveStep + } + + function boundPosition(pos) { + return Math.max(0, Math.min(pos, 1.0 - control.size)) + } + + var touch = touchEvent(control) + + touch.press(0, control, 0, 0).commit() + compare(control.position, 0) + + touch.move(0, control, control.width * 0.3, 0).commit() + var expectedMovePos = 0.3 + if (control.snapMode === ScrollBar.SnapAlways) { + expectedMovePos = snappedPosition(expectedMovePos) + verify(expectedMovePos !== 0.3) + } + compare(control.position, expectedMovePos) + + touch.release(0, control, control.width * 0.75, 0).commit() + var expectedReleasePos = 0.75 + if (control.snapMode !== ScrollBar.NoSnap) { + expectedReleasePos = snappedPosition(expectedReleasePos) + verify(expectedReleasePos !== 0.75) + } + compare(control.position, expectedReleasePos) + + control.position = 0 + touch.press(0, control, 0, 0).commit() + + var steps = 0 + var prevPos = 0 + + for (var x = 0; x < control.width; ++x) { + touch.move(0, control, x, 0).commit() + expectedMovePos = boundPosition(x / control.width) + if (control.snapMode === ScrollBar.SnapAlways) + expectedMovePos = snappedPosition(expectedMovePos) + compare(control.position, expectedMovePos) + + if (control.position !== prevPos) + ++steps + prevPos = control.position + } + compare(steps, data.steps) + + touch.release(0, control, control.width - 1).commit() + } + + function test_interactive_data() { + return [ + { tag: "true", interactive: true }, + { tag: "false", interactive: false } + ] + } + + function test_interactive(data) { + var control = createTemporaryObject(scrollBar, testCase, {interactive: data.interactive}) + verify(control) + + compare(control.interactive, data.interactive) + + // press-move-release + mousePress(control, 0, 0, Qt.LeftButton) + compare(control.pressed, data.interactive) + + mouseMove(control, control.width / 2, control.height / 2) + compare(control.position, data.interactive ? 0.5 : 0.0) + + mouseRelease(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.pressed, false) + + // change to non-interactive while pressed + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.pressed, data.interactive) + + mouseMove(control, control.width, control.height) + compare(control.position, data.interactive ? 1.0 : 0.0) + + control.interactive = false + compare(control.interactive, false) + compare(control.pressed, false) + + mouseMove(control, control.width / 2, control.height / 2) + compare(control.position, data.interactive ? 1.0 : 0.0) + + mouseRelease(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.pressed, false) + + // change back to interactive & try press-move-release again + control.interactive = true + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.pressed, true) + + mouseMove(control, 0, 0) + compare(control.position, 0.0) + + mouseRelease(control, 0, 0, Qt.LeftButton) + compare(control.pressed, false) + } + + function test_policy() { + var control = createTemporaryObject(scrollBar, testCase, {active: true}) + verify(control) + + compare(ScrollBar.AsNeeded, Qt.ScrollBarAsNeeded) + compare(ScrollBar.AlwaysOff, Qt.ScrollBarAlwaysOff) + compare(ScrollBar.AlwaysOn, Qt.ScrollBarAlwaysOn) + + compare(control.visible, true) + compare(control.policy, ScrollBar.AsNeeded) + + control.size = 0.5 + verify(control.state === "active" || control.contentItem.state === "active") + + control.size = 1.0 + verify(control.state !== "active" && control.contentItem.state !== "active") + + control.policy = ScrollBar.AlwaysOff + compare(control.visible, false) + + control.policy = ScrollBar.AlwaysOn + compare(control.visible, true) + verify(control.state === "active" || control.contentItem.state === "active") + } + + function test_overshoot() { + var container = createTemporaryObject(flickable, testCase) + verify(container) + waitForRendering(container) + + var vertical = scrollBar.createObject(container, {size: 0.5}) + container.ScrollBar.vertical = vertical + + var horizontal = scrollBar.createObject(container, {size: 0.5}) + container.ScrollBar.horizontal = horizontal + + // negative vertical overshoot (pos < 0) + vertical.position = -0.1 + compare(vertical.contentItem.y, vertical.topPadding) + compare(vertical.contentItem.height, 0.4 * vertical.availableHeight) + + // positive vertical overshoot (pos + size > 1) + vertical.position = 0.8 + compare(vertical.contentItem.y, vertical.topPadding + 0.8 * vertical.availableHeight) + compare(vertical.contentItem.height, 0.2 * vertical.availableHeight) + + // negative horizontal overshoot (pos < 0) + horizontal.position = -0.1 + compare(horizontal.contentItem.x, horizontal.leftPadding) + compare(horizontal.contentItem.width, 0.4 * horizontal.availableWidth) + + // positive horizontal overshoot (pos + size > 1) + horizontal.position = 0.8 + compare(horizontal.contentItem.x, horizontal.leftPadding + 0.8 * horizontal.availableWidth) + compare(horizontal.contentItem.width, 0.2 * horizontal.availableWidth) + } + + function test_orientation() { + var control = createTemporaryObject(scrollBar, testCase) + verify(control) + + compare(control.orientation, Qt.Vertical) + compare(control.horizontal, false) + compare(control.vertical, true) + + control.orientation = Qt.Horizontal + compare(control.orientation, Qt.Horizontal) + compare(control.horizontal, true) + compare(control.vertical, false) + } + + function test_flashing() { + var control = createTemporaryObject(scrollBar, testCase, {size: 0.2}) + verify(control) + + var activeSpy = signalSpy.createObject(control, {target: control, signalName: "activeChanged"}) + verify(activeSpy.valid) + + compare(control.active, false) + if (control.contentItem) + compare(control.contentItem.opacity, 0) + if (control.background) + compare(control.background.opacity, 0) + + control.increase() + compare(control.position, 0.1) + compare(control.active, false) + compare(activeSpy.count, 2) + if (control.contentItem) + verify(control.contentItem.opacity > 0) + if (control.background) + verify(control.background.opacity > 0) + if (control.contentItem) + tryCompare(control.contentItem, "opacity", 0) + if (control.background) + tryCompare(control.background, "opacity", 0) + + control.decrease() + compare(control.position, 0.0) + compare(control.active, false) + compare(activeSpy.count, 4) + if (control.contentItem) + verify(control.contentItem.opacity > 0) + if (control.background) + verify(control.background.opacity > 0) + if (control.contentItem) + tryCompare(control.contentItem, "opacity", 0) + if (control.background) + tryCompare(control.background, "opacity", 0) + } + + function test_minimumSize() { + var container = createTemporaryObject(flickable, testCase) + verify(container) + waitForRendering(container) + + var vertical = scrollBar.createObject(container, {minimumSize: 0.1}) + container.ScrollBar.vertical = vertical + + compare(container.visibleArea.heightRatio, 0.5) + compare(vertical.size, 0.5) + compare(vertical.visualSize, 0.5) + compare(vertical.contentItem.height, 0.5 * vertical.availableHeight) + + container.contentHeight = 2000 + + compare(container.visibleArea.heightRatio, 0.05) + compare(vertical.size, 0.05) + compare(vertical.visualSize, 0.1) + compare(vertical.contentItem.height, 0.1 * vertical.availableHeight) + + verify(container.atYBeginning) + compare(container.visibleArea.yPosition, 0.0) + compare(vertical.position, 0.0) + compare(vertical.visualPosition, 0.0) + compare(vertical.contentItem.y, vertical.topPadding) + + container.contentY = 1900 + + verify(container.atYEnd) + compare(container.visibleArea.yPosition, 0.95) + compare(vertical.position, 0.95) + compare(vertical.visualPosition, 0.9) + compare(vertical.contentItem.y, vertical.topPadding + 0.9 * vertical.availableHeight) + + container.contentHeight = 125 + + compare(container.visibleArea.heightRatio, 0.8) + compare(vertical.size, 0.8) + compare(vertical.visualSize, 0.8) + compare(vertical.contentItem.height, 0.8 * vertical.availableHeight) + + verify(container.atYEnd) + compare(container.visibleArea.yPosition, 0.2) + compare(vertical.position, 0.2) + compare(vertical.visualPosition, 0.2) + compare(vertical.contentItem.y, vertical.topPadding + 0.2 * vertical.availableHeight) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_scrollindicator.qml b/tests/auto/quickcontrols2/controls/data/tst_scrollindicator.qml new file mode 100644 index 0000000000..5f6006d40e --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_scrollindicator.qml @@ -0,0 +1,309 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "ScrollIndicator" + + Component { + id: scrollIndicator + ScrollIndicator { } + } + + Component { + id: mouseArea + MouseArea { } + } + + Component { + id: flickable + Flickable { + width: 100 + height: 100 + contentWidth: 200 + contentHeight: 200 + boundsBehavior: Flickable.StopAtBounds + flickableDirection: Flickable.HorizontalAndVerticalFlick + } + } + + function test_attach() { + var container = createTemporaryObject(flickable, testCase) + verify(container) + waitForRendering(container) + + var vertical = createTemporaryObject(scrollIndicator, null) + verify(!vertical.parent) + compare(vertical.size, 0.0) + compare(vertical.position, 0.0) + compare(vertical.active, false) + compare(vertical.orientation, Qt.Vertical) + compare(vertical.x, 0) + compare(vertical.y, 0) + verify(vertical.width > 0) + verify(vertical.height > 0) + + container.ScrollIndicator.vertical = vertical + compare(vertical.parent, container) + compare(vertical.orientation, Qt.Vertical) + compare(vertical.size, container.visibleArea.heightRatio) + compare(vertical.position, container.visibleArea.yPosition) + compare(vertical.x, container.width - vertical.width) + compare(vertical.y, 0) + verify(vertical.width > 0) + compare(vertical.height, container.height) + // vertical scroll indicator follows flickable's width + container.width += 10 + compare(vertical.x, container.width - vertical.width) + vertical.implicitWidth -= 1 + compare(vertical.x, container.width - vertical.width) + // ...unless explicitly positioned + vertical.x = 123 + container.width += 10 + compare(vertical.x, 123) + + var horizontal = createTemporaryObject(scrollIndicator, null) + verify(!horizontal.parent) + compare(horizontal.size, 0.0) + compare(horizontal.position, 0.0) + compare(horizontal.active, false) + compare(horizontal.orientation, Qt.Vertical) + compare(horizontal.x, 0) + compare(horizontal.y, 0) + verify(horizontal.width > 0) + verify(horizontal.height > 0) + + container.ScrollIndicator.horizontal = horizontal + compare(horizontal.parent, container) + compare(horizontal.orientation, Qt.Horizontal) + compare(horizontal.size, container.visibleArea.widthRatio) + compare(horizontal.position, container.visibleArea.xPosition) + compare(horizontal.x, 0) + compare(horizontal.y, container.height - horizontal.height) + compare(horizontal.width, container.width) + verify(horizontal.height > 0) + // horizontal scroll indicator follows flickable's height + container.height += 10 + compare(horizontal.y, container.height - horizontal.height) + horizontal.implicitHeight -= 1 + compare(horizontal.y, container.height - horizontal.height) + // ...unless explicitly positioned + horizontal.y = 123 + container.height += 10 + compare(horizontal.y, 123) + + var velocity = container.maximumFlickVelocity + + compare(container.flicking, false) + container.flick(-velocity, -velocity) + compare(container.flicking, true) + tryCompare(container, "flicking", false) + + compare(vertical.size, container.visibleArea.heightRatio) + compare(vertical.position, container.visibleArea.yPosition) + compare(horizontal.size, container.visibleArea.widthRatio) + compare(horizontal.position, container.visibleArea.xPosition) + + compare(container.flicking, false) + container.flick(velocity, velocity) + compare(container.flicking, true) + tryCompare(container, "flicking", false) + + compare(vertical.size, container.visibleArea.heightRatio) + compare(vertical.position, container.visibleArea.yPosition) + compare(horizontal.size, container.visibleArea.widthRatio) + compare(horizontal.position, container.visibleArea.xPosition) + + var oldY = vertical.y + var oldHeight = vertical.height + vertical.parent = testCase + vertical.y -= 10 + container.height += 10 + compare(vertical.y, oldY - 10) + compare(vertical.height, oldHeight) + + var oldX = horizontal.x + var oldWidth = horizontal.width + horizontal.parent = testCase + horizontal.x -= 10 + container.width += 10 + compare(horizontal.x, oldX - 10) + compare(horizontal.width, oldWidth) + } + + function test_warning() { + ignoreWarning(Qt.resolvedUrl("tst_scrollindicator.qml") + ":55:1: QML TestCase: ScrollIndicator must be attached to a Flickable") + testCase.ScrollIndicator.vertical = null + } + + function test_overshoot() { + var container = createTemporaryObject(flickable, testCase) + verify(container) + waitForRendering(container) + + var vertical = scrollIndicator.createObject(container, {size: 0.5}) + container.ScrollIndicator.vertical = vertical + + var horizontal = scrollIndicator.createObject(container, {size: 0.5}) + container.ScrollIndicator.horizontal = horizontal + + // negative vertical overshoot (pos < 0) + vertical.position = -0.1 + compare(vertical.contentItem.y, vertical.topPadding) + compare(vertical.contentItem.height, 0.4 * vertical.availableHeight) + + // positive vertical overshoot (pos + size > 1) + vertical.position = 0.8 + compare(vertical.contentItem.y, vertical.topPadding + 0.8 * vertical.availableHeight) + compare(vertical.contentItem.height, 0.2 * vertical.availableHeight) + + // negative horizontal overshoot (pos < 0) + horizontal.position = -0.1 + compare(horizontal.contentItem.x, horizontal.leftPadding) + compare(horizontal.contentItem.width, 0.4 * horizontal.availableWidth) + + // positive horizontal overshoot (pos + size > 1) + horizontal.position = 0.8 + compare(horizontal.contentItem.x, horizontal.leftPadding + 0.8 * horizontal.availableWidth) + compare(horizontal.contentItem.width, 0.2 * horizontal.availableWidth) + } + + function test_orientation() { + var control = createTemporaryObject(scrollIndicator, testCase) + verify(control) + + compare(control.orientation, Qt.Vertical) + compare(control.horizontal, false) + compare(control.vertical, true) + + control.orientation = Qt.Horizontal + compare(control.orientation, Qt.Horizontal) + compare(control.horizontal, true) + compare(control.vertical, false) + } + + // QTBUG-61785 + function test_mouseArea() { + var ma = createTemporaryObject(mouseArea, testCase, {width: testCase.width, height: testCase.height}) + verify(ma) + + var control = scrollIndicator.createObject(ma, {active: true, size: 0.9, width: testCase.width, height: testCase.height}) + verify(control) + + mousePress(control) + verify(ma.pressed) + + mouseRelease(control) + verify(!ma.pressed) + + var touch = touchEvent(control) + touch.press(0, control).commit() + verify(ma.pressed) + + touch.release(0, control).commit() + verify(!ma.pressed) + } + + function test_minimumSize() { + var container = createTemporaryObject(flickable, testCase) + verify(container) + waitForRendering(container) + + var vertical = scrollIndicator.createObject(container, {minimumSize: 0.1}) + container.ScrollIndicator.vertical = vertical + + compare(container.visibleArea.heightRatio, 0.5) + compare(vertical.size, 0.5) + compare(vertical.visualSize, 0.5) + compare(vertical.contentItem.height, 0.5 * vertical.availableHeight) + + container.contentHeight = 2000 + + compare(container.visibleArea.heightRatio, 0.05) + compare(vertical.size, 0.05) + compare(vertical.visualSize, 0.1) + compare(vertical.contentItem.height, 0.1 * vertical.availableHeight) + + verify(container.atYBeginning) + compare(container.visibleArea.yPosition, 0.0) + compare(vertical.position, 0.0) + compare(vertical.visualPosition, 0.0) + compare(vertical.contentItem.y, vertical.topPadding) + + container.contentY = 1900 + + verify(container.atYEnd) + compare(container.visibleArea.yPosition, 0.95) + compare(vertical.position, 0.95) + compare(vertical.visualPosition, 0.9) + compare(vertical.contentItem.y, vertical.topPadding + 0.9 * vertical.availableHeight) + + container.contentHeight = 125 + + compare(container.visibleArea.heightRatio, 0.8) + compare(vertical.size, 0.8) + compare(vertical.visualSize, 0.8) + compare(vertical.contentItem.height, 0.8 * vertical.availableHeight) + + verify(container.atYEnd) + compare(container.visibleArea.yPosition, 0.2) + compare(vertical.position, 0.2) + compare(vertical.visualPosition, 0.2) + compare(vertical.contentItem.y, vertical.topPadding + 0.2 * vertical.availableHeight) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_scrollview.qml b/tests/auto/quickcontrols2/controls/data/tst_scrollview.qml new file mode 100644 index 0000000000..46afa503ad --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_scrollview.qml @@ -0,0 +1,579 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "ScrollView" + + Component { + id: signalSpy + SignalSpy { } + } + + Component { + id: scrollView + ScrollView { } + } + + Component { + id: scrollBarComponent + ScrollBar {} + } + + Component { + id: scrollableLabel + ScrollView { + Label { + text: "ABC" + font.pixelSize: 512 + } + } + } + + Component { + id: scrollableLabels + ScrollView { + contentHeight: label1.implicitHeight + label2.implicitHeight + label3.implicitHeight + Label { + id: label1 + text: "First" + font.pixelSize: 96 + } + Label { + id: label2 + text: "Second" + font.pixelSize: 96 + } + Label { + id: label3 + text: "Third" + font.pixelSize: 96 + } + } + } + + Component { + id: flickableLabel + ScrollView { + Flickable { + contentWidth: label.implicitWidth + contentHeight: label.implicitHeight + Label { + id: label + text: "ABC" + font.pixelSize: 512 + } + } + } + } + + Component { + id: emptyFlickable + ScrollView { + Flickable { + } + } + } + + Component { + id: labelComponent + Label { + text: "ABC" + font.pixelSize: 512 + } + } + + Component { + id: scrollableListView + ScrollView { + ListView { + model: 3 + delegate: Label { + text: modelData + } + } + } + } + + Component { + id: scrollableFlickable + ScrollView { + Flickable { + Item { + width: 100 + height: 100 + } + } + } + } + + Component { + id: scrollableWithContentSize + ScrollView { + contentWidth: 1000 + contentHeight: 1000 + Flickable { + } + } + } + + Component { + id: scrollableAndFlicableWithContentSize + ScrollView { + contentWidth: 1000 + contentHeight: 1000 + Flickable { + contentWidth: 200 + contentHeight: 200 + } + } + } + + Component { + id: scrollableTextArea + ScrollView { + TextArea { + text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas id dignissim ipsum. Nam molestie nisl turpis." + wrapMode: TextArea.WordWrap + } + } + } + Component { + id: scrollableTextAreaWithSibling + ScrollView { + Item { + } + TextArea { + } + } + } + + function test_scrollBars() { + var control = createTemporaryObject(scrollView, testCase, {width: 200, height: 200}) + verify(control) + + var vertical = control.ScrollBar.vertical + verify(vertical) + + var horizontal = control.ScrollBar.horizontal + verify(horizontal) + + control.contentHeight = 400 + verify(vertical.size > 0) + compare(control.contentItem.visibleArea.heightRatio, vertical.size) + + control.contentWidth = 400 + verify(horizontal.size > 0) + compare(control.contentItem.visibleArea.widthRatio, horizontal.size) + + vertical.increase() + verify(vertical.position > 0) + compare(control.contentItem.visibleArea.yPosition, vertical.position) + + horizontal.increase() + verify(horizontal.position > 0) + compare(control.contentItem.visibleArea.xPosition, horizontal.position) + } + + function test_oneChild_data() { + return [ + { tag: "label", component: scrollableLabel }, + { tag: "flickable", component: flickableLabel } + ] + } + + function test_oneChild(data) { + var control = createTemporaryObject(data.component, testCase) + verify(control) + + var flickable = control.contentItem + verify(flickable.hasOwnProperty("contentX")) + verify(flickable.hasOwnProperty("contentY")) + + var label = flickable.contentItem.children[0] + compare(label.text, "ABC") + + compare(control.implicitWidth, label.implicitWidth) + compare(control.implicitHeight, label.implicitHeight) + + compare(control.contentWidth, label.implicitWidth) + compare(control.contentHeight, label.implicitHeight) + + compare(flickable.contentWidth, label.implicitWidth) + compare(flickable.contentHeight, label.implicitHeight) + + control.contentWidth = 200 + compare(control.implicitWidth, 200) + compare(control.contentWidth, 200) + compare(flickable.contentWidth, 200) + + control.contentHeight = 100 + compare(control.implicitHeight, 100) + compare(control.contentHeight, 100) + compare(flickable.contentHeight, 100) + } + + function test_multipleChildren() { + var control = createTemporaryObject(scrollableLabels, testCase) + verify(control) + + var flickable = control.contentItem + verify(flickable.hasOwnProperty("contentX")) + verify(flickable.hasOwnProperty("contentY")) + + compare(control.contentChildren, flickable.contentItem.children) + + var label1 = control.contentChildren[0] + compare(label1.text, "First") + + var label2 = control.contentChildren[1] + compare(label2.text, "Second") + + var label3 = control.contentChildren[2] + compare(label3.text, "Third") + + var expectedContentHeight = label1.implicitHeight + label2.implicitHeight + label3.implicitHeight + compare(control.contentHeight, expectedContentHeight) + compare(flickable.contentHeight, expectedContentHeight) + } + + function test_listView() { + var control = createTemporaryObject(scrollableListView, testCase) + verify(control) + + var listview = control.contentItem + verify(listview.hasOwnProperty("contentX")) + verify(listview.hasOwnProperty("contentY")) + verify(listview.hasOwnProperty("model")) + + compare(control.contentWidth, listview.contentWidth) + compare(control.contentHeight, listview.contentHeight) + } + + function test_scrollableFlickable() { + // Check that if the application adds a flickable as a child of a + // scrollview, the scrollview doesn't try to calculate and change + // the flickables contentWidth/Height based on the flickables + // children, even if the flickable has an empty or negative content + // size. Some flickables (e.g ListView) sets a negative + // contentWidth on purpose, which should be respected. + var scrollview = createTemporaryObject(scrollableFlickable, testCase) + verify(scrollview) + + var flickable = scrollview.contentItem + verify(flickable.hasOwnProperty("contentX")) + verify(flickable.hasOwnProperty("contentY")) + + compare(flickable.contentWidth, -1) + compare(flickable.contentHeight, -1) + compare(scrollview.contentWidth, -1) + compare(scrollview.contentHeight, -1) + } + + function test_scrollableWithContentSize() { + // Check that if the scrollview has contentWidth/Height set, but + // not the flickable, then those values will be forwarded and used + // by the flickable (rather than trying to calculate the content size + // based on the flickables children). + var scrollview = createTemporaryObject(scrollableWithContentSize, testCase) + verify(scrollview) + + var flickable = scrollview.contentItem + verify(flickable.hasOwnProperty("contentX")) + verify(flickable.hasOwnProperty("contentY")) + + compare(flickable.contentWidth, 1000) + compare(flickable.contentHeight, 1000) + compare(scrollview.contentWidth, 1000) + compare(scrollview.contentHeight, 1000) + } + + function test_scrollableAndFlickableWithContentSize() { + // Check that if both the scrollview and the flickable has + // contentWidth/Height set (which is an inconsistency/fault + // by the app), the content size of the scrollview wins. + var scrollview = createTemporaryObject(scrollableAndFlicableWithContentSize, testCase) + verify(scrollview) + + var flickable = scrollview.contentItem + verify(flickable.hasOwnProperty("contentX")) + verify(flickable.hasOwnProperty("contentY")) + + compare(flickable.contentWidth, 1000) + compare(flickable.contentHeight, 1000) + compare(scrollview.contentWidth, 1000) + compare(scrollview.contentHeight, 1000) + } + + function test_flickableWithExplicitContentSize() { + var control = createTemporaryObject(emptyFlickable, testCase) + verify(control) + + var flickable = control.contentItem + verify(flickable.hasOwnProperty("contentX")) + verify(flickable.hasOwnProperty("contentY")) + + var flickableContentSize = 1000; + flickable.contentWidth = flickableContentSize; + flickable.contentHeight = flickableContentSize; + + compare(flickable.contentWidth, flickableContentSize) + compare(flickable.contentHeight, flickableContentSize) + compare(control.implicitWidth, flickableContentSize) + compare(control.implicitHeight, flickableContentSize) + compare(control.contentWidth, flickableContentSize) + compare(control.contentHeight, flickableContentSize) + + // Add a single child to the flickable. This should not + // trick ScrollView into taking the implicit size of + // the child as content size, since the flickable + // already has an explicit content size. + labelComponent.createObject(flickable); + + compare(flickable.contentWidth, flickableContentSize) + compare(flickable.contentHeight, flickableContentSize) + compare(control.implicitWidth, flickableContentSize) + compare(control.implicitHeight, flickableContentSize) + compare(control.contentWidth, flickableContentSize) + compare(control.contentHeight, flickableContentSize) + } + + function test_mouse() { + var control = createTemporaryObject(scrollView, testCase, {width: 200, height: 200, contentHeight: 400}) + verify(control) + + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.contentItem.contentY, 0) + + for (var y = control.height / 2; y >= 0; --y) { + mouseMove(control, control.width / 2, y, 10) + compare(control.contentItem.contentY, 0) + } + + mouseRelease(control, control.width / 2, 0, Qt.LeftButton) + compare(control.contentItem.contentY, 0) + } + + function test_hover() { + var control = createTemporaryObject(scrollView, testCase, {width: 200, height: 200, contentHeight: 400}) + verify(control) + + var vertical = control.ScrollBar.vertical + verify(vertical) + vertical.hoverEnabled = true + + mouseMove(vertical, vertical.width / 2, vertical.height / 2) + compare(vertical.visible, true) + compare(vertical.hovered, true) + compare(vertical.active, true) + compare(vertical.interactive, true) + } + + function test_wheel() { + var control = createTemporaryObject(scrollView, testCase, {width: 200, height: 200, contentHeight: 400}) + verify(control) + + var vertical = control.ScrollBar.vertical + verify(vertical) + + mouseWheel(control, control.width / 2, control.height / 2, 0, -120) + compare(vertical.visible, true) + compare(vertical.active, true) + compare(vertical.interactive, true) + } + + function test_touch() { + var control = createTemporaryObject(scrollView, testCase, {width: 200, height: 200, contentHeight: 400}) + verify(control) + + var vertical = control.ScrollBar.vertical + verify(vertical) + + var touch = touchEvent(control) + touch.press(0, control, control.width / 2, control.height / 2).commit() + compare(control.contentItem.contentY, 0) + + compare(vertical.active, false) + compare(vertical.interactive, false) + + var maxContentY = 0 + for (var y = control.height / 2; y >= 0; --y) { + touch.move(0, control, control.width / 2, y).commit() + maxContentY = Math.max(maxContentY, control.contentItem.contentY) + } + verify(maxContentY > 0) + + compare(vertical.active, true) + compare(vertical.interactive, false) + + touch.release(0, control, control.width / 2, 0).commit() + } + + function test_keys() { + var control = createTemporaryObject(scrollView, testCase, {width: 200, height: 200, contentWidth: 400, contentHeight: 400}) + verify(control) + + control.forceActiveFocus() + verify(control.activeFocus) + + var vertical = control.ScrollBar.vertical + verify(vertical) + + compare(vertical.position, 0.0) + for (var i = 1; i <= 10; ++i) { + keyClick(Qt.Key_Down) + compare(vertical.position, Math.min(0.5, i * 0.1)) + } + compare(vertical.position, 0.5) + for (i = 1; i <= 10; ++i) { + keyClick(Qt.Key_Up) + compare(vertical.position, Math.max(0.0, 0.5 - i * 0.1)) + } + compare(vertical.position, 0.0) + + var horizontal = control.ScrollBar.horizontal + verify(horizontal) + + compare(horizontal.position, 0.0) + for (i = 1; i <= 10; ++i) { + keyClick(Qt.Key_Right) + compare(horizontal.position, Math.min(0.5, i * 0.1)) + } + compare(horizontal.position, 0.5) + for (i = 1; i <= 10; ++i) { + keyClick(Qt.Key_Left) + compare(horizontal.position, Math.max(0.0, 0.5 - i * 0.1)) + } + compare(horizontal.position, 0.0) + } + + function test_textArea() { + // TODO: verify no binding loop warnings (QTBUG-62325) + var control = createTemporaryObject(scrollableTextArea, testCase) + verify(control) + + var flickable = control.contentItem + verify(flickable && flickable.hasOwnProperty("contentX")) + + var textArea = flickable.contentItem.children[0] + verify(textArea && textArea.hasOwnProperty("text")) + + compare(control.contentWidth, flickable.contentWidth) + compare(control.contentHeight, flickable.contentHeight) + } + + function test_textAreaWithSibling() { + // Checks that it does not crash when the ScrollView is deleted + var control = createTemporaryObject(scrollableTextAreaWithSibling, testCase) + verify(control) + } + + Component { + id: zeroSizedContentItemComponent + ScrollView { + width: 100 + height: 100 + contentItem: Item {} + } + } + + function test_zeroSizedContentItem() { + ignoreWarning(/ScrollView only supports Flickable types as its contentItem/) + let control = createTemporaryObject(zeroSizedContentItemComponent, testCase) + verify(control) + + let verticalScrollBar = control.ScrollBar.vertical + verify(verticalScrollBar) + // Scrolling a ScrollView with a zero-sized contentItem shouldn't crash. + mouseDrag(verticalScrollBar, verticalScrollBar.width / 2, verticalScrollBar.height / 2, 0, 50) + + let horizontalScrollBar = control.ScrollBar.horizontal + verify(verticalScrollBar) + mouseDrag(horizontalScrollBar, horizontalScrollBar.width / 2, horizontalScrollBar.height / 2, 50, 0) + } + + function test_customScrollBars() { + let control = createTemporaryObject(scrollView, testCase) + verify(control) + control.ScrollBar.vertical.objectName = "oldVerticalScrollBar" + control.ScrollBar.horizontal.objectName = "oldHorizontalScrollBar" + + let oldVerticalScrollBar = control.ScrollBar.vertical + verify(oldVerticalScrollBar) + compare(oldVerticalScrollBar.objectName, "oldVerticalScrollBar") + + let oldHorizontalScrollBar = control.ScrollBar.horizontal + verify(oldHorizontalScrollBar) + compare(oldHorizontalScrollBar.objectName, "oldHorizontalScrollBar") + + // Create the new scroll bars imperatively so that we can easily access the old ones. + control.ScrollBar.vertical = scrollBarComponent.createObject(control, { objectName: "newVerticalScrollBar" }) + verify(control.ScrollBar.vertical) + let newVerticalScrollBar = findChild(control, "newVerticalScrollBar") + verify(newVerticalScrollBar) + verify(newVerticalScrollBar.visible) + verify(!oldVerticalScrollBar.visible) + + control.ScrollBar.horizontal = scrollBarComponent.createObject(control, { objectName: "newHorizontalScrollBar" }) + verify(control.ScrollBar.horizontal) + let newHorizontalScrollBar = findChild(control, "newHorizontalScrollBar") + verify(newHorizontalScrollBar) + verify(newHorizontalScrollBar.visible) + verify(!oldHorizontalScrollBar.visible) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_selectionrectangle.qml b/tests/auto/quickcontrols2/controls/data/tst_selectionrectangle.qml new file mode 100644 index 0000000000..13ddf00f95 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_selectionrectangle.qml @@ -0,0 +1,311 @@ +/**************************************************************************** +** +** 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 QtTest +import QtQuick.Controls +import Qt.labs.qmlmodels + +TestCase { + id: testCase + width: 200 + height: 200 + visible: true + when: windowShown + name: "SelectionRectangle" + + property real cellWidth: 50 + property real cellHeight: 20 + property Item handle: null + property bool handleWasDragged: false + + Component { + id: handleComp + Rectangle { + id: handle + width: 28 + height: width + radius: width / 2 + property bool dragging: SelectionRectangle.dragging + property Item control: SelectionRectangle.control + border.width: 1 + border.color: "red" + visible: SelectionRectangle.control.active + + SelectionRectangle.onDraggingChanged: { + if (SelectionRectangle.dragging) + testCase.handleWasDragged = true + } + + Component.onCompleted: testCase.handle = handle + } + } + + Component { + id: tableviewComp + TableView { + id: tableView + clip: true + anchors.fill: parent + + model: TableModel { + TableModelColumn { display: "c1" } + TableModelColumn { display: "c2" } + TableModelColumn { display: "c3" } + TableModelColumn { display: "c4" } + rows: [ + { "c1": "v1", "c2":"v2", "c3":"v3", "c4": "v4" }, + { "c1": "v1", "c2":"v2", "c3":"v3", "c4": "v4" }, + { "c1": "v1", "c2":"v2", "c3":"v3", "c4": "v4" }, + { "c1": "v1", "c2":"v2", "c3":"v3", "c4": "v4" }, + ] + } + + delegate: Rectangle { + required property bool selected + implicitWidth: cellWidth + implicitHeight: cellHeight + color: selected ? "lightblue" : "gray" + Text { text: "cell" } + } + + selectionModel: ItemSelectionModel { + model: tableView.model + } + + property alias selectionRectangle: selectionRectangle + SelectionRectangle { + id: selectionRectangle + target: tableView + } + } + + } + + Component { + id: signalSpy + SignalSpy { } + } + + function test_set_target() { + let tableView = createTemporaryObject(tableviewComp, testCase) + verify(tableView) + let selectionRectangle = tableView.selectionRectangle + verify(selectionRectangle) + + compare(selectionRectangle.target, tableView) + + selectionRectangle.target = null + compare(selectionRectangle.target, null) + + selectionRectangle.target = tableView + compare(selectionRectangle.target, tableView) + } + + function test_set_selectionMode() { + let tableView = createTemporaryObject(tableviewComp, testCase) + verify(tableView) + let selectionRectangle = tableView.selectionRectangle + verify(selectionRectangle) + + // Default selection mode should be Auto + compare(selectionRectangle.selectionMode, SelectionRectangle.Auto) + + selectionRectangle.selectionMode = SelectionRectangle.Drag + compare(selectionRectangle.selectionMode, SelectionRectangle.Drag) + + selectionRectangle.selectionMode = SelectionRectangle.PressAndHold + compare(selectionRectangle.selectionMode, SelectionRectangle.PressAndHold) + + selectionRectangle.selectionMode = SelectionRectangle.Auto + compare(selectionRectangle.selectionMode, SelectionRectangle.Auto) + } + + function test_set_handles() { + let tableView = createTemporaryObject(tableviewComp, testCase) + verify(tableView) + let selectionRectangle = tableView.selectionRectangle + verify(selectionRectangle) + + selectionRectangle.topLeftHandle = null + compare(selectionRectangle.topLeftHandle, null) + + selectionRectangle.bottomRightHandle = null + compare(selectionRectangle.bottomRightHandle, null) + + selectionRectangle.topLeftHandle = handleComp + compare(selectionRectangle.topLeftHandle, handleComp) + + selectionRectangle.bottomRightHandle = handleComp + compare(selectionRectangle.bottomRightHandle, handleComp) + } + + function test_drag() { + let tableView = createTemporaryObject(tableviewComp, testCase) + verify(tableView) + let selectionRectangle = tableView.selectionRectangle + verify(selectionRectangle) + + selectionRectangle.selectionMode = SelectionRectangle.Drag + + let activeSpy = signalSpy.createObject(selectionRectangle, {target: selectionRectangle, signalName: "activeChanged"}) + let draggingSpy = signalSpy.createObject(selectionRectangle, {target: selectionRectangle, signalName: "draggingChanged"}) + verify(activeSpy.valid) + verify(draggingSpy.valid) + + verify(!tableView.selectionModel.hasSelection) + mouseDrag(tableView, 1, 1, (cellWidth * 2) - 2, 1, Qt.LeftButton) + verify(tableView.selectionModel.hasSelection) + compare(tableView.selectionModel.selectedIndexes.length, 2) + verify(tableView.selectionModel.isSelected(tableView.model.index(0, 0))) + verify(tableView.selectionModel.isSelected(tableView.model.index(0, 1))) + + compare(activeSpy.count, 1) + compare(draggingSpy.count, 2) + + // Remove selection + mouseClick(tableView, 1, 1, Qt.LeftButton) + verify(!tableView.selectionModel.hasSelection) + compare(draggingSpy.count, 2) + compare(activeSpy.count, 2) + + // Ensure that a press and hold doesn't start a selection + mousePress(tableView, 1, 1, Qt.LeftButton) + mousePress(tableView, 1, 1, Qt.LeftButton, Qt.NoModifier, 1000) + verify(!tableView.selectionModel.hasSelection) + } + + function test_pressAndHold() { + let tableView = createTemporaryObject(tableviewComp, testCase) + verify(tableView) + let selectionRectangle = tableView.selectionRectangle + verify(selectionRectangle) + + selectionRectangle.selectionMode = SelectionRectangle.PressAndHold + + let activeSpy = signalSpy.createObject(selectionRectangle, {target: selectionRectangle, signalName: "activeChanged"}) + let draggingSpy = signalSpy.createObject(selectionRectangle, {target: selectionRectangle, signalName: "draggingChanged"}) + verify(activeSpy.valid) + verify(draggingSpy.valid) + + verify(!tableView.selectionModel.hasSelection) + // Do a press and hold + mousePress(tableView, 1, 1, Qt.LeftButton) + mousePress(tableView, 1, 1, Qt.LeftButton, Qt.NoModifier, 1000) + verify(tableView.selectionModel.hasSelection) + compare(tableView.selectionModel.selectedIndexes.length, 1) + verify(tableView.selectionModel.isSelected(tableView.model.index(0, 0))) + + compare(draggingSpy.count, 0) + compare(activeSpy.count, 1) + + // Remove selection + mouseClick(tableView, 1, 1, Qt.LeftButton) + verify(!tableView.selectionModel.hasSelection) + compare(draggingSpy.count, 0) + compare(activeSpy.count, 2) + + // Ensure that a drag doesn't start a selection + mouseDrag(tableView, 1, 1, (cellWidth * 2) - 2, 1, Qt.LeftButton) + verify(!tableView.selectionModel.hasSelection) + } + + function test_handleDragTopLeft() { + let tableView = createTemporaryObject(tableviewComp, testCase) + verify(tableView) + let selectionRectangle = tableView.selectionRectangle + verify(selectionRectangle) + + selectionRectangle.selectionMode = SelectionRectangle.Drag + + verify(!tableView.selectionModel.hasSelection) + // Select four cells in the middle + mouseDrag(tableView, cellWidth + 1, cellHeight + 1, (cellWidth * 2) - 2, (cellHeight * 2) - 2, Qt.LeftButton) + compare(tableView.selectionModel.selectedIndexes.length, 4) + for (var x = 1; x < 3; ++x) { + for (var y = 1; y < 3; ++y) { + verify(tableView.selectionModel.isSelected(tableView.model.index(x, y))) + } + } + + // Drag on the top left handle, so that the selection extends to cell 0, 0 + mouseDrag(tableView, cellWidth, cellHeight, -cellWidth / 2, -cellHeight / 2, Qt.LeftButton) + compare(tableView.selectionModel.selectedIndexes.length, 9) + for (x = 0; x < 3; ++x) { + for (y = 0; y < 3; ++y) { + verify(tableView.selectionModel.isSelected(tableView.model.index(x, y))) + } + } + } + + function test_handleDragBottomRight() { + let tableView = createTemporaryObject(tableviewComp, testCase) + verify(tableView) + let selectionRectangle = tableView.selectionRectangle + verify(selectionRectangle) + + selectionRectangle.selectionMode = SelectionRectangle.Drag + + verify(!tableView.selectionModel.hasSelection) + // Select four cells in the middle + mouseDrag(tableView, cellWidth + 1, cellHeight + 1, (cellWidth * 2) - 2, (cellHeight * 2) - 2, Qt.LeftButton) + compare(tableView.selectionModel.selectedIndexes.length, 4) + for (var x = 1; x < 3; ++x) { + for (var y = 1; y < 3; ++y) { + verify(tableView.selectionModel.isSelected(tableView.model.index(x, y))) + } + } + + // Drag on the bottom right handle, so that the selection shrinks to cell 1, 1 + mouseDrag(tableView, cellWidth * 2, cellHeight * 2, -cellWidth / 2, -cellHeight / 2, Qt.LeftButton) + compare(tableView.selectionModel.selectedIndexes.length, 1) + verify(tableView.selectionModel.isSelected(tableView.model.index(1, 1))) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_slider.qml b/tests/auto/quickcontrols2/controls/data/tst_slider.qml new file mode 100644 index 0000000000..d105473c38 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_slider.qml @@ -0,0 +1,940 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "Slider" + + Component { + id: signalSpy + SignalSpy { } + } + + Component { + id: slider + Slider { } + } + + function test_defaults() { + var control = createTemporaryObject(slider, testCase) + verify(control) + + compare(control.stepSize, 0) + compare(control.snapMode, Slider.NoSnap) + compare(control.orientation, Qt.Horizontal) + compare(control.horizontal, true) + compare(control.vertical, false) + } + + function test_value() { + var control = createTemporaryObject(slider, testCase) + verify(control) + + compare(control.value, 0.0) + control.value = 0.5 + compare(control.value, 0.5) + control.value = 1.0 + compare(control.value, 1.0) + control.value = -1.0 + compare(control.value, 0.0) + control.value = 2.0 + compare(control.value, 1.0) + } + + function test_range() { + var control = createTemporaryObject(slider, testCase, {from: 0, to: 100, value: 50}) + verify(control) + + compare(control.from, 0) + compare(control.to, 100) + compare(control.value, 50) + compare(control.position, 0.5) + + control.value = 1000 + compare(control.value, 100) + compare(control.position, 1) + + control.value = -1 + compare(control.value, 0) + compare(control.position, 0) + + control.from = 25 + compare(control.from, 25) + compare(control.value, 25) + compare(control.position, 0) + + control.to = 75 + compare(control.to, 75) + compare(control.value, 25) + compare(control.position, 0) + + control.value = 50 + compare(control.value, 50) + compare(control.position, 0.5) + } + + function test_inverted() { + var control = createTemporaryObject(slider, testCase, {from: 1.0, to: -1.0}) + verify(control) + + compare(control.from, 1.0) + compare(control.to, -1.0) + compare(control.value, 0.0) + compare(control.position, 0.5) + + control.value = 2.0 + compare(control.value, 1.0) + compare(control.position, 0.0) + + control.value = -2.0 + compare(control.value, -1.0) + compare(control.position, 1.0) + + control.value = 0.0 + compare(control.value, 0.0) + compare(control.position, 0.5) + } + + function test_position() { + var control = createTemporaryObject(slider, testCase) + verify(control) + + compare(control.value, 0.0) + compare(control.position, 0.0) + + control.value = 0.25 + compare(control.value, 0.25) + compare(control.position, 0.25) + + control.value = 0.75 + compare(control.value, 0.75) + compare(control.position, 0.75) + } + + function test_visualPosition() { + var control = createTemporaryObject(slider, testCase) + verify(control) + + compare(control.value, 0.0) + compare(control.visualPosition, 0.0) + + control.value = 0.25 + compare(control.value, 0.25) + compare(control.visualPosition, 0.25) + + // RTL locale + control.locale = Qt.locale("ar_EG") + compare(control.visualPosition, 0.25) + + // RTL locale + LayoutMirroring + control.LayoutMirroring.enabled = true + compare(control.visualPosition, 0.75) + + // LTR locale + LayoutMirroring + control.locale = Qt.locale("en_US") + compare(control.visualPosition, 0.75) + + // LTR locale + control.LayoutMirroring.enabled = false + compare(control.visualPosition, 0.25) + + // LayoutMirroring + control.LayoutMirroring.enabled = true + compare(control.visualPosition, 0.75) + } + + function test_orientation() { + var control = createTemporaryObject(slider, testCase) + verify(control) + + compare(control.orientation, Qt.Horizontal) + compare(control.horizontal, true) + compare(control.vertical, false) + verify(control.width > control.height) + + control.orientation = Qt.Vertical + compare(control.orientation, Qt.Vertical) + compare(control.horizontal, false) + compare(control.vertical, true) + verify(control.width < control.height) + } + + function test_mouse_data() { + return [ + { tag: "horizontal", orientation: Qt.Horizontal, live: false }, + { tag: "vertical", orientation: Qt.Vertical, live: false }, + { tag: "horizontal:live", orientation: Qt.Horizontal, live: true }, + { tag: "vertical:live", orientation: Qt.Vertical, live: true } + ] + } + + function test_mouse(data) { + var control = createTemporaryObject(slider, testCase, {orientation: data.orientation, live: data.live}) + verify(control) + + var pressedCount = 0 + var movedCount = 0 + + var pressedSpy = signalSpy.createObject(control, {target: control, signalName: "pressedChanged"}) + verify(pressedSpy.valid) + + var movedSpy = signalSpy.createObject(control, {target: control, signalName: "moved"}) + verify(movedSpy.valid) + + mousePress(control, 0, control.height - 1, Qt.LeftButton) + compare(pressedSpy.count, ++pressedCount) + compare(movedSpy.count, movedCount) + compare(control.pressed, true) + compare(control.value, 0.0) + compare(control.position, 0.0) + + // mininum on the left in horizontal vs. at the bottom in vertical + mouseMove(control, -control.width, 2 * control.height, 0) + compare(pressedSpy.count, pressedCount) + compare(movedSpy.count, movedCount) + compare(control.pressed, true) + compare(control.value, 0.0) + compare(control.position, 0.0) + + mouseMove(control, control.width * 0.5, control.height * 0.5, 0) + compare(pressedSpy.count, pressedCount) + compare(movedSpy.count, ++movedCount) + compare(control.pressed, true) + compare(control.value, data.live ? 0.5 : 0.0) + compare(control.position, 0.5) + + mouseRelease(control, control.width * 0.5, control.height * 0.5, Qt.LeftButton) + compare(pressedSpy.count, ++pressedCount) + compare(movedSpy.count, movedCount) + compare(control.pressed, false) + compare(control.value, 0.5) + compare(control.position, 0.5) + + mousePress(control, control.width - 1, 0, Qt.LeftButton) + compare(pressedSpy.count, ++pressedCount) + compare(movedSpy.count, ++movedCount) + compare(control.pressed, true) + compare(control.value, data.live ? 1.0 : 0.5) + compare(control.position, 1.0) + + // maximum on the right in horizontal vs. at the top in vertical + mouseMove(control, control.width * 2, -control.height, 0) + compare(pressedSpy.count, pressedCount) + compare(movedSpy.count, movedCount) + compare(control.pressed, true) + compare(control.value, data.live ? 1.0 : 0.5) + compare(control.position, 1.0) + + mouseMove(control, control.width * 0.75, control.height * 0.25, 0) + compare(pressedSpy.count, pressedCount) + compare(movedSpy.count, ++movedCount) + compare(control.pressed, true) + compare(control.value, data.live ? control.position : 0.5) + verify(control.position >= 0.75) + + mouseRelease(control, control.width * 0.25, control.height * 0.75, Qt.LeftButton) + compare(pressedSpy.count, ++pressedCount) + compare(movedSpy.count, ++movedCount) + compare(control.pressed, false) + compare(control.value, control.position) + verify(control.value <= 0.25 && control.value >= 0.0) + verify(control.position <= 0.25 && control.position >= 0.0) + + // QTBUG-53846 + mouseClick(control, control.width * 0.5, control.height * 0.5, Qt.LeftButton) + compare(movedSpy.count, ++movedCount) + compare(pressedSpy.count, pressedCount += 2) + compare(control.value, 0.5) + compare(control.position, 0.5) + } + + function test_touch_data() { + return [ + { tag: "horizontal", orientation: Qt.Horizontal, live: false }, + { tag: "vertical", orientation: Qt.Vertical, live: false }, + { tag: "horizontal:live", orientation: Qt.Horizontal, live: true }, + { tag: "vertical:live", orientation: Qt.Vertical, live: true } + ] + } + + function test_touch(data) { + var control = createTemporaryObject(slider, testCase, {orientation: data.orientation, live: data.live}) + verify(control) + + var pressedCount = 0 + var movedCount = 0 + + var pressedSpy = signalSpy.createObject(control, {target: control, signalName: "pressedChanged"}) + verify(pressedSpy.valid) + + var movedSpy = signalSpy.createObject(control, {target: control, signalName: "moved"}) + verify(movedSpy.valid) + + var touch = touchEvent(control) + touch.press(0, control, 0, 0).commit() + compare(pressedSpy.count, ++pressedCount) + compare(movedSpy.count, movedCount) + compare(control.pressed, true) + compare(control.value, 0.0) + compare(control.position, 0.0) + + // mininum on the left in horizontal vs. at the bottom in vertical + touch.move(0, control, -control.width, 2 * control.height, 0).commit() + compare(pressedSpy.count, pressedCount) + compare(movedSpy.count, movedCount) + compare(control.pressed, true) + compare(control.value, 0.0) + compare(control.position, 0.0) + + touch.move(0, control, control.width * 0.5, control.height * 0.5, 0).commit() + compare(pressedSpy.count, pressedCount) + compare(movedSpy.count, ++movedCount) + compare(control.pressed, true) + compare(control.value, data.live ? 0.5 : 0.0) + compare(control.position, 0.5) + + touch.release(0, control, control.width * 0.5, control.height * 0.5).commit() + compare(pressedSpy.count, ++pressedCount) + compare(movedSpy.count, movedCount) + compare(control.pressed, false) + compare(control.value, 0.5) + compare(control.position, 0.5) + + touch.press(0, control, control.width - 1, control.height - 1).commit() + compare(pressedSpy.count, ++pressedCount) + compare(movedSpy.count, movedCount) + compare(control.pressed, true) + compare(control.value, 0.5) + compare(control.position, 0.5) + + // maximum on the right in horizontal vs. at the top in vertical + touch.move(0, control, control.width * 2, -control.height, 0).commit() + compare(pressedSpy.count, pressedCount) + compare(movedSpy.count, ++movedCount) + compare(control.pressed, true) + compare(control.value, data.live ? 1.0 : 0.5) + compare(control.position, 1.0) + + touch.move(0, control, control.width * 0.75, control.height * 0.25, 0).commit() + compare(pressedSpy.count, pressedCount) + compare(movedSpy.count, ++movedCount) + compare(control.pressed, true) + compare(control.value, data.live ? control.position : 0.5) + verify(control.position >= 0.75) + + touch.release(0, control, control.width * 0.25, control.height * 0.75).commit() + compare(pressedSpy.count, ++pressedCount) + compare(movedSpy.count, ++movedCount) + compare(control.pressed, false) + compare(control.value, control.position) + verify(control.value <= 0.25 && control.value >= 0.0) + verify(control.position <= 0.25 && control.position >= 0.0) + + // QTBUG-53846 + touch.press(0, control).commit().release(0, control).commit() + compare(movedSpy.count, ++movedCount) + compare(pressedSpy.count, pressedCount += 2) + compare(control.value, 0.5) + compare(control.position, 0.5) + } + + function test_multiTouch() { + var control1 = createTemporaryObject(slider, testCase, {live: false}) + verify(control1) + + var pressedCount1 = 0 + var movedCount1 = 0 + + var pressedSpy1 = signalSpy.createObject(control1, {target: control1, signalName: "pressedChanged"}) + verify(pressedSpy1.valid) + + var movedSpy1 = signalSpy.createObject(control1, {target: control1, signalName: "moved"}) + verify(movedSpy1.valid) + + var touch = touchEvent(control1) + touch.press(0, control1, 0, 0).commit().move(0, control1, control1.width, control1.height).commit() + + compare(pressedSpy1.count, ++pressedCount1) + compare(movedSpy1.count, ++movedCount1) + compare(control1.pressed, true) + compare(control1.position, 1.0) + + // second touch point on the same control is ignored + touch.stationary(0).press(1, control1, 0, 0).commit() + touch.stationary(0).move(1, control1).commit() + touch.stationary(0).release(1).commit() + + compare(pressedSpy1.count, pressedCount1) + compare(movedSpy1.count, movedCount1) + compare(control1.pressed, true) + compare(control1.position, 1.0) + + var control2 = createTemporaryObject(slider, testCase, {y: control1.height, live: false}) + verify(control2) + + var pressedCount2 = 0 + var movedCount2 = 0 + + var pressedSpy2 = signalSpy.createObject(control2, {target: control2, signalName: "pressedChanged"}) + verify(pressedSpy2.valid) + + var movedSpy2 = signalSpy.createObject(control2, {target: control2, signalName: "moved"}) + verify(movedSpy2.valid) + + // press the second slider + touch.stationary(0).press(2, control2, 0, 0).commit() + + compare(pressedSpy2.count, ++pressedCount2) + compare(movedSpy2.count, movedCount2) + compare(control2.pressed, true) + compare(control2.position, 0.0) + + compare(pressedSpy1.count, pressedCount1) + compare(movedSpy1.count, movedCount1) + compare(control1.pressed, true) + compare(control1.position, 1.0) + + // move both sliders + touch.move(0, control1).move(2, control2).commit() + + compare(pressedSpy2.count, pressedCount2) + compare(movedSpy2.count, ++movedCount2) + compare(control2.pressed, true) + compare(control2.position, 0.5) + compare(control2.value, 0.0) + + compare(pressedSpy1.count, pressedCount1) + compare(movedSpy1.count, ++movedCount1) + compare(control1.pressed, true) + compare(control1.position, 0.5) + compare(control1.value, 0.0) + + // release both sliders + touch.release(0, control1).release(2, control2).commit() + + compare(pressedSpy2.count, ++pressedCount2) + compare(movedSpy2.count, movedCount2) + compare(control2.pressed, false) + compare(control2.position, 0.5) + compare(control2.value, 0.5) + + compare(pressedSpy1.count, ++pressedCount1) + compare(movedSpy1.count, movedCount1) + compare(control1.pressed, false) + compare(control1.position, 0.5) + compare(control1.value, 0.5) + } + + function test_keys_data() { + return [ + { tag: "horizontal", orientation: Qt.Horizontal, decrease: Qt.Key_Left, increase: Qt.Key_Right }, + { tag: "vertical", orientation: Qt.Vertical, decrease: Qt.Key_Down, increase: Qt.Key_Up } + ] + } + + function test_keys(data) { + var control = createTemporaryObject(slider, testCase, {orientation: data.orientation}) + verify(control) + + var pressedCount = 0 + var movedCount = 0 + + var pressedSpy = signalSpy.createObject(control, {target: control, signalName: "pressedChanged"}) + verify(pressedSpy.valid) + + var movedSpy = signalSpy.createObject(control, {target: control, signalName: "moved"}) + verify(movedSpy.valid) + + control.forceActiveFocus() + verify(control.activeFocus) + + var oldValue = 0.0 + control.value = 0.5 + + for (var d1 = 1; d1 <= 10; ++d1) { + oldValue = control.value + keyPress(data.decrease) + compare(control.pressed, true) + compare(pressedSpy.count, ++pressedCount) + if (oldValue !== control.value) + compare(movedSpy.count, ++movedCount) + + compare(control.value, Math.max(0.0, 0.5 - d1 * 0.1)) + compare(control.value, control.position) + + keyRelease(data.decrease) + compare(control.pressed, false) + compare(pressedSpy.count, ++pressedCount) + compare(movedSpy.count, movedCount) + } + + for (var i1 = 1; i1 <= 20; ++i1) { + oldValue = control.value + keyPress(data.increase) + compare(control.pressed, true) + compare(pressedSpy.count, ++pressedCount) + if (oldValue !== control.value) + compare(movedSpy.count, ++movedCount) + + compare(control.value, Math.min(1.0, 0.0 + i1 * 0.1)) + compare(control.value, control.position) + + keyRelease(data.increase) + compare(control.pressed, false) + compare(pressedSpy.count, ++pressedCount) + compare(movedSpy.count, movedCount) + } + + control.stepSize = 0.25 + + for (var d2 = 1; d2 <= 10; ++d2) { + oldValue = control.value + keyPress(data.decrease) + compare(control.pressed, true) + compare(pressedSpy.count, ++pressedCount) + if (oldValue !== control.value) + compare(movedSpy.count, ++movedCount) + + compare(control.value, Math.max(0.0, 1.0 - d2 * 0.25)) + compare(control.value, control.position) + + keyRelease(data.decrease) + compare(control.pressed, false) + compare(pressedSpy.count, ++pressedCount) + compare(movedSpy.count, movedCount) + } + + for (var i2 = 1; i2 <= 10; ++i2) { + oldValue = control.value + keyPress(data.increase) + compare(control.pressed, true) + compare(pressedSpy.count, ++pressedCount) + if (oldValue !== control.value) + compare(movedSpy.count, ++movedCount) + + compare(control.value, Math.min(1.0, 0.0 + i2 * 0.25)) + compare(control.value, control.position) + + keyRelease(data.increase) + compare(control.pressed, false) + compare(pressedSpy.count, ++pressedCount) + compare(movedSpy.count, movedCount) + } + } + + function test_padding() { + // test with "unbalanced" paddings (left padding != right padding) to ensure + // that the slider position calculation is done taking padding into account + // ==> the position is _not_ 0.5 in the middle of the control + var control = createTemporaryObject(slider, testCase, {leftPadding: 10, rightPadding: 20, live: false}) + verify(control) + + var pressedSpy = signalSpy.createObject(control, {target: control, signalName: "pressedChanged"}) + verify(pressedSpy.valid) + + mousePress(control, 0, 0, Qt.LeftButton) + compare(pressedSpy.count, 1) + compare(control.pressed, true) + compare(control.value, 0.0) + compare(control.position, 0.0) + compare(control.visualPosition, 0.0) + + mouseMove(control, control.leftPadding + control.availableWidth * 0.5, control.height * 0.5, 0) + compare(pressedSpy.count, 1) + compare(control.pressed, true) + compare(control.value, 0.0) + compare(control.position, 0.5) + compare(control.visualPosition, 0.5) + + mouseMove(control, control.width * 0.5, control.height * 0.5, 0) + compare(pressedSpy.count, 1) + compare(control.pressed, true) + compare(control.value, 0.0) + verify(control.position > 0.5) + verify(control.visualPosition > 0.5) + + mouseRelease(control, control.leftPadding + control.availableWidth * 0.5, control.height * 0.5, Qt.LeftButton) + compare(pressedSpy.count, 2) + compare(control.pressed, false) + compare(control.value, 0.5) + compare(control.position, 0.5) + compare(control.visualPosition, 0.5) + + // RTL + control.value = 0 + control.locale = Qt.locale("ar_EG") + + mousePress(control, 0, 0, Qt.LeftButton) + compare(pressedSpy.count, 3) + compare(control.pressed, true) + compare(control.value, 0.0) + compare(control.position, 0.0) + compare(control.visualPosition, 0.0) + + mouseMove(control, control.leftPadding + control.availableWidth * 0.5, control.height * 0.5, 0) + compare(pressedSpy.count, 3) + compare(control.pressed, true) + compare(control.value, 0.0) + compare(control.position, 0.5) + compare(control.visualPosition, 0.5) + + mouseMove(control, control.width * 0.5, control.height * 0.5, 0) + compare(pressedSpy.count, 3) + compare(control.pressed, true) + compare(control.value, 0.0) + verify(control.position > 0.5) + verify(control.visualPosition > 0.5) + + mouseRelease(control, control.leftPadding + control.availableWidth * 0.5, control.height * 0.5, Qt.LeftButton) + compare(pressedSpy.count, 4) + compare(control.pressed, false) + compare(control.value, 0.5) + compare(control.position, 0.5) + compare(control.visualPosition, 0.5) + } + + function calcMousePos(control, t) { + t = Math.min(Math.max(t, 0.0), 1.0); + return control.leftPadding + control.handle.width * 0.5 + t * (control.availableWidth - control.handle.width) + } + + function snapModeData(immediate) { + return [ + { tag: "NoSnap", snapMode: Slider.NoSnap, from: 0, to: 2, values: [0, 0, 0.25], positions: [0, 0.1, 0.1] }, + { tag: "SnapAlways (0..2)", snapMode: Slider.SnapAlways, from: 0, to: 2, values: [0.0, 0.0, 0.2], positions: [0.0, 0.1, 0.1] }, + { tag: "SnapAlways (1..3)", snapMode: Slider.SnapAlways, from: 1, to: 3, values: [1.0, 1.0, 1.2], positions: [0.0, 0.1, 0.1] }, + { tag: "SnapAlways (-1..1)", snapMode: Slider.SnapAlways, from: -1, to: 1, values: [0.0, 0.0, -0.8], positions: [immediate ? 0.0 : 0.5, 0.1, 0.1] }, + { tag: "SnapAlways (1..-1)", snapMode: Slider.SnapAlways, from: 1, to: -1, values: [0.0, 0.0, 0.8], positions: [immediate ? 0.0 : 0.5, 0.1, 0.1] }, + { tag: "SnapOnRelease (0..2)", snapMode: Slider.SnapOnRelease, from: 0, to: 2, values: [0.0, 0.0, 0.2], positions: [0.0, 0.1, 0.1] }, + { tag: "SnapOnRelease (1..3)", snapMode: Slider.SnapOnRelease, from: 1, to: 3, values: [1.0, 1.0, 1.2], positions: [0.0, 0.1, 0.1] }, + { tag: "SnapOnRelease (-1..1)", snapMode: Slider.SnapOnRelease, from: -1, to: 1, values: [0.0, 0.0, -0.8], positions: [immediate ? 0.0 : 0.5, 0.1, 0.1] }, + { tag: "SnapOnRelease (1..-1)", snapMode: Slider.SnapOnRelease, from: 1, to: -1, values: [0.0, 0.0, 0.8], positions: [immediate ? 0.0 : 0.5, 0.1, 0.1] }, + // Live + { tag: "SnapAlwaysLive", snapMode: Slider.SnapAlways, from: 0, to: 1, value: 0, stepSize: 1, live: true, sliderPos: 0.6, values: [0, 1, 1], positions: [0, 1, 1] }, + { tag: "SnapAlwaysLive", snapMode: Slider.SnapAlways, from: 0, to: 1, value: 0, stepSize: 1, live: true, sliderPos: 0.4, values: [0, 0, 0], positions: [0, 0, 0] }, + { tag: "NoSnapLive", snapMode: Slider.NoSnap, from: 0, to: 1, value: 0, stepSize: 1, live: true, sliderPos: 0.6, values: [0, 1, 1], positions: [0, 0.6, 0.6] }, + { tag: "NoSnapLive", snapMode: Slider.NoSnap, from: 0, to: 1, value: 0, stepSize: 1, live: true, sliderPos: 0.4, values: [0, 0, 0], positions: [0, 0.4, 0.4] }, + { tag: "SnapOnReleaseLive", snapMode: Slider.SnapOnRelease, from: 0, to: 1, value: 0, stepSize: 1, live: true, sliderPos: 0.6, values: [0, 1, 1], positions: [0, 0.6, 1] }, + { tag: "SnapOnReleaseLive", snapMode: Slider.SnapOnRelease, from: 0, to: 1, value: 0, stepSize: 1, live: true, sliderPos: 0.4, values: [0, 0, 0], positions: [0, 0.4, 0] }, + ] + } + + function testSnapMode(data, useMouse) { + let live = data.live !== undefined ? data.live : false + let stepSize = data.stepSize !== undefined ? data.stepSize : 0.2 + let sliderPos = data.sliderPos !== undefined ? data.sliderPos : 0.1 + let fuzz = 0.05 + + var control = createTemporaryObject(slider, testCase, {live: live, snapMode: data.snapMode, from: data.from, to: data.to, stepSize: stepSize}) + verify(control) + var touch = useMouse ? null : touchEvent(control) + + if (useMouse) + mousePress(control, calcMousePos(control, 0.0)) + else + touch.press(0, control, calcMousePos(control, 0.0)).commit() + + fuzzyCompare(control.value, data.values[0], fuzz) + fuzzyCompare(control.position, data.positions[0], fuzz) + + if (useMouse) + mouseMove(control, calcMousePos(control, sliderPos)) + else + touch.move(0, control, calcMousePos(control, sliderPos)).commit() + + fuzzyCompare(control.value, data.values[1], fuzz) + fuzzyCompare(control.position, data.positions[1], fuzz) + + if (useMouse) + mouseRelease(control, calcMousePos(control, sliderPos)) + else + touch.release(0, control, calcMousePos(control, sliderPos)).commit() + + fuzzyCompare(control.value, data.values[2], fuzz) + fuzzyCompare(control.position, data.positions[2], fuzz) + } + + function test_snapMode_touch_data() { + return snapModeData(false) + } + + function test_snapMode_touch(data) { + return testSnapMode(data, false) + } + + function test_snapMode_mouse_data() { + return snapModeData(true) + } + + function test_snapMode_mouse(data) { + return testSnapMode(data, true) + } + + function test_wheel_data() { + return [ + { tag: "horizontal", orientation: Qt.Horizontal, dx: 120, dy: 0 }, + { tag: "vertical", orientation: Qt.Vertical, dx: 0, dy: 120 } + ] + } + + function test_wheel(data) { + var control = createTemporaryObject(slider, testCase, {wheelEnabled: true, orientation: data.orientation}) + verify(control) + + var movedCount = 0 + var movedSpy = signalSpy.createObject(control, {target: control, signalName: "moved"}) + verify(movedSpy.valid) + + compare(control.value, 0.0) + + mouseWheel(control, control.width / 2, control.height / 2, data.dx, data.dy) + compare(movedSpy.count, ++movedCount) + compare(control.value, 0.1) + compare(control.position, 0.1) + + control.stepSize = 0.2 + + mouseWheel(control, control.width / 2, control.height / 2, data.dx, data.dy) + compare(movedSpy.count, ++movedCount) + compare(control.value, 0.3) + compare(control.position, 0.3) + + control.stepSize = 10.0 + + mouseWheel(control, control.width / 2, control.height / 2, -data.dx, -data.dy) + compare(movedSpy.count, ++movedCount) + compare(control.value, 0.0) + compare(control.position, 0.0) + + // no change + mouseWheel(control, control.width / 2, control.height / 2, -data.dx, -data.dy) + compare(movedSpy.count, movedCount) + compare(control.value, 0.0) + compare(control.position, 0.0) + + control.to = 10.0 + control.stepSize = 5.0 + + mouseWheel(control, control.width / 2, control.height / 2, data.dx, data.dy) + compare(movedSpy.count, ++movedCount) + compare(control.value, 5.0) + compare(control.position, 0.5) + + mouseWheel(control, control.width / 2, control.height / 2, 0.5 * data.dx, 0.5 * data.dy) + compare(movedSpy.count, ++movedCount) + compare(control.value, 7.5) + compare(control.position, 0.75) + + mouseWheel(control, control.width / 2, control.height / 2, -data.dx, -data.dy) + compare(movedSpy.count, ++movedCount) + compare(control.value, 2.5) + compare(control.position, 0.25) + } + + function test_wheelPropagation_data() { + return [ + { tag: "horizontal", orientation: Qt.Horizontal, dx: 120, dy: 0 }, + { tag: "vertical", orientation: Qt.Vertical, dx: 0, dy: 120 } + ] + } + + Component { + id: mouseAreaComponent + MouseArea {} + } + + function test_wheelPropagation(data) { + var mouseArea = createTemporaryObject(mouseAreaComponent, testCase, { width: parent.width, height: parent.height }) + verify(mouseArea) + + var mouseAreaWheelSpy = signalSpy.createObject(mouseArea, { target: mouseArea, signalName: "wheel" }) + verify(mouseAreaWheelSpy.valid) + + var control = createTemporaryObject(slider, mouseArea, + { wheelEnabled: true, orientation: data.orientation, stepSize: 1 }) + verify(control) + compare(control.value, 0.0) + + var movedCount = 0 + var movedSpy = signalSpy.createObject(control, { target: control, signalName: "moved" }) + verify(movedSpy.valid) + + // Scroll the handle to the edge. + mouseWheel(control, control.width / 2, control.height / 2, data.dx, data.dy) + compare(control.value, 1.0) + compare(control.position, 1.0) + compare(movedSpy.count, ++movedCount) + compare(mouseAreaWheelSpy.count, 0) + + // Scroll again; the wheel event shouldn't go through to the MouseArea parent. + mouseWheel(control, control.width / 2, control.height / 2, data.dx, data.dy) + compare(control.value, 1.0) + compare(control.position, 1.0) + compare(movedSpy.count, movedCount) + compare(mouseAreaWheelSpy.count, 0) + + // Scroll the handle to the other edge. + mouseWheel(control, control.width / 2, control.height / 2, -data.dx, -data.dy) + compare(control.value, 0.0) + compare(control.position, 0.0) + compare(movedSpy.count, ++movedCount) + compare(mouseAreaWheelSpy.count, 0) + + // Scroll again; the wheel event shouldn't go through to the MouseArea parent. + mouseWheel(control, control.width / 2, control.height / 2, -data.dx, -data.dy) + compare(control.value, 0.0) + compare(control.position, 0.0) + compare(movedSpy.count, movedCount) + compare(mouseAreaWheelSpy.count, 0) + } + + function test_valueAt_data() { + return [ + { tag: "0.0..1.0", properties: { from: 0.0, to: 1.0 }, values: [0.0, 0.2, 0.5, 1.0] }, + { tag: "0..100", properties: { from: 0, to: 100 }, values: [0, 20, 50, 100] }, + { tag: "100..-100", properties: { from: 100, to: -100 }, values: [100, 60, 0, -100] }, + { tag: "-7..7", properties: { from: -7, to: 7, stepSize: 1.0 }, values: [-7.0, -4.0, 0.0, 7.0] }, + { tag: "-3..7", properties: { from: -3, to: 7, stepSize: 5.0 }, values: [-3.0, -3.0, 2.0, 7.0] }, + ] + } + + function test_valueAt(data) { + let control = createTemporaryObject(slider, testCase, data.properties) + verify(control) + + compare(control.valueAt(0.0), data.values[0]) + compare(control.valueAt(0.2), data.values[1]) + compare(control.valueAt(0.5), data.values[2]) + compare(control.valueAt(1.0), data.values[3]) + } + + function test_nullHandle() { + var control = createTemporaryObject(slider, testCase) + verify(control) + + control.handle = null + + mousePress(control) + verify(control.pressed, true) + + mouseRelease(control) + compare(control.pressed, false) + } + + function test_touchDragThreshold_data() { + var d1 = 3; var d2 = 7; + return [ + { tag: "horizontal", orientation: Qt.Horizontal, dx1: d1, dy1: 0, dx2: d2, dy2: 0 }, + { tag: "vertical", orientation: Qt.Vertical, dx1: 0, dy1: -d1, dx2: 0, dy2: -d2 }, + { tag: "horizontal2", orientation: Qt.Horizontal, dx1: -d1, dy1: 0, dx2: -d2, dy2: 0 }, + { tag: "vertical2", orientation: Qt.Vertical, dx1: 0, dy1: d1, dx2: 0, dy2: d2 } + ] + } + + function test_touchDragThreshold(data) { + var control = createTemporaryObject(slider, testCase, {touchDragThreshold: 10, live: true, orientation: data.orientation, value: 0.5}) + verify(control) + compare(control.touchDragThreshold, 10) + + var valueChangedCount = 0 + var valueChangedSpy = signalSpy.createObject(control, {target: control, signalName: "touchDragThresholdChanged"}) + verify(valueChangedSpy.valid) + + control.touchDragThreshold = undefined + compare(control.touchDragThreshold, -1) // reset to -1 + compare(valueChangedSpy.count, ++valueChangedCount) + + var t = 5 + control.touchDragThreshold = t + compare(control.touchDragThreshold, t) + compare(valueChangedSpy.count, ++valueChangedCount) + + control.touchDragThreshold = t + compare(control.touchDragThreshold, t) + compare(valueChangedSpy.count, valueChangedCount) + + var pressedCount = 0 + var movedCount = 0 + + var pressedSpy = signalSpy.createObject(control, {target: control, signalName: "pressedChanged"}) + verify(pressedSpy.valid) + + var movedSpy = signalSpy.createObject(control, {target: control, signalName: "moved"}) + verify(movedSpy.valid) + + var touch = touchEvent(control) + var x0 = control.handle.x + control.handle.width * 0.5 + var y0 = control.handle.y + control.handle.height * 0.5 + touch.press(0, control, x0, y0).commit() + compare(pressedSpy.count, ++pressedCount) + compare(movedSpy.count, movedCount) + compare(control.pressed, true) + + touch.move(0, control, x0 + data.dx1, y0 + data.dy1).commit() + compare(pressedSpy.count, pressedCount) + compare(movedSpy.count, movedCount) // shouldn't move + compare(control.pressed, true) + + touch.move(0, control, x0 + data.dx2, y0 + data.dy2).commit() + compare(pressedSpy.count, pressedCount) + compare(movedSpy.count, ++movedCount) + compare(control.pressed, true) + + touch.release(0, control, x0 + data.dx2, y0 + data.dy2).commit() + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_spinbox.qml b/tests/auto/quickcontrols2/controls/data/tst_spinbox.qml new file mode 100644 index 0000000000..6220a857b3 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_spinbox.qml @@ -0,0 +1,715 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls +import QtQuick.Window + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "SpinBox" + + Component { + id: signalSpy + SignalSpy { } + } + + Component { + id: spinBox + SpinBox { } + } + + Component { + id: mouseArea + MouseArea { } + } + + function test_defaults() { + var control = createTemporaryObject(spinBox, testCase) + verify(control) + + compare(control.from, 0) + compare(control.to, 99) + compare(control.value, 0) + compare(control.stepSize, 1) + compare(control.editable, false) + compare(control.up.pressed, false) + compare(control.up.indicator.enabled, true) + compare(control.down.pressed, false) + compare(control.down.indicator.enabled, false) + } + + function test_value() { + var control = createTemporaryObject(spinBox, testCase) + verify(control) + + compare(control.value, 0) + control.value = 50 + compare(control.value, 50) + control.value = 99 + compare(control.value, 99) + control.value = -99 + compare(control.value, 0) + control.value = 100 + compare(control.value, 99) + } + + function test_range() { + var control = createTemporaryObject(spinBox, testCase, {from: 0, to: 100, value: 50}) + verify(control) + + compare(control.from, 0) + compare(control.to, 100) + compare(control.value, 50) + compare(control.up.indicator.enabled, true) + compare(control.down.indicator.enabled, true) + + control.value = 1000 + compare(control.value, 100) + compare(control.up.indicator.enabled, false) + compare(control.down.indicator.enabled, true) + + control.wrap = true + compare(control.up.indicator.enabled, true) + compare(control.down.indicator.enabled, true) + + control.value = -1 + compare(control.value, 0) + compare(control.up.indicator.enabled, true) + compare(control.down.indicator.enabled, true) + + control.from = 25 + compare(control.from, 25) + compare(control.value, 25) + compare(control.up.indicator.enabled, true) + compare(control.down.indicator.enabled, true) + + control.wrap = false + compare(control.up.indicator.enabled, true) + compare(control.down.indicator.enabled, false) + + control.value = 30 + compare(control.from, 25) + compare(control.value, 30) + compare(control.up.indicator.enabled, true) + compare(control.down.indicator.enabled, true) + + control.from = 30 + compare(control.from, 30) + compare(control.value, 30) + compare(control.up.indicator.enabled, true) + compare(control.down.indicator.enabled, false) + + control.to = 75 + compare(control.to, 75) + compare(control.value, 30) + compare(control.up.indicator.enabled, true) + compare(control.down.indicator.enabled, false) + + control.value = 50 + compare(control.value, 50) + compare(control.up.indicator.enabled, true) + compare(control.down.indicator.enabled, true) + + control.to = 50 + compare(control.to, 50) + compare(control.value, 50) + compare(control.up.indicator.enabled, false) + compare(control.down.indicator.enabled, true) + + control.to = 40 + compare(control.to, 40) + compare(control.value, 40) + compare(control.up.indicator.enabled, false) + compare(control.down.indicator.enabled, true) + } + + function test_inverted() { + var control = createTemporaryObject(spinBox, testCase, {from: 100, to: -100}) + verify(control) + + compare(control.from, 100) + compare(control.to, -100) + compare(control.value, 0) + compare(control.up.indicator.enabled, true) + compare(control.down.indicator.enabled, true) + + control.value = 200 + compare(control.value, 100) + compare(control.up.indicator.enabled, true) + compare(control.down.indicator.enabled, false) + + control.value = -200 + compare(control.value, -100) + compare(control.up.indicator.enabled, false) + compare(control.down.indicator.enabled, true) + + control.value = 0 + compare(control.value, 0) + compare(control.up.indicator.enabled, true) + compare(control.down.indicator.enabled, true) + } + + function test_mouse_data() { + return [ + { tag: "up", button: "up", value: 50, enabled: true, hold: false, modified: 1, expected: 51 }, + { tag: "down", button: "down", value: 50, enabled: true, hold: false, modified: 1, expected: 49 }, + { tag: "up:disabled", button: "up", value: 99, enabled: false, hold: false, modified: 0, expected: 99 }, + { tag: "down:disabled", button: "down", value: 0, enabled: false, hold: false, modified: 0, expected: 0 }, + { tag: "up:hold", button: "up", value: 95, enabled: true, hold: true, modified: 4, expected: 99 }, + { tag: "down:hold", button: "down", value: 5, enabled: true, hold: true, modified: 5, expected: 0 } + ] + } + + function test_mouse(data) { + var control = createTemporaryObject(spinBox, testCase, {value: data.value}) + verify(control) + + var button = control[data.button] + verify(button) + + var pressedSpy = signalSpy.createObject(control, {target: button, signalName: "pressedChanged"}) + verify(pressedSpy.valid) + + var valueModifiedSpy = signalSpy.createObject(control, {target: control, signalName: "valueModified"}) + verify(valueModifiedSpy.valid) + + mousePress(button.indicator) + compare(pressedSpy.count, data.enabled ? 1 : 0) + compare(button.pressed, data.enabled) + compare(control.value, data.value) + compare(valueModifiedSpy.count, 0) + + if (data.hold) + tryCompare(control, "value", data.expected) + + mouseRelease(button.indicator) + compare(pressedSpy.count, data.enabled ? 2 : 0) + compare(button.pressed, false) + compare(control.value, data.expected) + compare(valueModifiedSpy.count, data.modified) + } + + function test_keys_data() { + return [ + { tag: "1", properties: { from: 1, to: 10, value: 1, stepSize: 1 }, upSteps: [2,3,4], downSteps: [3,2,1,1] }, + { tag: "2", properties: { from: 1, to: 10, value: 10, stepSize: 2 }, upSteps: [10,10], downSteps: [8,6,4] }, + { tag: "25", properties: { from: 0, to: 100, value: 50, stepSize: 25 }, upSteps: [75,100,100], downSteps: [75,50,25,0,0] }, + { tag: "wrap1", properties: { wrap: true, from: 1, to: 10, value: 1, stepSize: 1 }, upSteps: [2,3], downSteps: [2,1,10,9] }, + { tag: "wrap2", properties: { wrap: true, from: 1, to: 10, value: 10, stepSize: 2 }, upSteps: [1,3,5], downSteps: [3,1,10,8,6] }, + { tag: "wrap25", properties: { wrap: true, from: 0, to: 100, value: 50, stepSize: 25 }, upSteps: [75,100,0,25], downSteps: [0,100,75] } + ] + } + + function test_keys(data) { + var control = createTemporaryObject(spinBox, testCase, data.properties) + verify(control) + + var upPressedCount = 0 + var downPressedCount = 0 + var valueModifiedCount = 0 + + var upPressedSpy = signalSpy.createObject(control, {target: control.up, signalName: "pressedChanged"}) + verify(upPressedSpy.valid) + + var downPressedSpy = signalSpy.createObject(control, {target: control.down, signalName: "pressedChanged"}) + verify(downPressedSpy.valid) + + var valueModifiedSpy = signalSpy.createObject(control, {target: control, signalName: "valueModified"}) + verify(valueModifiedSpy.valid) + + control.forceActiveFocus() + verify(control.activeFocus) + + for (var u = 0; u < data.upSteps.length; ++u) { + var wasUpEnabled = control.wrap || control.value < control.to + keyPress(Qt.Key_Up) + compare(control.up.pressed, wasUpEnabled) + compare(control.down.pressed, false) + if (wasUpEnabled) { + ++upPressedCount + ++valueModifiedCount + } + compare(upPressedSpy.count, upPressedCount) + compare(valueModifiedSpy.count, valueModifiedCount) + + compare(control.value, data.upSteps[u]) + + keyRelease(Qt.Key_Up) + compare(control.down.pressed, false) + compare(control.up.pressed, false) + if (wasUpEnabled) + ++upPressedCount + compare(upPressedSpy.count, upPressedCount) + compare(valueModifiedSpy.count, valueModifiedCount) + } + + for (var d = 0; d < data.downSteps.length; ++d) { + var wasDownEnabled = control.wrap || control.value > control.from + keyPress(Qt.Key_Down) + compare(control.down.pressed, wasDownEnabled) + compare(control.up.pressed, false) + if (wasDownEnabled) { + ++downPressedCount + ++valueModifiedCount + } + compare(downPressedSpy.count, downPressedCount) + compare(valueModifiedSpy.count, valueModifiedCount) + + compare(control.value, data.downSteps[d]) + + keyRelease(Qt.Key_Down) + compare(control.down.pressed, false) + compare(control.up.pressed, false) + if (wasDownEnabled) + ++downPressedCount + compare(downPressedSpy.count, downPressedCount) + compare(valueModifiedSpy.count, valueModifiedCount) + } + } + + function test_locale() { + var control = createTemporaryObject(spinBox, testCase) + verify(control) + + control.locale = Qt.locale("ar_EG") // Arabic, Egypt + + var numbers = ["Ù ", "Ù¡", "Ù¢", "Ù£", "Ù¤", "Ù¥", "Ù¦", "Ù§", "Ù¨", "Ù©"] + for (var i = 0; i < 10; ++i) { + control.value = i + compare(control.contentItem.text, numbers[i]) + } + } + + function test_baseline() { + var control = createTemporaryObject(spinBox, testCase) + verify(control) + compare(control.baselineOffset, control.contentItem.y + control.contentItem.baselineOffset) + } + + function test_focus() { + var control = createTemporaryObject(spinBox, testCase, {from: 10, to: 1000, value: 100, focus: true}) + verify(control) + + control.forceActiveFocus() + compare(control.activeFocus, true) + + compare(control.from, 10) + compare(control.to, 1000) + compare(control.value, 100) + + control.focus = false + compare(control.activeFocus, false) + + compare(control.from, 10) + compare(control.to, 1000) + compare(control.value, 100) + } + + function test_initialFocus() { + var window = testCase.Window.window + verify(window) + compare(window.activeFocusItem, window.contentItem) + + var control = createTemporaryObject(spinBox, testCase, { editable: true, focus: true }) + verify(control) + tryCompare(control.contentItem, "activeFocus", true) + } + + function test_editable() { + var control = createTemporaryObject(spinBox, testCase) + verify(control) + + var valueModifiedSpy = signalSpy.createObject(control, {target: control, signalName: "valueModified"}) + verify(valueModifiedSpy.valid) + + control.contentItem.forceActiveFocus() + compare(control.contentItem.activeFocus, true) + + compare(control.editable, false) + control.contentItem.selectAll() + keyClick(Qt.Key_5) + keyClick(Qt.Key_Return) + compare(control.value, 0) + compare(valueModifiedSpy.count, 0) + + control.editable = true + compare(control.editable, true) + control.contentItem.selectAll() + keyClick(Qt.Key_5) + keyClick(Qt.Key_Return) + compare(control.value, 5) + compare(valueModifiedSpy.count, 1) + } + + function test_wheel_data() { + return [ + { tag: "1", properties: { from: 1, to: 10, value: 1, stepSize: 1 }, upSteps: [2,3,4], downSteps: [3,2,1,1] }, + { tag: "2", properties: { from: 1, to: 10, value: 10, stepSize: 2 }, upSteps: [10,10], downSteps: [8,6,4] }, + { tag: "25", properties: { from: 0, to: 100, value: 50, stepSize: 25 }, upSteps: [75,100,100], downSteps: [75,50,25,0,0] }, + { tag: "wrap1", properties: { wrap: true, from: 1, to: 10, value: 1, stepSize: 1 }, upSteps: [2,3], downSteps: [2,1,10,9] }, + { tag: "wrap2", properties: { wrap: true, from: 1, to: 10, value: 10, stepSize: 2 }, upSteps: [1,3,5], downSteps: [3,1,10,8,6] }, + { tag: "wrap25", properties: { wrap: true, from: 0, to: 100, value: 50, stepSize: 25 }, upSteps: [75,100,0,25], downSteps: [0,100,75] } + ] + } + + function test_wheel(data) { + var ma = createTemporaryObject(mouseArea, testCase, {width: 100, height: 100}) + verify(ma) + + data.properties.wheelEnabled = true + var control = spinBox.createObject(ma, data.properties) + verify(control) + + var valueModifiedCount = 0 + var valueModifiedSpy = signalSpy.createObject(control, {target: control, signalName: "valueModified"}) + verify(valueModifiedSpy.valid) + + var delta = 120 + + var spy = signalSpy.createObject(ma, {target: ma, signalName: "wheel"}) + verify(spy.valid) + + for (var u = 0; u < data.upSteps.length; ++u) { + var wasUpEnabled = control.wrap || control.value < control.to + mouseWheel(control, control.width / 2, control.height / 2, delta, delta) + if (wasUpEnabled) + ++valueModifiedCount + compare(valueModifiedSpy.count, valueModifiedCount) + compare(spy.count, 0) // no propagation + compare(control.value, data.upSteps[u]) + } + + for (var d = 0; d < data.downSteps.length; ++d) { + var wasDownEnabled = control.wrap || control.value > control.from + mouseWheel(control, control.width / 2, control.height / 2, -delta, -delta) + if (wasDownEnabled) + ++valueModifiedCount + compare(valueModifiedSpy.count, valueModifiedCount) + compare(spy.count, 0) // no propagation + compare(control.value, data.downSteps[d]) + } + } + + function test_initiallyDisabledIndicators_data() { + return [ + { tag: "down disabled", from: 0, value: 0, to: 99, upEnabled: true, downEnabled: false }, + { tag: "up disabled", from: 0, value: 99, to: 99, upEnabled: false, downEnabled: true }, + { tag: "inverted, down disabled", from: 99, value: 99, to: 0, upEnabled: true, downEnabled: false }, + { tag: "inverted, up disabled", from: 99, value: 0, to: 0, upEnabled: false, downEnabled: true } + ] + } + + function test_initiallyDisabledIndicators(data) { + var control = createTemporaryObject(spinBox, testCase, { from: data.from, value: data.value, to: data.to }) + verify(control) + + compare(control.up.indicator.enabled, data.upEnabled) + compare(control.down.indicator.enabled, data.downEnabled) + } + + function test_hover_data() { + return [ + { tag: "up:true", button: "up", hoverEnabled: true, value: 50 }, + { tag: "up:false", button: "up", hoverEnabled: false, value: 50 }, + { tag: "up:max", button: "up", hoverEnabled: true, value: 99 }, + { tag: "down:true", button: "down", hoverEnabled: true, value: 50 }, + { tag: "down:false", button: "down", hoverEnabled: false, value: 50 }, + { tag: "down:min", button: "down", hoverEnabled: true, value: 0 } + ] + } + + function test_hover(data) { + var control = createTemporaryObject(spinBox, testCase, {hoverEnabled: data.hoverEnabled, value: data.value}) + verify(control) + + var button = control[data.button] + compare(control.hovered, false) + compare(button.hovered, false) + + mouseMove(control, button.indicator.x + button.indicator.width / 2, button.indicator.y + button.indicator.height / 2) + compare(control.hovered, data.hoverEnabled) + compare(button.hovered, data.hoverEnabled && button.indicator.enabled) + + mouseMove(control, button.indicator.x - 1, button.indicator.y - 1) + compare(button.hovered, false) + } + + function test_hoverWhilePressed_data() { + return [ + { tag: "up" }, + { tag: "down" }, + ] + } + + // QTBUG-74688 + function test_hoverWhilePressed(data) { + var control = createTemporaryObject(spinBox, testCase, { hoverEnabled: true, value: 50 }) + verify(control) + + var button = control[data.tag] + compare(control.hovered, false) + compare(button.hovered, false) + + // Hover over the indicator. It should be hovered. + var buttonXCenter = button.indicator.x + button.indicator.width / 2 + var buttonYCenter = button.indicator.y + button.indicator.height / 2 + mouseMove(control, buttonXCenter, buttonYCenter) + compare(button.hovered, true) + + // Press on the indicator and then move the mouse outside of it. + mousePress(control, buttonXCenter, buttonYCenter) + compare(button.hovered, true) + mouseMove(control, buttonXCenter - button.indicator.width, buttonYCenter - button.indicator.height) + // It should not be pressed or hovered. + compare(button.pressed, false) + compare(button.hovered, false) + + mouseRelease(control) + } + + function test_valueFromText_data() { + return [ + { tag: "editable", editable: true }, + { tag: "non-editable", editable: false } + ] + } + + function test_valueFromText(data) { + var control = createTemporaryObject(spinBox, testCase, {editable: data.editable}) + verify(control) + + control.forceActiveFocus() + verify(control.activeFocus) + + var valueFromTextCalls = 0 + control.valueFromText = function(text, locale) { + ++valueFromTextCalls + return Number.fromLocaleString(locale, text); + } + + keyClick(Qt.Key_Enter) + compare(valueFromTextCalls, data.editable ? 1 : 0) + + keyClick(Qt.Key_Return) + compare(valueFromTextCalls, data.editable ? 2 : 0) + + control.focus = false + compare(valueFromTextCalls, data.editable ? 3 : 0) + } + + function test_callDefaultValueFromText() { + var control = createTemporaryObject(spinBox, testCase) + verify(control) + compare(control.valueFromText("123", control.locale), 123) + } + + function test_autoRepeat() { + var control = createTemporaryObject(spinBox, testCase) + verify(control) + + compare(control.value, 0) + + var valueSpy = signalSpy.createObject(control, {target: control, signalName: "valueChanged"}) + verify(valueSpy.valid) + + var countBefore = 0 + + // repeat up + mousePress(control.up.indicator) + verify(control.up.pressed) + compare(valueSpy.count, 0) + valueSpy.wait() + valueSpy.wait() + countBefore = valueSpy.count + mouseRelease(control.up.indicator) + verify(!control.up.pressed) + compare(valueSpy.count, countBefore) + + valueSpy.clear() + + // repeat down + mousePress(control.down.indicator) + verify(control.down.pressed) + compare(valueSpy.count, 0) + valueSpy.wait() + valueSpy.wait() + countBefore = valueSpy.count + mouseRelease(control.down.indicator) + verify(!control.down.pressed) + compare(valueSpy.count, countBefore) + + mousePress(control.up.indicator) + verify(control.up.pressed) + valueSpy.wait() + + // move inside during repeat -> continue repeat (QTBUG-57085) + mouseMove(control.up.indicator, control.up.indicator.width / 4, control.up.indicator.height / 4) + verify(control.up.pressed) + valueSpy.wait() + + valueSpy.clear() + + // move outside during repeat -> stop repeat + mouseMove(control.up.indicator, -1, -1) + verify(!control.up.pressed) + // NOTE: The following wait() is NOT a reliable way to test that the + // auto-repeat timer is not running, but there's no way dig into the + // private APIs from QML. If this test ever fails in the future, it + // indicates that the auto-repeat timer logic is broken. + wait(125) + compare(valueSpy.count, 0) + + mouseRelease(control.up.indicator, -1, -1) + verify(!control.up.pressed) + } + + function test_initialValue() { + var control = createTemporaryObject(spinBox, testCase, {from: 1000, to: 10000}) + verify(control) + compare(control.value, 1000) + } + + Component { + id: sizeBox + SpinBox { + from: 0 + to: items.length - 1 + + property var items: ["Small", "Medium", "Large"] + + validator: RegularExpressionValidator { + regularExpression: new RegExp("(Small|Medium|Large)", "i") + } + + textFromValue: function(value) { + return items[value]; + } + + valueFromText: function(text) { + for (var i = 0; i < items.length; ++i) { + if (items[i].toLowerCase().indexOf(text.toLowerCase()) === 0) + return i + } + return sb.value + } + } + } + + function test_textFromValue_data() { + return [ + { tag: "default", component: spinBox, values: [0, 10, 99], displayTexts: ["0", "10", "99"] }, + { tag: "custom", component: sizeBox, values: [0, 1, 2], displayTexts: ["Small", "Medium", "Large"] } + ] + } + + function test_textFromValue(data) { + var control = createTemporaryObject(data.component, testCase) + verify(control) + + for (var i = 0; i < data.values.length; ++i) { + control.value = data.values[i] + compare(control.value, data.values[i]) + compare(control.displayText, data.displayTexts[i]) + } + } + + function test_callDefaultTextFromValue() { + var control = createTemporaryObject(spinBox, testCase) + verify(control) + compare(control.textFromValue(123, control.locale), "123") + } + + Component { + id: overriddenSpinBox + SpinBox { + value: 50 + up.indicator: Rectangle { + property string s: "this is the one" + } + } + } + + function test_indicatorOverridden() { + var control = createTemporaryObject(overriddenSpinBox, testCase) + verify(control) + compare(control.up.indicator.s, "this is the one"); + } + + function test_valueEnterFromOutsideRange() { + // Check that changing from 2 to 99 goes to 98 then changing to 99 puts it back to 98 + var control = createTemporaryObject(spinBox, testCase, {from: 2, to: 98, value: 2, editable: true}) + verify(control) + + control.forceActiveFocus() + verify(control.activeFocus) + + keyClick(Qt.Key_Backspace) + keyClick(Qt.Key_Backspace) + keyClick(Qt.Key_9) + keyClick(Qt.Key_9) + keyClick(Qt.Key_Return) + compare(control.value, 98) + compare(control.displayText, "98") + compare(control.contentItem.text, "98") + + keyClick(Qt.Key_Backspace) + keyClick(Qt.Key_9) + keyClick(Qt.Key_Return) + compare(control.value, 98) + compare(control.displayText, "98") + compare(control.contentItem.text, "98") + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_splitview.qml b/tests/auto/quickcontrols2/controls/data/tst_splitview.qml new file mode 100644 index 0000000000..b9ace27ee3 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_splitview.qml @@ -0,0 +1,2148 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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.Window +import QtTest +import Qt.labs.settings + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "SplitView" + + function initTestCase() { + // For the serialization tests. + Qt.application.name = "qtquickcontrols2-splitview-auto-test" + Qt.application.domain = "org.qt-project" + Qt.application.organization = "QtProject" + } + + function findHandles(splitView) { + var handles = [] + for (var i = 0; i < splitView.children.length; ++i) { + var child = splitView.children[i] + if (child.objectName.toLowerCase().indexOf("handle") !== -1) + handles.push(child) + } + return handles + } + + function compareSizes(control, expectedGeometries, context) { + if (context === undefined) + context = "" + else + context = " (" + context + ")" + + compare(control.count, Math.floor(expectedGeometries.length / 2) + 1, + "Mismatch in actual vs expected number of split items" + context) + + var handles = findHandles(control) + compare(handles.length, Math.floor(expectedGeometries.length / 2), + "Mismatch in actual vs expected number of handle items" + context) + + for (var i = 0, splitItemIndex = 0, handleItemIndex = 0; i < expectedGeometries.length; ++i) { + var item = null + var itemType = "" + var typeSpecificIndex = -1 + if (i % 2 == 0) { + item = control.itemAt(splitItemIndex) + itemType = "split item" + typeSpecificIndex = splitItemIndex + ++splitItemIndex + } else { + item = handles[handleItemIndex] + itemType = "handle item" + typeSpecificIndex = handleItemIndex + ++handleItemIndex + } + + verify(item, itemType + " at index " + typeSpecificIndex + " should not be null" + context) + + var expectedGeometry = expectedGeometries[i] + if (expectedGeometry.hasOwnProperty("hidden")) { + // It's geometry doesn't matter because it's hidden. + verify(!item.visible, itemType + " at index " + typeSpecificIndex + " should be hidden" + context) + continue + } + + // Note that the indices mentioned here account for handles; they do not + // match the indices reported by QQuickSplitView's logging categories. + compare(item.x, expectedGeometry.x, "Mismatch in actual vs expected x value of " + + itemType + " at index " + typeSpecificIndex + context) + compare(item.y, expectedGeometry.y, "Mismatch in actual vs expected y value of " + + itemType + " at index " + typeSpecificIndex + context) + compare(item.width, expectedGeometry.width, "Mismatch in actual vs expected width value of " + + itemType + " at index " + typeSpecificIndex + context) + compare(item.height, expectedGeometry.height, "Mismatch in actual vs expected height value of " + + itemType + " at index " + typeSpecificIndex + context) + } + } + + property real defaultHorizontalHandleWidth: 10 + property real defaultVerticalHandleHeight: 10 + + + Component { + id: signalSpyComponent + SignalSpy {} + } + + Component { + id: handleComponent + Rectangle { + objectName: "handle" + implicitWidth: defaultHorizontalHandleWidth + implicitHeight: defaultVerticalHandleHeight + color: "#444" + + Text { + objectName: "handleText_" + text + text: parent.x + "," + parent.y + " " + parent.width + "x" + parent.height + color: "white" + anchors.centerIn: parent + rotation: 90 + } + } + } + + Component { + id: splitViewComponent + + SplitView { + anchors.fill: parent + handle: handleComponent + } + } + + Component { + id: rectangleComponent + + Rectangle {} + } + + function test_addItemsAfterCompletion() { + var control = createTemporaryObject(splitViewComponent, testCase) + verify(control) + + var item0 = rectangleComponent.createObject(control, { implicitWidth: 25, color: "salmon" }) + verify(item0) + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + // The last item fills the width by default, and since there is only one item... + compare(item0.x, 0) + compare(item0.y, 0) + compare(item0.width, testCase.width) + compare(item0.height, testCase.height) + + var item1 = rectangleComponent.createObject(control, { implicitWidth: 25, color: "steelblue" }) + verify(item1) + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + // Now that a second item has been added, the first item goes back to its preferred (implicit) width. + compare(item0.x, 0) + compare(item0.y, 0) + compare(item0.width, item0.implicitWidth) + compare(item0.height, testCase.height) + var handles = findHandles(control) + var handle0 = handles[0] + compare(handle0.x, item0.implicitWidth) + compare(handle0.y, 0) + compare(handle0.width, defaultHorizontalHandleWidth) + compare(handle0.height, testCase.height) + compare(item1.x, item0.implicitWidth + defaultHorizontalHandleWidth) + compare(item1.y, 0) + compare(item1.width, testCase.width - item0.implicitWidth - defaultHorizontalHandleWidth) + compare(item1.height, testCase.height) + } + + function test_addItemsWithNoSizeAfterCompletion() { + var control = createTemporaryObject(splitViewComponent, testCase) + verify(control) + + var item0 = rectangleComponent.createObject(control, { color: "salmon" }) + verify(item0) + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compare(item0.x, 0) + compare(item0.y, 0) + compare(item0.width, testCase.width) + compare(item0.height, testCase.height) + + var item1 = rectangleComponent.createObject(control, { color: "steelblue" }) + verify(item1) + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compare(item0.x, 0) + compare(item0.y, 0) + compare(item0.width, 0) + compare(item0.height, testCase.height) + var handles = findHandles(control) + var handle0 = handles[0] + compare(handle0.x, 0) + compare(handle0.y, 0) + compare(handle0.width, defaultHorizontalHandleWidth) + compare(handle0.height, testCase.height) + compare(item1.x, defaultHorizontalHandleWidth) + compare(item1.y, 0) + compare(item1.width, testCase.width - defaultHorizontalHandleWidth) + compare(item1.height, testCase.height) + } + + Component { + id: threeZeroSizedItemsComponent + + SplitView { + anchors.fill: parent + handle: handleComponent + + Rectangle { + objectName: "salmon" + color: objectName + } + Rectangle { + objectName: "navajowhite" + color: objectName + } + Rectangle { + objectName: "steelblue" + color: objectName + } + } + } + + function test_changeAttachedPropertiesAfterCompletion() { + var control = createTemporaryObject(threeZeroSizedItemsComponent, testCase) + verify(control) + + var item0 = control.itemAt(0) + compare(item0.x, 0) + compare(item0.y, 0) + compare(item0.width, 0) + compare(item0.height, testCase.height) + + var handles = findHandles(control) + var handle0 = handles[0] + compare(handle0.x, 0) + compare(handle0.y, 0) + compare(handle0.width, defaultHorizontalHandleWidth) + compare(handle0.height, testCase.height) + + var item1 = control.itemAt(1) + compare(item1.x, defaultHorizontalHandleWidth) + compare(item1.y, 0) + compare(item1.width, 0) + compare(item1.height, testCase.height) + + var handle1 = handles[1] + compare(handle1.x, defaultHorizontalHandleWidth) + compare(handle1.y, 0) + compare(handle1.width, defaultHorizontalHandleWidth) + compare(handle1.height, testCase.height) + + var item2 = control.itemAt(2) + compare(item2.x, defaultHorizontalHandleWidth * 2) + compare(item2.y, 0) + compare(item2.width, testCase.width - item2.x) + compare(item2.height, testCase.height) + + item0.SplitView.preferredWidth = 25 + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compare(item0.x, 0) + compare(item0.y, 0) + compare(item0.width, 25) + compare(item0.height, testCase.height) + compare(handle0.x, item0.width) + compare(handle0.y, 0) + compare(handle0.width, defaultHorizontalHandleWidth) + compare(handle0.height, testCase.height) + compare(item1.x, 25 + defaultHorizontalHandleWidth) + compare(item1.y, 0) + compare(item1.width, 0) + compare(item1.height, testCase.height) + compare(handle1.x, item1.x + item1.width) + compare(handle1.y, 0) + compare(handle1.width, defaultHorizontalHandleWidth) + compare(handle1.height, testCase.height) + compare(item2.x, item1.x + item1.width + defaultHorizontalHandleWidth) + compare(item2.y, 0) + compare(item2.width, testCase.width - item2.x) + compare(item2.height, testCase.height) + + item0.SplitView.minimumWidth = 50 + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compare(item0.x, 0) + compare(item0.y, 0) + compare(item0.width, 50) + compare(item0.height, testCase.height) + compare(handle0.x, item0.width) + compare(handle0.y, 0) + compare(handle0.width, defaultHorizontalHandleWidth) + compare(handle0.height, testCase.height) + compare(item1.x, 50 + defaultHorizontalHandleWidth) + compare(item1.y, 0) + compare(item1.width, 0) + compare(item1.height, testCase.height) + compare(handle1.x, item1.x + item1.width) + compare(handle1.y, 0) + compare(handle1.width, defaultHorizontalHandleWidth) + compare(handle1.height, testCase.height) + compare(item2.x, item1.x + item1.width + defaultHorizontalHandleWidth) + compare(item2.y, 0) + compare(item2.width, testCase.width - item2.x) + compare(item2.height, testCase.height) + + item0.SplitView.preferredWidth = 100 + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compare(item0.x, 0) + compare(item0.y, 0) + compare(item0.width, 100) + compare(item0.height, testCase.height) + compare(handle0.x, item0.width) + compare(handle0.y, 0) + compare(handle0.width, defaultHorizontalHandleWidth) + compare(handle0.height, testCase.height) + compare(item1.x, 100 + defaultHorizontalHandleWidth) + compare(item1.y, 0) + compare(item1.width, 0) + compare(item1.height, testCase.height) + compare(handle1.x, item1.x + item1.width) + compare(handle1.y, 0) + compare(handle1.width, defaultHorizontalHandleWidth) + compare(handle1.height, testCase.height) + compare(item2.x, item1.x + item1.width + defaultHorizontalHandleWidth) + compare(item2.y, 0) + compare(item2.width, testCase.width - item2.x) + compare(item2.height, testCase.height) + + item0.SplitView.maximumWidth = 75 + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compare(item0.x, 0) + compare(item0.y, 0) + compare(item0.width, 75) + compare(item0.height, testCase.height) + compare(handle0.x, item0.width) + compare(handle0.y, 0) + compare(handle0.width, defaultHorizontalHandleWidth) + compare(handle0.height, testCase.height) + compare(item1.x, 75 + defaultHorizontalHandleWidth) + compare(item1.y, 0) + compare(item1.width, 0) + compare(item1.height, testCase.height) + compare(handle1.x, item1.x + item1.width) + compare(handle1.y, 0) + compare(handle1.width, defaultHorizontalHandleWidth) + compare(handle1.height, testCase.height) + compare(item2.x, item1.x + item1.width + defaultHorizontalHandleWidth) + compare(item2.y, 0) + compare(item2.width, testCase.width - item2.x) + compare(item2.height, testCase.height) + + item1.SplitView.fillWidth = true + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compare(item0.x, 0) + compare(item0.y, 0) + compare(item0.width, 75) + compare(item0.height, testCase.height) + compare(handle0.x, item0.width) + compare(handle0.y, 0) + compare(handle0.width, defaultHorizontalHandleWidth) + compare(handle0.height, testCase.height) + compare(item1.x, 75 + defaultHorizontalHandleWidth) + compare(item1.y, 0) + compare(item1.width, testCase.width - 75 - defaultHorizontalHandleWidth * 2) + compare(item1.height, testCase.height) + compare(handle1.x, item1.x + item1.width) + compare(handle1.y, 0) + compare(handle1.width, defaultHorizontalHandleWidth) + compare(handle1.height, testCase.height) + compare(item2.x, testCase.width) + compare(item2.y, 0) + compare(item2.width, 0) + compare(item2.height, testCase.height) + } + + Component { + id: itemComponent + Item {} + } + + Component { + id: objectComponent + QtObject {} + } + + function test_useAttachedPropertiesIncorrectly_data() { + var properties = [ "fillWidth", "fillHeight", "minimumWidth", "minimumHeight", + "preferredWidth", "preferredHeight", "maximumWidth", "maximumHeight" ] + + var data = [] + + for (var i = 0; i < properties.length; ++i) { + var property = properties[i] + data.push({ tag: "Item," + property, component: itemComponent, property: property, + expectedWarning: /.*SplitView: attached properties must be accessed through a direct child of SplitView/ }) + } + + for (i = 0; i < properties.length; ++i) { + property = properties[i] + data.push({ tag: "QtObject," + property, component: objectComponent, property: property, + expectedWarning: /.*SplitView: attached properties can only be used on Items/ }) + } + + return data + } + + function test_useAttachedPropertiesIncorrectly(data) { + // The object (whatever it may be) is not managed by a SplitView. + var object = createTemporaryObject(data.component, testCase, { objectName: data.tag }) + verify(object) + + ignoreWarning(data.expectedWarning) + // Should warn, but not crash. + object.SplitView[data.property] = 1; + } + + function test_sizes_data() { + var splitViewWidth = testCase.width + var splitViewHeight = testCase.height + var data = [ + { + // When the combined size of items is too large, the non-fill items should just exceed + // the size of the SplitView, exactly as they would were they in a RowLayout, for example. + tag: "fillItemOnLeft", + expectedGeometries: [ + // We're the fill item, but since the combined implicitWidths + // of the other two items take up all the space, we get none. + { x: 0, y: 0, width: 0, height: splitViewHeight }, + // First handle. + { x: 0, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // The second item does not fill, so its width should be unchanged. + { x: defaultHorizontalHandleWidth, y: 0, width: 200, height: splitViewHeight }, + // Second handle. + { x: 200 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, + height: splitViewHeight }, + // The third item also gets its implicitWidth. + { x: 200 + defaultHorizontalHandleWidth * 2, y: 0, width: 200, height: splitViewHeight } + ] + }, + { + // Same as above except vertical. + tag: "fillItemOnTop", + expectedGeometries: [ + // We're the fill item, but since the combined implicitHeights + // of the other two items take up all the space, we get none. + { x: 0, y: 0, width: splitViewWidth, height: 0 }, + // First handle. + { x: 0, y: 0, width: splitViewWidth, height: defaultVerticalHandleHeight }, + // The second item does not fill, so its height should be unchanged. + { x: 0, y: defaultVerticalHandleHeight, width: splitViewWidth, height: 200 }, + // Second handle. + { x: 0, y: 200 + defaultVerticalHandleHeight, width: splitViewWidth, + height: defaultVerticalHandleHeight }, + // The third item also gets its implicitHeight. + { x: 0, y: 200 + defaultVerticalHandleHeight * 2, width: splitViewWidth, height: 200 } + ] + }, + { + tag: "fillItemInMiddle", + expectedGeometries: [ + // Our size is fixed. + { x: 0, y: 0, width: 25, height: splitViewHeight }, + // First handle. + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // The second item fills. + { x: 25 + defaultHorizontalHandleWidth, y: 0, + width: splitViewWidth - 25 - 200 - defaultHorizontalHandleWidth * 2, height: splitViewHeight }, + // Second handle. + { x: splitViewWidth - 200 - defaultHorizontalHandleWidth, y: 0, + width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // The third item's size is also fixed. + { x: splitViewWidth - 200, y: 0, width: 200, height: splitViewHeight } + ] + } + ] + return data + } + + function test_sizes(data) { + var component = Qt.createComponent("splitview/" + data.tag + ".qml") + compare(component.status, Component.Ready, component.errorString()); + var control = createTemporaryObject(component, testCase, { "handle": handleComponent }) + verify(control) + + compareSizes(control, data.expectedGeometries) + } + + Component { + id: threeSizedItemsComponent + + SplitView { + anchors.fill: parent + handle: handleComponent + + Rectangle { + objectName: "salmon" + color: objectName + implicitWidth: 25 + implicitHeight: 25 + } + Rectangle { + objectName: "navajowhite" + color: objectName + implicitWidth: 100 + implicitHeight: 100 + } + Rectangle { + objectName: "steelblue" + color: objectName + implicitWidth: 200 + implicitHeight: 200 + } + } + } + + function test_resetAttachedProperties_data() { + var splitViewWidth = testCase.width + var splitViewHeight = testCase.height + var data = [ + { + tag: "resetMinimumWidth", + orientation: Qt.Horizontal, + // Set the minimumWidth to 50. It should be used instead of implicitWidth since it's greater than 25. + splitItemIndex: 0, + propertyName: "minimumWidth", + propertyValue: 50, + expectedGeometriesBefore: [ + { x: 0, y: 0, width: 50, height: splitViewHeight }, + { x: 50, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 50 + defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + { x: 50 + 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, + height: splitViewHeight }, + { x: 50 + 100 + defaultHorizontalHandleWidth * 2, y: 0, + width: splitViewWidth - 50 - 100 - defaultHorizontalHandleWidth * 2, height: splitViewHeight } + ], + // minimumWidth is now undefined, so implicitWidth should be used instead. + expectedGeometriesAfter: [ + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, + height: splitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth * 2, y: 0, + width: splitViewWidth - 25 - 100 - defaultHorizontalHandleWidth * 2, height: splitViewHeight } + ] + }, + { + tag: "resetMinimumHeight", + orientation: Qt.Vertical, + // Set the minimumHeight to 50. It should be used instead of implicitHeight since it's greater than 25. + splitItemIndex: 0, + propertyName: "minimumHeight", + propertyValue: 50, + expectedGeometriesBefore: [ + { x: 0, y: 0, width: splitViewWidth, height: 50 }, + { x: 0, y: 50, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 50 + defaultVerticalHandleHeight, width: splitViewWidth, height: 100 }, + { x: 0, y: 50 + 100 + defaultVerticalHandleHeight, width: splitViewWidth, + height: defaultVerticalHandleHeight }, + { x: 0, y: 50 + 100 + defaultVerticalHandleHeight * 2, width: splitViewWidth, + height: splitViewHeight - 50 - 100 - defaultVerticalHandleHeight * 2 } + ], + // preferredHeight is now undefined, so implicitHeight should be used instead. + expectedGeometriesAfter: [ + { x: 0, y: 0, width: splitViewWidth, height: 25 }, + { x: 0, y: 25, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + defaultVerticalHandleHeight, width: splitViewWidth, height: 100 }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight, width: splitViewWidth, + height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight * 2, width: splitViewWidth, + height: splitViewHeight - 25 - 100 - defaultVerticalHandleHeight * 2 } + ] + }, + { + tag: "resetPreferredWidth", + orientation: Qt.Horizontal, + // Set the preferredWidth to 50; it should be used instead of implicitWidth. + splitItemIndex: 0, + propertyName: "preferredWidth", + propertyValue: 50, + expectedGeometriesBefore: [ + { x: 0, y: 0, width: 50, height: splitViewHeight }, + { x: 50, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 50 + defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + { x: 50 + 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, + height: splitViewHeight }, + { x: 50 + 100 + defaultHorizontalHandleWidth * 2, y: 0, + width: splitViewWidth - 50 - 100 - defaultHorizontalHandleWidth * 2, height: splitViewHeight } + ], + // preferredWidth is now undefined, so implicitWidth should be used instead. + expectedGeometriesAfter: [ + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, + height: splitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth * 2, y: 0, + width: splitViewWidth - 25 - 100 - defaultHorizontalHandleWidth * 2, height: splitViewHeight } + ] + }, + { + tag: "resetPreferredHeight", + orientation: Qt.Vertical, + // Set the preferredHeight to 50; it should be used instead of implicitHeight. + splitItemIndex: 0, + propertyName: "preferredHeight", + propertyValue: 50, + expectedGeometriesBefore: [ + { x: 0, y: 0, width: splitViewWidth, height: 50 }, + { x: 0, y: 50, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 50 + defaultVerticalHandleHeight, width: splitViewWidth, height: 100 }, + { x: 0, y: 50 + 100 + defaultVerticalHandleHeight, width: splitViewWidth, + height: defaultVerticalHandleHeight }, + { x: 0, y: 50 + 100 + defaultVerticalHandleHeight * 2, width: splitViewWidth, + height: splitViewHeight - 50 - 100 - defaultVerticalHandleHeight * 2 } + ], + // preferredHeight is now undefined, so implicitHeight should be used instead. + expectedGeometriesAfter: [ + { x: 0, y: 0, width: splitViewWidth, height: 25 }, + { x: 0, y: 25, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + defaultVerticalHandleHeight, width: splitViewWidth, height: 100 }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight, width: splitViewWidth, + height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight * 2, width: splitViewWidth, + height: splitViewHeight - 25 - 100 - defaultVerticalHandleHeight * 2 } + ] + }, + { + tag: "resetMaximumWidth", + orientation: Qt.Horizontal, + // Set the maximumWidth to 15. It should be used instead of implicitWidth since it's less than 25. + splitItemIndex: 0, + propertyName: "maximumWidth", + propertyValue: 15, + expectedGeometriesBefore: [ + { x: 0, y: 0, width: 15, height: splitViewHeight }, + { x: 15, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 15 + defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + { x: 15 + 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, + height: splitViewHeight }, + { x: 15 + 100 + defaultHorizontalHandleWidth * 2, y: 0, + width: splitViewWidth - 15 - 100 - defaultHorizontalHandleWidth * 2, height: splitViewHeight } + ], + // maximumWidth is now undefined, so implicitWidth should be used instead. + expectedGeometriesAfter: [ + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, + height: splitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth * 2, y: 0, + width: splitViewWidth - 25 - 100 - defaultHorizontalHandleWidth * 2, height: splitViewHeight } + ] + }, + { + tag: "resetMaximumHeight", + orientation: Qt.Vertical, + // Set the preferredHeight to 15. It should be used instead of implicitHeight if it's not undefined. + splitItemIndex: 0, + propertyName: "maximumHeight", + propertyValue: 15, + expectedGeometriesBefore: [ + { x: 0, y: 0, width: splitViewWidth, height: 15 }, + { x: 0, y: 15, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 15 + defaultVerticalHandleHeight, width: splitViewWidth, height: 100 }, + { x: 0, y: 15 + 100 + defaultVerticalHandleHeight, width: splitViewWidth, + height: defaultVerticalHandleHeight }, + { x: 0, y: 15 + 100 + defaultVerticalHandleHeight * 2, width: splitViewWidth, + height: splitViewHeight - 15 - 100 - defaultVerticalHandleHeight * 2 } + ], + // preferredHeight is now undefined, so implicitHeight should be used instead. + expectedGeometriesAfter: [ + { x: 0, y: 0, width: splitViewWidth, height: 25 }, + { x: 0, y: 25, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + defaultVerticalHandleHeight, width: splitViewWidth, height: 100 }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight, width: splitViewWidth, + height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight * 2, width: splitViewWidth, + height: splitViewHeight - 25 - 100 - defaultVerticalHandleHeight * 2 } + ] + }, + ] + return data; + } + + function test_resetAttachedProperties(data) { + var control = createTemporaryObject(threeSizedItemsComponent, testCase, + { "orientation": data.orientation }) + verify(control) + + var splitItem = control.itemAt(data.splitItemIndex) + splitItem.SplitView[data.propertyName] = data.propertyValue + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compareSizes(control, data.expectedGeometriesBefore, "after setting attached property") + + splitItem.SplitView[data.propertyName] = undefined + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compareSizes(control, data.expectedGeometriesAfter, "after resetting attached property") + } + + function test_orientation() { + var control = createTemporaryObject(threeSizedItemsComponent, testCase) + verify(control) + + var item0 = control.itemAt(0) + compare(item0.x, 0) + compare(item0.y, 0) + compare(item0.width, item0.implicitWidth) + compare(item0.height, testCase.height) + + var handles = findHandles(control) + var handle0 = handles[0] + compare(handle0.x, item0.implicitWidth) + compare(handle0.y, 0) + compare(handle0.width, defaultHorizontalHandleWidth) + compare(handle0.height, testCase.height) + + var item1 = control.itemAt(1) + compare(item1.x, item0.width + defaultHorizontalHandleWidth) + compare(item1.y, 0) + compare(item1.width, item1.implicitWidth) + compare(item1.height, testCase.height) + + var handle1 = handles[1] + compare(handle1.x, item1.x + item1.width) + compare(handle1.y, 0) + compare(handle1.width, defaultHorizontalHandleWidth) + compare(handle1.height, testCase.height) + + var item2 = control.itemAt(2) + compare(item2.x, item0.width + item1.width + defaultHorizontalHandleWidth * 2) + compare(item2.y, 0) + compare(item2.width, testCase.width - item2.x) + compare(item2.height, testCase.height) + + control.orientation = Qt.Vertical + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compare(item0.x, 0) + compare(item0.y, 0) + compare(item0.width, testCase.width) + compare(item0.height, item0.implicitHeight) + handles = findHandles(control) + handle0 = handles[0] + compare(handle0.x, 0) + compare(handle0.y, item0.implicitHeight) + compare(handle0.width, testCase.width) + compare(handle0.height, defaultVerticalHandleHeight) + compare(item1.x, 0) + compare(item1.y, item0.height + defaultVerticalHandleHeight) + compare(item1.width, testCase.width) + compare(item1.height, item1.implicitHeight) + handle1 = handles[1] + compare(handle1.x, 0) + compare(handle1.y, item1.y + item1.height) + compare(handle1.width, testCase.width) + compare(handle1.height, defaultVerticalHandleHeight) + compare(item2.x, 0) + compare(item2.y, item0.height + item1.height + defaultVerticalHandleHeight * 2) + compare(item2.width, testCase.width) + compare(item2.height, testCase.height - item2.y) + } + + readonly property int splitViewMargins: 50 + + Component { + id: threeItemsMinSizeAndFillComponent + + SplitView { + anchors.fill: parent + handle: handleComponent + + Rectangle { + objectName: "salmon" + color: objectName + implicitWidth: 25 + implicitHeight: 25 + SplitView.minimumWidth: 25 + SplitView.minimumHeight: 25 + SplitView.fillWidth: true + SplitView.fillHeight: true + } + Rectangle { + objectName: "navajowhite" + color: objectName + implicitWidth: 100 + implicitHeight: 100 + } + Rectangle { + objectName: "steelblue" + color: objectName + implicitWidth: 200 + implicitHeight: 200 + } + } + } + + Component { + id: repeaterSplitViewComponent + + SplitView { + anchors.fill: parent + handle: handleComponent + + property alias repeater: repeater + + Repeater { + id: repeater + model: 3 + delegate: Rectangle { + objectName: "rectDelegate" + index + + SplitView.preferredWidth: 25 + + color: "#aaff0000" + + Text { + text: parent.x + "," + parent.y + " " + parent.width + "x" + parent.height + color: "white" + rotation: 90 + anchors.centerIn: parent + } + } + } + } + } + + Component { + id: hiddenItemSplitViewComponent + + SplitView { + anchors.fill: parent + handle: handleComponent + + Rectangle { + objectName: "steelblue" + color: objectName + + SplitView.minimumWidth: 50 + } + Rectangle { + objectName: "tomato" + color: objectName + + SplitView.fillWidth: true + SplitView.preferredWidth: 200 + } + Rectangle { + objectName: "navajowhite" + color: objectName + visible: false + + SplitView.minimumWidth: visible ? 100 : 0 + } + Rectangle { + objectName: "mediumseagreen" + color: objectName + + SplitView.minimumWidth: 50 + } + } + } + + function test_dragHandle_data() { + var splitViewWidth = testCase.width - splitViewMargins * 2 + var splitViewHeight = testCase.height - splitViewMargins * 2 + var data = [ + { + tag: "fillThirdItemAndDragFirstHandlePastRightSide", + component: threeSizedItemsComponent, + orientation: Qt.Horizontal, + // The index of the item that will fill. + fillIndex: 2, + // The index of the handle to be dragged. + handleIndex: 0, + // The position where the center of the handle will be. + newHandlePos: Qt.point(testCase.width + 20, testCase.height / 2), + // The expected geometry of each item managed by the SplitView before dragging the handle. + expectedGeometriesBeforeDrag: [ + // First item. + { x: 0, y: 0, width: 25, height: splitViewHeight }, + // First handle. + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // Second item. + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + // Second handle. + { x: 25 + 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // Third item (fills). + { x: 25 + 100 + defaultHorizontalHandleWidth * 2, y: 0, + width: splitViewWidth - 25 - 100 - defaultHorizontalHandleWidth * 2, height: splitViewHeight } + ], + // The expected geometry of each item managed by the SplitView after dragging the handle. + expectedGeometriesAfterDrag: [ + // The fill item is to the right of the handle at index 0, so the handle belongs + // to the left item: us. We should consume all of the fill item's width. + { x: 0, y: 0, width: splitViewWidth - 100 - defaultHorizontalHandleWidth * 2, + height: splitViewHeight }, + // First handle. + { x: splitViewWidth - defaultHorizontalHandleWidth * 2 - 100, + y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // The second item does not fill, so its width should be unchanged. + { x: splitViewWidth - 100 - defaultHorizontalHandleWidth, + y: 0, width: 100, height: splitViewHeight }, + // Second handle. + { x: splitViewWidth - defaultHorizontalHandleWidth, + y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // The last item does fill, so it should lose all of its width. + { x: splitViewWidth, y: 0, width: 0, height: splitViewHeight } + ] + }, + { + tag: "fillThirdItemAndDragFirstHandlePastBottomSide", + component: threeSizedItemsComponent, + orientation: Qt.Vertical, + fillIndex: 2, + handleIndex: 0, + newHandlePos: Qt.point(testCase.width / 2, testCase.height + 20), + expectedGeometriesBeforeDrag: [ + { x: 0, y: 0, width: splitViewWidth, height: 25 }, + { x: 0, y: 25, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + defaultVerticalHandleHeight, width: splitViewWidth, height: 100 }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight * 2, + width: splitViewWidth, height: splitViewHeight - 25 - 100 - defaultVerticalHandleHeight * 2 } + ], + // The expected geometry of each item managed by the SplitView after dragging the handle. + expectedGeometriesAfterDrag: [ + // The fill item is to the bottom of the handle at index 0, so the handle belongs + // to the top item: us. We should consume all of the fill item's width. + { x: 0, y: 0, width: splitViewWidth, + height: splitViewHeight - 100 - defaultVerticalHandleHeight * 2 }, + // First handle. + { x: 0, y: splitViewHeight - defaultVerticalHandleHeight * 2 - 100, + width: splitViewWidth, height: defaultVerticalHandleHeight }, + // The second item does not fill, so its height should be unchanged. + { x: 0, y: splitViewWidth - 100 - defaultVerticalHandleHeight, + width: splitViewWidth, height: 100 }, + // Second handle. + { x: 0, y: splitViewHeight - defaultVerticalHandleHeight, + width: splitViewWidth, height: defaultVerticalHandleHeight }, + // The last item does fill, so it should lose all of its width. + { x: 0, y: splitViewHeight, width: splitViewWidth, height: 0 } + ] + }, + { + tag: "fillThirdItemAndDragSecondHandlePastLeftSide", + component: threeSizedItemsComponent, + orientation: Qt.Horizontal, + fillIndex: 2, + handleIndex: 1, + newHandlePos: Qt.point(-20, testCase.height / 2), + expectedGeometriesBeforeDrag: [ + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth * 2, y: 0, + width: splitViewWidth - 25 - 100 - defaultHorizontalHandleWidth * 2, height: splitViewHeight } + ], + expectedGeometriesAfterDrag: [ + // The fill item is to the right of the handle at index 1, so the handle belongs + // to the second item; our width should be unchanged. + { x: 0, y: 0, width: 25, height: splitViewHeight }, + // First handle. + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // The second item is the one being resized, and since we're dragging its handle + // to the left, its width should decrease. + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: 0, height: splitViewHeight }, + // Second handle. + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, + height: splitViewHeight }, + // The last item fills, so it should get the second item's lost width. + { x: 25 + defaultHorizontalHandleWidth * 2, y: 0, + width: splitViewWidth - 25 - defaultHorizontalHandleWidth * 2, height: splitViewHeight } + ] + }, + { + tag: "fillThirdItemAndDragSecondHandlePastTopSide", + component: threeSizedItemsComponent, + orientation: Qt.Vertical, + fillIndex: 2, + handleIndex: 1, + newHandlePos: Qt.point(testCase.width / 2, -20), + expectedGeometriesBeforeDrag: [ + { x: 0, y: 0, width: splitViewWidth, height: 25 }, + { x: 0, y: 25, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + defaultVerticalHandleHeight, width: splitViewWidth, height: 100 }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight * 2, + width: splitViewWidth, height: splitViewHeight - 25 - 100 - defaultVerticalHandleHeight * 2 } + ], + expectedGeometriesAfterDrag: [ + // The fill item is to the bottom of the handle at index 1, so the handle belongs + // to the second item; our height should be unchanged. + { x: 0, y: 0, width: splitViewWidth, height: 25 }, + // First handle. + { x: 0, y: 25, width: splitViewWidth, height: defaultVerticalHandleHeight }, + // The second item is the one being resized, and since we're dragging its handle + // to the top, its height should decrease. + { x: 0, y: 25 + defaultVerticalHandleHeight, width: splitViewWidth, height: 0 }, + // Second handle. + { x: 0, y: 25 + defaultVerticalHandleHeight, width: splitViewWidth, + height: defaultVerticalHandleHeight }, + // The last item fills, so it should get the second item's lost height. + { x: 0, y: 25 + defaultVerticalHandleHeight * 2, + width: splitViewWidth, height: splitViewHeight - 25 - defaultVerticalHandleHeight * 2 } + ] + }, + { + // First item should start off empty and then eventually take up all of 3rd item's space + // as the handle is dragged past the right side. + tag: "fillFirstItemAndDragSecondHandlePastRightSide", + component: threeSizedItemsComponent, + orientation: Qt.Horizontal, + fillIndex: 0, + handleIndex: 1, + newHandlePos: Qt.point(testCase.width + 20, testCase.height / 2), + expectedGeometriesBeforeDrag: [ + { x: 0, y: 0, width: 0, height: splitViewHeight }, + { x: 0, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + { x: 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 100 + defaultHorizontalHandleWidth * 2, y: 0, width: 200, height: splitViewHeight } + ], + expectedGeometriesAfterDrag: [ + // The fill item is to the left of the handle at index 1, so the handle belongs + // to the third item. Since we're moving the handle to the right side of the + // SplitView, our width should grow as we consume the width of the third item. + { x: 0, y: 0, width: splitViewWidth - 100 - defaultHorizontalHandleWidth * 2, height: splitViewHeight }, + // First handle. + { x: splitViewWidth - 100 - defaultHorizontalHandleWidth * 2, y: 0, + width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // The second item's width remains unchanged. + { x: splitViewWidth - 100 - defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + // Second handle. + { x: splitViewWidth - defaultHorizontalHandleWidth, y: 0, + width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // The last item loses its width. + { x: splitViewWidth, y: 0, width: 0, height: splitViewHeight } + ] + }, + { + // First item should start off empty and then eventually take up all of 3rd item's space + // as the handle is dragged past the bottom side. + tag: "fillFirstItemAndDragSecondHandlePastBottomSide", + component: threeSizedItemsComponent, + orientation: Qt.Vertical, + fillIndex: 0, + handleIndex: 1, + newHandlePos: Qt.point(testCase.width / 2, testCase.height + 20), + expectedGeometriesBeforeDrag: [ + { x: 0, y: 0, width: splitViewWidth, height: 0 }, + { x: 0, y: 0, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: defaultVerticalHandleHeight, width: splitViewWidth, height: 100 }, + { x: 0, y: 100 + defaultVerticalHandleHeight, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 100 + defaultVerticalHandleHeight * 2, width: splitViewWidth, height: 200 } + ], + expectedGeometriesAfterDrag: [ + // The fill item is to the top of the handle at index 1, so the handle belongs + // to the third item. Since we're moving the handle to the bottom side of the + // SplitView, our height should grow as we consume the height of the third item. + { x: 0, y: 0, width: splitViewWidth, height: splitViewHeight - 100 - defaultVerticalHandleHeight * 2 }, + // First handle. + { x: 0, y: splitViewHeight - 100 - defaultVerticalHandleHeight * 2, + width: splitViewWidth, height: defaultVerticalHandleHeight }, + // The second item's width remains unchanged. + { x: 0, y: splitViewHeight - 100 - defaultVerticalHandleHeight, width: splitViewWidth, height: 100 }, + // Second handle. + { x: 0, y: splitViewHeight - defaultVerticalHandleHeight, + width: splitViewWidth, height: defaultVerticalHandleHeight }, + // The last item loses its width. + { x: 0, y: splitViewHeight, width: splitViewHeight, height: 0 } + ] + }, + { + tag: "fillFirstItemAndDragFirstHandlePastLeftSide", + component: threeSizedItemsComponent, + orientation: Qt.Horizontal, + fillIndex: 0, + handleIndex: 0, + newHandlePos: Qt.point(-20, testCase.height / 2), + expectedGeometriesBeforeDrag: [ + { x: 0, y: 0, width: 0, height: splitViewHeight }, + { x: 0, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + { x: 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // The second item's implicitWidth is 100, and ours is 200. The available width is 300, + // so both items get their implicit widths. + { x: 100 + defaultHorizontalHandleWidth * 2, y: 0, width: splitViewWidth - 100, height: splitViewHeight } + ], + // Should be unchanged. + expectedGeometriesAfterDrag: [ + { x: 0, y: 0, width: 0, height: splitViewHeight }, + { x: 0, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + { x: 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 100 + defaultHorizontalHandleWidth * 2, y: 0, width: splitViewWidth - 100, height: splitViewHeight } + ] + }, + { + tag: "fillFirstItemWithMinWidthAndDragFirstHandlePastLeftSide", + component: threeItemsMinSizeAndFillComponent, + orientation: Qt.Horizontal, + fillIndex: 0, + handleIndex: 0, + newHandlePos: Qt.point(-20, testCase.height / 2), + expectedGeometriesBeforeDrag: [ + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth * 2, y: 0, width: splitViewWidth - 100, height: splitViewHeight } + ], + // Should be unchanged. + expectedGeometriesAfterDrag: [ + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth * 2, y: 0, width: splitViewWidth - 100, height: splitViewHeight } + ] + }, + { + tag: "repeater", + component: repeaterSplitViewComponent, + orientation: Qt.Horizontal, + fillIndex: 2, + handleIndex: 1, + newHandlePos: Qt.point(200, testCase.height / 2), + expectedGeometriesBeforeDrag: [ + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: 25, height: splitViewHeight }, + { x: 25 * 2 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 * 2 + defaultHorizontalHandleWidth * 2, y: 0, width: splitViewWidth - 70 , height: splitViewHeight } + ], + expectedGeometriesAfterDrag: [ + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: 105, height: splitViewHeight }, + { x: 140, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 150, y: 0, width: 150, height: splitViewHeight } + ] + }, + { + tag: "hiddenItemSplitViewComponent", + // [50] | [200 (fill)] | [hidden] | [50] + component: hiddenItemSplitViewComponent, + orientation: Qt.Horizontal, + fillIndex: 1, + handleIndex: 1, + // Drag to the horizontal centre of the SplitView. + newHandlePos: Qt.point(splitViewMargins + 150, testCase.height / 2), + expectedGeometriesBeforeDrag: [ + { x: 0, y: 0, width: 50, height: splitViewHeight }, + { x: 50, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 50 + defaultHorizontalHandleWidth, y: 0, width: 200 - defaultHorizontalHandleWidth * 2, height: splitViewHeight }, + { x: 250 - (defaultHorizontalHandleWidth * 2) + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { hidden: true }, // Third item should be hidden. + { hidden: true }, // Handle for third item should be hidden. + { x: 250 - (defaultHorizontalHandleWidth * 2) + defaultHorizontalHandleWidth * 2, y: 0, width: 50, height: splitViewHeight } + ], + expectedGeometriesAfterDrag: [ + { x: 0, y: 0, width: 50, height: splitViewHeight }, + { x: 50, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // Width of the fill item should end up smaller. + { x: 50 + defaultHorizontalHandleWidth, y: 0, width: 100 - defaultHorizontalHandleWidth * 2, height: splitViewHeight }, + { x: 150 - (defaultHorizontalHandleWidth * 2) + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { hidden: true }, // Third item should be hidden. + { hidden: true }, // Handle for third item should be hidden. + // Width of the last item should grow. + { x: 150 - (defaultHorizontalHandleWidth * 2) + defaultHorizontalHandleWidth * 2, y: 0, width: 150, height: splitViewHeight } + ] + } + ] + return data + } + + function test_dragHandle(data) { + var control = createTemporaryObject(data.component, testCase) + verify(control) + + control.orientation = data.orientation + + // Ensure that there is space to drag outside of the SplitView. + control.anchors.margins = splitViewMargins + + var fillItem = control.itemAt(data.fillIndex) + if (control.orientation === Qt.Horizontal) + fillItem.SplitView.fillWidth = true + else + fillItem.SplitView.fillHeight = true + + // Check the sizes (and visibility) of the items before the drag. + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compareSizes(control, data.expectedGeometriesBeforeDrag, "before drag") + + // Drag the handle. + var handles = findHandles(control) + var targetHandle = handles[data.handleIndex] + verify(targetHandle.visible) + mousePress(targetHandle) + verify(control.resizing) + // newHandlePos is in scene coordinates, so map it to coordinates local to the handle. + var localPos = testCase.mapToItem(targetHandle, data.newHandlePos.x, data.newHandlePos.y) + mouseMove(targetHandle, localPos.x - targetHandle.width / 2, localPos.y - targetHandle.height / 2) + verify(control.resizing) + compareSizes(control, data.expectedGeometriesAfterDrag, "after drag move") + + // The geometries should remain unchanged after releasing. + mouseRelease(targetHandle, localPos.x - targetHandle.width / 2, localPos.y - targetHandle.height / 2, Qt.LeftButton) + verify(!control.resizing) + compareSizes(control, data.expectedGeometriesAfterDrag, "after drag release") + } + + function test_splitViewGeometryChanges_data() { + var defaultSplitViewWidth = testCase.width + var defaultSplitViewHeight = testCase.height + + var data = [ + { + tag: "growWidth", + orientation: Qt.Horizontal, + splitViewWidth: 800, + expectedGeometries: [ + { x: 0, y: 0, width: 25, height: defaultSplitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: defaultSplitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: 100, height: defaultSplitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, + height: defaultSplitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth * 2, y: 0, + width: 800 - 25 - 100 - defaultHorizontalHandleWidth * 2, height: defaultSplitViewHeight } + ] + }, + { + // Same as above except vertical. + tag: "growHeight", + orientation: Qt.Vertical, + splitViewHeight: 800, + expectedGeometries: [ + { x: 0, y: 0, width: defaultSplitViewWidth, height: 25 }, + { x: 0, y: 25, width: defaultSplitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + defaultVerticalHandleHeight, width: defaultSplitViewWidth, height: 100 }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight, width: defaultSplitViewWidth, + height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight * 2, width: defaultSplitViewWidth, + height: 800 - 25 - 100 - defaultVerticalHandleHeight * 2 } + ] + }, + { + tag: "shrinkWidth", + orientation: Qt.Horizontal, + splitViewWidth: 200, + expectedGeometries: [ + { x: 0, y: 0, width: 25, height: defaultSplitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: defaultSplitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: 100, height: defaultSplitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, + height: defaultSplitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth * 2, y: 0, + width: 200 - 25 - 100 - defaultHorizontalHandleWidth * 2, height: defaultSplitViewHeight } + ] + }, + { + // Same as above except vertical. + tag: "shrinkHeight", + orientation: Qt.Vertical, + splitViewHeight: 200, + expectedGeometries: [ + { x: 0, y: 0, width: defaultSplitViewWidth, height: 25 }, + { x: 0, y: 25, width: defaultSplitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + defaultVerticalHandleHeight, width: defaultSplitViewWidth, height: 100 }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight, width: defaultSplitViewWidth, + height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + 100 + defaultVerticalHandleHeight * 2, width: defaultSplitViewWidth, + height: 200 - 25 - 100 - defaultVerticalHandleHeight * 2 } + ] + }, + ] + return data + } + + function test_splitViewGeometryChanges(data) { + var control = createTemporaryObject(threeSizedItemsComponent, testCase, + { "handle": handleComponent, "anchors.fill": undefined, "orientation": data.orientation }) + verify(control) + + if (data.hasOwnProperty("splitViewWidth")) + control.width = data.splitViewWidth + else + control.width = testCase.width + + if (data.hasOwnProperty("splitViewHeight")) + control.height = data.splitViewHeight + else + control.height = testCase.height + + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compareSizes(control, data.expectedGeometries) + } + + function test_splitItemImplicitSizeChanges_data() { + var defaultSplitViewWidth = testCase.width + var defaultSplitViewHeight = testCase.height + + var data = [ + { + tag: "growImplicitWidth", + orientation: Qt.Horizontal, + splitItemImplicitWidth: 50, + expectedGeometries: [ + { x: 0, y: 0, width: 50, height: defaultSplitViewHeight }, + { x: 50, y: 0, width: defaultHorizontalHandleWidth, height: defaultSplitViewHeight }, + { x: 50 + defaultHorizontalHandleWidth, y: 0, width: 100, height: defaultSplitViewHeight }, + { x: 50 + 100 + defaultHorizontalHandleWidth, y: 0, width: defaultHorizontalHandleWidth, + height: defaultSplitViewHeight }, + { x: 50 + 100 + defaultHorizontalHandleWidth * 2, y: 0, + width: defaultSplitViewWidth - 50 - 100 - defaultHorizontalHandleWidth * 2, height: defaultSplitViewHeight } + ] + }, + { + tag: "growImplicitHeight", + orientation: Qt.Vertical, + splitItemImplicitHeight: 50, + expectedGeometries: [ + { x: 0, y: 0, width: defaultSplitViewWidth, height: 50 }, + { x: 0, y: 50, width: defaultSplitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 50 + defaultVerticalHandleHeight, width: defaultSplitViewWidth, height: 100 }, + { x: 0, y: 50 + 100 + defaultVerticalHandleHeight, width: defaultSplitViewWidth, + height: defaultVerticalHandleHeight }, + { x: 0, y: 50 + 100 + defaultVerticalHandleHeight * 2, width: defaultSplitViewWidth, + height: defaultSplitViewHeight - 50 - 100 - defaultVerticalHandleHeight * 2 } + ] + } + ] + return data + } + + // Tests that implicitWidth/Height changes in items are noticed by SplitView. + function test_splitItemImplicitSizeChanges(data) { + var control = createTemporaryObject(threeSizedItemsComponent, testCase, + { "handle": handleComponent, "orientation": data.orientation }) + verify(control) + + var firstItem = control.itemAt(0) + + if (data.hasOwnProperty("splitItemImplicitWidth")) + firstItem.implicitWidth = data.splitItemImplicitWidth + + if (data.hasOwnProperty("splitItemImplicitHeight")) + firstItem.implicitHeight = data.splitItemImplicitHeight + + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compareSizes(control, data.expectedGeometries) + } + + Component { + id: largerHandle + Rectangle { + objectName: "largerHandle" + implicitWidth: 20 + implicitHeight: 20 + color: "#444" + } + } + + Component { + id: smallerHandle + Rectangle { + objectName: "smallerHandle" + implicitWidth: 5 + implicitHeight: 5 + color: "#444" + } + } + + function test_handleChanges_data() { + var splitViewWidth = testCase.width + var splitViewHeight = testCase.height + + var data = [ + { + tag: "growHandleWidth", + orientation: Qt.Horizontal, + handleComponent: largerHandle, + expectedGeometries: [ + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: 20, height: splitViewHeight }, + { x: 25 + 20, y: 0, width: 100, height: splitViewHeight }, + { x: 25 + 100 + 20, y: 0, width: 20, height: splitViewHeight }, + { x: 25 + 100 + 20 * 2, y: 0, width: splitViewWidth - 25 - 100 - 20 * 2, + height: splitViewHeight } + ] + }, + { + // Same as above except vertical. + tag: "growHandleHeight", + orientation: Qt.Vertical, + handleComponent: largerHandle, + expectedGeometries: [ + { x: 0, y: 0, width: splitViewWidth, height: 25 }, + { x: 0, y: 25, width: splitViewWidth, height: 20 }, + { x: 0, y: 25 + 20, width: splitViewWidth, height: 100 }, + { x: 0, y: 25 + 100 + 20, width: splitViewWidth, height: 20 }, + { x: 0, y: 25 + 100 + 20 * 2, width: splitViewWidth, + height: splitViewHeight - 25 - 100 - 20 * 2 } + ] + }, + { + tag: "shrinkHandleWidth", + orientation: Qt.Horizontal, + handleComponent: smallerHandle, + expectedGeometries: [ + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: 5, height: splitViewHeight }, + { x: 25 + 5, y: 0, width: 100, height: splitViewHeight }, + { x: 25 + 100 + 5, y: 0, width: 5, height: splitViewHeight }, + { x: 25 + 100 + 5 * 2, y: 0, width: splitViewWidth - 25 - 100 - 5 * 2, + height: splitViewHeight } + ] + }, + { + // Same as above except vertical. + tag: "shrinkHandleHeight", + orientation: Qt.Vertical, + handleComponent: smallerHandle, + expectedGeometries: [ + { x: 0, y: 0, width: splitViewWidth, height: 25 }, + { x: 0, y: 25, width: splitViewWidth, height: 5 }, + { x: 0, y: 25 + 5, width: splitViewWidth, height: 100 }, + { x: 0, y: 25 + 100 + 5, width: splitViewWidth, height: 5 }, + { x: 0, y: 25 + 100 + 5 * 2, width: splitViewWidth, + height: splitViewHeight - 25 - 100 - 5 * 2 } + ] + } + ] + return data + } + + function test_handleChanges(data) { + var control = createTemporaryObject(threeSizedItemsComponent, testCase, + { "orientation": data.orientation }) + verify(control) + + control.handle = data.handleComponent + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compareSizes(control, data.expectedGeometries) + } + + function test_insertRemoveItems_data() { + var splitViewWidth = testCase.width + var splitViewHeight = testCase.height + + var data = [ + { + tag: "insertItemAtHorizontalEnd", + orientation: Qt.Horizontal, + insertItemAtIndex: 3, + expectedGeometries: [ + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: 100, height: splitViewHeight }, + { x: 25 + 100 + defaultHorizontalHandleWidth, y: 0, + width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // This was the fill item originally, but since no fill item is explicitly + // specified, and we added an item to the right of it, it is no longer the fill item + // because it's no longer last. + { x: 25 + 100 + defaultHorizontalHandleWidth * 2, y: 0, + width: 200, height: splitViewHeight }, + // Handle for newly added item. + { x: 25 + 100 + 200 + defaultHorizontalHandleWidth * 2, y: 0, + width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // Newly added item. + { x: 25 + 100 + 200 + defaultHorizontalHandleWidth * 3, y: 0, + width: splitViewWidth - 25 - 100 - 200 - defaultHorizontalHandleWidth * 3, + height: splitViewHeight }, + ] + }, + { + tag: "insertItemAtHorizontalBeginning", + orientation: Qt.Horizontal, + insertItemAtIndex: 0, + expectedGeometries: [ + // Newly added item. + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, width: 25, height: splitViewHeight }, + { x: 25 * 2 + defaultHorizontalHandleWidth, y: 0, + width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 * 2 + defaultHorizontalHandleWidth * 2, y: 0, width: 100, height: splitViewHeight }, + { x: 25 * 2 + 100 + defaultHorizontalHandleWidth * 2, y: 0, + width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // Fill item doesn't change. + { x: 25 * 2 + 100 + defaultHorizontalHandleWidth * 3, y: 0, + width: splitViewWidth - 25 * 2 - 100 - defaultHorizontalHandleWidth * 3, + height: splitViewHeight }, + ] + }, + { + tag: "removeItemFromHorizontalEnd", + orientation: Qt.Horizontal, + removeItemAtIndex: 2, + expectedGeometries: [ + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, + width: splitViewWidth - 25 - defaultHorizontalHandleWidth, height: splitViewHeight }, + ] + }, + { + tag: "removeItemFromHorizontalBeginning", + orientation: Qt.Horizontal, + removeItemAtIndex: 0, + expectedGeometries: [ + { x: 0, y: 0, width: 100, height: splitViewHeight }, + { x: 100, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 100 + defaultHorizontalHandleWidth, y: 0, + width: splitViewWidth - 100 - defaultHorizontalHandleWidth, height: splitViewHeight }, + ] + } + ] + return data + } + + Component { + id: smallRectComponent + + Rectangle { + objectName: "darkseagreen" + color: objectName + implicitWidth: 25 + implicitHeight: 25 + } + } + + function test_insertRemoveItems(data) { + var control = createTemporaryObject(threeSizedItemsComponent, testCase, + { "orientation": data.orientation }) + verify(control) + + if (data.hasOwnProperty("removeItemAtIndex")) { + var itemToRemove = control.itemAt(data.removeItemAtIndex) + verify(itemToRemove) + + control.removeItem(itemToRemove) + } else if (data.hasOwnProperty("insertItemAtIndex")) { + var itemToAdd = smallRectComponent.createObject(control) + control.insertItem(data.insertItemAtIndex, itemToAdd) + } + + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compareSizes(control, data.expectedGeometries) + } + + function test_removeAllItems() { + var control = createTemporaryObject(threeSizedItemsComponent, testCase) + verify(control) + + while (control.count > 0) + var itemToRemove = control.removeItem(control.itemAt(0)) + // Shouldn't crash. + } + + function test_hideItems_data() { + var splitViewWidth = testCase.width + var splitViewHeight = testCase.height + + var data = [ + { + tag: "hideItemAtHorizontalEnd", + orientation: Qt.Horizontal, + hideIndices: [2], + expectedGeometries: [ + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, + width: splitViewWidth - 25 - defaultHorizontalHandleWidth, height: splitViewHeight }, + { hidden: true }, // Handle for second item should be hidden. + { hidden: true } // Last item should be hidden. + ] + }, + { + tag: "hideItemAtHorizontalBeginning", + orientation: Qt.Horizontal, + hideIndices: [0], + expectedGeometries: [ + { hidden: true }, // First item should be hidden. + { hidden: true }, // Handle for first item should be hidden. + { x: 0, y: 0, width: 100, height: splitViewHeight }, + { x: 100, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 100 + defaultHorizontalHandleWidth, y: 0, + width: splitViewWidth - 100 - defaultHorizontalHandleWidth, height: splitViewHeight } + ] + }, + { + tag: "hideItemAtVerticalEnd", + orientation: Qt.Vertical, + hideIndices: [2], + expectedGeometries: [ + { x: 0, y: 0, width: splitViewWidth, height: 25 }, + { x: 0, y: 25, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 25 + defaultVerticalHandleHeight, + width: splitViewWidth, height: splitViewHeight - 25 - defaultVerticalHandleHeight }, + { hidden: true }, // Handle for second item should be hidden. + { hidden: true } // Last item should be hidden. + ] + }, + { + tag: "hideItemAtVerticalBeginning", + orientation: Qt.Vertical, + hideIndices: [0], + expectedGeometries: [ + { hidden: true }, // First item should be hidden. + { hidden: true }, // Handle for first item should be hidden. + { x: 0, y: 0, width: splitViewWidth, height: 100 }, + { x: 0, y: 100, width: splitViewWidth, height: defaultVerticalHandleHeight }, + { x: 0, y: 100 + defaultVerticalHandleHeight, + width: splitViewWidth, height: splitViewHeight - 100 - defaultVerticalHandleHeight } + ] + }, + { + // No handles should be visible when there's only one item. + tag: "hideLastTwoHorizontalItems", + orientation: Qt.Horizontal, + hideIndices: [1, 2], + expectedGeometries: [ + { x: 0, y: 0, width: splitViewWidth, height: splitViewHeight }, + { hidden: true }, // Handle for first item should be hidden. + { hidden: true }, // Second item should be hidden. + { hidden: true }, // Handle for second item should be hidden. + { hidden: true } // Third item should be hidden. + ] + } + ] + return data + } + + function test_hideItems(data) { + var control = createTemporaryObject(threeSizedItemsComponent, testCase, + { "orientation": data.orientation }) + verify(control) + + for (var i = 0; i < data.hideIndices.length; ++i) { + var itemToHide = control.itemAt(data.hideIndices[i]) + verify(itemToHide) + itemToHide.visible = false + } + + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compareSizes(control, data.expectedGeometries) + } + + function test_hideAndShowItems_data() { + var splitViewWidth = testCase.width + var splitViewHeight = testCase.height + + var data = [ + { + tag: "hideLastTwoHorizontalItems", + orientation: Qt.Horizontal, + hideIndices: [1, 2], + expectedGeometriesAfterHiding: [ + { x: 0, y: 0, width: splitViewWidth, height: splitViewHeight }, + { hidden: true }, // Handle for first item should be hidden. + { hidden: true }, // Second item should be hidden. + { hidden: true }, // Handle for second item should be hidden. + { hidden: true } // Third item should be hidden. + ], + showIndices: [1], + expectedGeometriesAfterShowing: [ + // First item should be visible with its implicit size. + { x: 0, y: 0, width: 25, height: splitViewHeight }, + // Handle for first item should be visible. + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // Second item should be visible and fill. + { x: 25 + defaultHorizontalHandleWidth, y: 0, + width: splitViewWidth - 25 - defaultHorizontalHandleWidth, height: splitViewHeight }, + { hidden: true }, // Handle for second item should be hidden. + { hidden: true } // Third item should be hidden. + ] + } + ] + return data + } + + function test_hideAndShowItems(data) { + var control = createTemporaryObject(threeSizedItemsComponent, testCase, + { "orientation": data.orientation }) + verify(control) + + for (var i = 0; i < data.hideIndices.length; ++i) { + var itemToHide = control.itemAt(data.hideIndices[i]) + verify(itemToHide) + itemToHide.visible = false + } + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compareSizes(control, data.expectedGeometriesAfterHiding, "after hiding") + + for (i = 0; i < data.showIndices.length; ++i) { + var itemToShow = control.itemAt(data.showIndices[i]) + verify(itemToShow) + itemToShow.visible = true + } + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compareSizes(control, data.expectedGeometriesAfterShowing, "after showing") + } + + function test_moveHiddenItems_data() { + var splitViewWidth = testCase.width + var splitViewHeight = testCase.height + + var data = [ + { + tag: "hideSecondItemAndMoveItToFirst", + orientation: Qt.Horizontal, + hideIndices: [1], + moveFromIndex: 1, + moveToIndex: 0, + expectedGeometriesAfterMoving: [ + { hidden: true }, // First item (was second) should be hidden. + { hidden: true }, // Handle for first item should be hidden. + // Second item (was first) should get its implicit size. + { x: 0, y: 0, width: 25, height: splitViewHeight }, + { x: 25, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 25 + defaultHorizontalHandleWidth, y: 0, + width: splitViewWidth - 25 - defaultHorizontalHandleWidth, height: splitViewHeight }, + ], + showIndices: [0], + expectedGeometriesAfterShowing: [ + // First item (was second) should be visible with its implicit size. + { x: 0, y: 0, width: 100, height: splitViewHeight }, + // Handle for first item (was second) should be visible. + { x: 100, y: 0, width: defaultHorizontalHandleWidth, height: splitViewHeight }, + // Second item (was first) should be visible with its implicit size. + { x: 100 + defaultHorizontalHandleWidth, y: 0, + width: 25, height: splitViewHeight }, + { x: 100 + 25 + defaultHorizontalHandleWidth, y: 0, + width: defaultHorizontalHandleWidth, height: splitViewHeight }, + { x: 100 + 25 + defaultHorizontalHandleWidth * 2, y: 0, + width: splitViewWidth - 100 - 25 - defaultHorizontalHandleWidth * 2, height: splitViewHeight } + ] + } + ] + return data + } + + function test_moveHiddenItems(data) { + var control = createTemporaryObject(threeSizedItemsComponent, testCase, + { "orientation": data.orientation }) + verify(control) + + for (var i = 0; i < data.hideIndices.length; ++i) { + var itemToHide = control.itemAt(data.hideIndices[i]) + verify(itemToHide) + itemToHide.visible = false + } + + control.moveItem(data.moveFromIndex, data.moveToIndex) + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compareSizes(control, data.expectedGeometriesAfterMoving, "after moving") + + for (i = 0; i < data.showIndices.length; ++i) { + var itemToShow = control.itemAt(data.showIndices[i]) + verify(itemToShow) + itemToShow.visible = true + } + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compareSizes(control, data.expectedGeometriesAfterShowing, "after showing") + } + + Component { + id: flickableComponent + + Flickable { + anchors.fill: parent + anchors.margins: 100 + } + } + + function test_draggingHandleInFlickable() { + var flickable = createTemporaryObject(flickableComponent, testCase) + verify(flickable) + + var control = threeSizedItemsComponent.createObject(flickable.contentItem) + verify(control) + + control.anchors.fill = undefined + control.width = 400 + control.height = control.parent.height - 100 + flickable.contentWidth = control.width + flickable.contentHeight = control.height + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + + var contentXSpy = signalSpyComponent.createObject(flickable, + { target: flickable, signalName: "contentXChanged" }) + verify(contentXSpy.valid) + var contentYSpy = signalSpyComponent.createObject(flickable, + { target: flickable, signalName: "contentYChanged" }) + verify(contentYSpy.valid) + + // Drag the first handle to the right; + // the flickable's contentX and contentY shouldn't change. + var firstItem = control.itemAt(0) + var firstItemOriginalWidth = firstItem.width + var handles = findHandles(control) + var firstHandle = handles[0] + // Add some vertical movement in there as well. + mouseDrag(firstHandle, firstHandle.width / 2, firstHandle.height / 2, 100, 50) + compare(contentXSpy.count, 0) + compare(contentYSpy.count, 0) + verify(firstItem.width > firstItemOriginalWidth) + + // Now do the same except vertically. + control.orientation = Qt.Vertical + control.width = control.parent.width - 100 + control.height = 400 + var firstItemOriginalHeight = firstItem.height + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + + // Add some horizontal movement in there as well. + mouseDrag(firstHandle, firstHandle.width / 2, firstHandle.height / 2, 50, 100) + compare(contentXSpy.count, 0) + compare(contentYSpy.count, 0) + verify(firstItem.height > firstItemOriginalHeight) + } + + function test_hoveredPressed() { + if ((Qt.platform.pluginName === "offscreen") || (Qt.platform.pluginName === "minimal")) + skip("Mouse hovering not functional on offscreen/minimal platforms") + + var control = createTemporaryObject(threeSizedItemsComponent, testCase) + verify(control) + control.anchors.margins = 50 + + var handles = findHandles(control) + var firstHandle = handles[0] + + var handleCenter = control.mapFromItem(firstHandle, firstHandle.width / 2, firstHandle.height / 2) + // Test fails if we don't do two moves for some reason... + mouseMove(control, handleCenter.x, handleCenter.y) + mouseMove(control, handleCenter.x, handleCenter.y) + verify(firstHandle.SplitHandle.hovered) + verify(!firstHandle.SplitHandle.pressed) + + mousePress(control, handleCenter.x, handleCenter.y) + verify(firstHandle.SplitHandle.hovered) + verify(firstHandle.SplitHandle.pressed) + + mouseRelease(control, handleCenter.x, handleCenter.y) + verify(firstHandle.SplitHandle.hovered) + verify(!firstHandle.SplitHandle.pressed) + + mouseMove(control, 0, 0) + verify(!firstHandle.SplitHandle.hovered) + verify(!firstHandle.SplitHandle.pressed) + } + + // Tests removing/adding/moving an item while it's pressed. + function test_modifyWhileHoveredPressed() { + if ((Qt.platform.pluginName === "offscreen") || (Qt.platform.pluginName === "minimal")) + skip("Mouse hovering not functional on offscreen/minimal platforms") + + var control = createTemporaryObject(threeSizedItemsComponent, testCase) + verify(control) + control.anchors.margins = 50 + + var handles = findHandles(control) + var firstHandle = handles[0] + + // First, ensure that the handle is hovered + pressed. + var handleCenter = control.mapFromItem(firstHandle, firstHandle.width / 2, firstHandle.height / 2) + // Test fails if we don't do two moves for some reason... + mouseMove(control, handleCenter.x, handleCenter.y) + mouseMove(control, handleCenter.x, handleCenter.y) + verify(firstHandle.SplitHandle.hovered) + verify(!firstHandle.SplitHandle.pressed) + + mousePress(control, handleCenter.x, handleCenter.y) + verify(firstHandle.SplitHandle.hovered) + verify(firstHandle.SplitHandle.pressed) + + // Then, remove it by removing the first item. + control.removeItem(control.itemAt(0)) + handles = findHandles(control) + firstHandle = null + compare(handles.length, 1) + + // No handles should be hovered/pressed. + for (var i = 0; i < handles.length; ++i) { + var handle = handles[i] + verify(!handle.SplitHandle.hovered, "handle at index " + i + " should not be hovered") + verify(!handle.SplitHandle.pressed, "handle at index " + i + " should not be hovered") + } + + mouseRelease(control, handleCenter.x, handleCenter.y) + } + + Component { + id: settingsComponent + Settings { + id: settings + } + } + + function test_saveAndRestoreState_data() { + return [ + { tag: "Horizontal", orientation: Qt.Horizontal, propertyName: "preferredWidth", propertyValue: 123 }, + { tag: "Vertical", orientation: Qt.Vertical, propertyName: "preferredHeight", propertyValue: 234 } + ] + } + + function test_saveAndRestoreState(data) { + var control = createTemporaryObject(threeSizedItemsComponent, testCase, { orientation: data.orientation }) + verify(control) + compare(control.orientation, data.orientation) + + var lastItem = control.itemAt(2) + verify(lastItem) + lastItem.SplitView[data.propertyName] = data.propertyValue + + // Save the state. + var settings = createTemporaryObject(settingsComponent, testCase) + verify(settings) + settings.setValue("splitView", control.saveState()) + + // Recreate the item to restore it to its "default" values. + control = createTemporaryObject(threeSizedItemsComponent, testCase) + lastItem = control.itemAt(2) + verify(lastItem) + compare(lastItem.SplitView[data.propertyName], -1) + + settings = createTemporaryObject(settingsComponent, testCase) + verify(settings) + + // Restore the state. + control.restoreState(settings.value("splitView")) + compare(lastItem.SplitView[data.propertyName], data.propertyValue) + } + + function test_changePreferredSizeDuringLayout() { + var control = createTemporaryObject(threeSizedItemsComponent, testCase) + verify(control) + + var firstItem = control.itemAt(0) + var secondItem = control.itemAt(1) + secondItem.widthChanged.connect(function() { + if (secondItem.width < 10) + firstItem.SplitView.preferredWidth = 50 + }) + + // Change the size of the item so that a layout happens, but + // make the size small enough that the item's onWidthChanged handler gets triggered. + // The onWidthChanged handler will set the preferredWidth of another item during the + // layout, so we need to make sure the assignment isn't lost since we return early in that case. + secondItem.implicitWidth = 5 + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compare(secondItem.width, 5) + compare(firstItem.width, 50) + + // Now do the same for height. + control.orientation = Qt.Vertical + secondItem.heightChanged.connect(function() { + if (secondItem.height < 10) + firstItem.SplitView.preferredHeight = 50 + }) + // Get the polishes for the orientation out of the way so that they + // don't intefere with our results. + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + + secondItem.implicitHeight = 5 + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + compare(secondItem.height, 5) + compare(firstItem.height, 50) + } + + // When the user drags a handle, we internally set preferredWidth/Height + // to reflect the new value. However, we also have to make sure that when + // we do so, it doesn't trigger a delayed layout. This is why we have + // m_ignoreNextDelayedLayoutRequest. This test checks that + // m_ignoreNextDelayedLayoutRequest doesn't interfere with any action from + // the user that results in a delayed layout. + function test_changePreferredSizeDuringLayoutWhileDraggingHandle() { + var control = createTemporaryObject(threeSizedItemsComponent, testCase) + verify(control) + + var firstItem = control.itemAt(0) + var secondItem = control.itemAt(1) + firstItem.widthChanged.connect(function() { + if (firstItem.width === 0) + secondItem.SplitView.preferredWidth = 50 + }) + + // Start dragging the handle. + var handles = findHandles(control) + var targetHandle = handles[0] + mousePress(targetHandle) + verify(control.resizing) + var localPos = testCase.mapToItem(targetHandle, 15, testCase.height / 2) + + // Move the handle to the very left, so that the item's width becomes zero. + mouseMove(targetHandle, -100, targetHandle.height / 2) + verify(control.resizing) + compare(firstItem.width, 0) + compare(secondItem.SplitView.preferredWidth, 50) + compare(secondItem.width, 50) + mouseRelease(targetHandle, -100, targetHandle.height / 2, Qt.LeftButton) + verify(!control.resizing) + } + + Component { + id: oneItemComponent + + SplitView { + Item {} + } + } + + // QTBUG-79270 + function test_hideSplitViewWithOneItem() { + var control = createTemporaryObject(oneItemComponent, testCase) + verify(control) + // Shouldn't be an assertion failure. + control.visible = false + } + + // QTBUG-79302: ensure that the Repeater's items are actually generated. + // test_dragHandle:repeater tests dragging behavior with a Repeater. + function test_repeater(data) { + var control = createTemporaryObject(repeaterSplitViewComponent, testCase) + verify(control) + compare(control.repeater.count, 3) + compare(control.contentChildren.length, 3) + } + + Component { + id: hoverableChildrenSplitViewComponent + + SplitView { + handle: handleComponent + anchors.fill: parent + + MouseArea { + objectName: "mouseArea1" + hoverEnabled: true + + SplitView.preferredWidth: 200 + } + MouseArea { + objectName: "mouseArea2" + hoverEnabled: true + } + } + } + + function test_hoverableChilden() { + if (Qt.platform.pluginName === "offscreen" || Qt.platform.pluginName === "minimal") + skip("Mouse hovering not functional on offscreen/minimal platforms") + + var control = createTemporaryObject(hoverableChildrenSplitViewComponent, testCase) + verify(control) + + verify(isPolishScheduled(control)) + verify(waitForItemPolished(control)) + + // Move the mouse over the handle. + var handles = findHandles(control) + var targetHandle = handles[0] + // Test fails if we don't do two moves for some reason... QTBUG-94968 + mouseMove(targetHandle, targetHandle.width / 2, targetHandle.height / 2) + mouseMove(targetHandle, targetHandle.width / 2, targetHandle.height / 2) + verify(targetHandle.SplitHandle.hovered) + + // Move the mouse to the MouseArea on the left. The handle should no longer be hovered. + mouseMove(control, 100, control.height / 2) + verify(!targetHandle.SplitHandle.hovered) + + // Move the mouse back over the handle. + mouseMove(targetHandle, targetHandle.width / 2, targetHandle.height / 2) + mouseMove(targetHandle, targetHandle.width / 2, targetHandle.height / 2) + verify(targetHandle.SplitHandle.hovered) + + // Move the mouse to the MouseArea on the right. The handle should no longer be hovered. + mouseMove(control, control.width - 100, control.height / 2) + verify(!targetHandle.SplitHandle.hovered) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_stackview.qml b/tests/auto/quickcontrols2/controls/data/tst_stackview.qml new file mode 100644 index 0000000000..a3df827ab0 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_stackview.qml @@ -0,0 +1,1433 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 200 + height: 200 + visible: true + when: windowShown + name: "StackView" + + Item { id: item } + Component { id: textField; TextField { } } + Component { id: component; Item { } } + + Component { + id: stackView + StackView { } + } + + Component { + id: signalSpy + SignalSpy { } + } + + function test_initialItem() { + var control1 = createTemporaryObject(stackView, testCase) + verify(control1) + compare(control1.currentItem, null) + control1.destroy() + + var control2 = createTemporaryObject(stackView, testCase, {initialItem: item}) + verify(control2) + compare(control2.currentItem, item) + control2.destroy() + + var control3 = createTemporaryObject(stackView, testCase, {initialItem: component}) + verify(control3) + verify(control3.currentItem) + control3.destroy() + } + + function test_currentItem() { + var control = createTemporaryObject(stackView, testCase, {initialItem: item}) + verify(control) + compare(control.currentItem, item) + control.push(component) + verify(control.currentItem !== item) + control.pop(StackView.Immediate) + compare(control.currentItem, item) + } + + function test_busy() { + var control = createTemporaryObject(stackView, testCase) + verify(control) + compare(control.busy, false) + + var busyCount = 0 + var busySpy = signalSpy.createObject(control, {target: control, signalName: "busyChanged"}) + verify(busySpy.valid) + + control.push(component) + compare(control.busy, false) + compare(busySpy.count, busyCount) + + control.push(component) + compare(control.busy, true) + compare(busySpy.count, ++busyCount) + tryCompare(control, "busy", false) + compare(busySpy.count, ++busyCount) + + control.replace(component) + compare(control.busy, true) + compare(busySpy.count, ++busyCount) + tryCompare(control, "busy", false) + compare(busySpy.count, ++busyCount) + + control.pop() + compare(control.busy, true) + compare(busySpy.count, ++busyCount) + tryCompare(control, "busy", false) + compare(busySpy.count, ++busyCount) + + control.pushEnter = null + control.pushExit = null + + control.push(component) + compare(control.busy, false) + compare(busySpy.count, busyCount) + + control.replaceEnter = null + control.replaceExit = null + + control.replace(component) + compare(control.busy, false) + compare(busySpy.count, busyCount) + + control.popEnter = null + control.popExit = null + + control.pop() + compare(control.busy, false) + compare(busySpy.count, busyCount) + } + + function test_status() { + var control = createTemporaryObject(stackView, testCase) + verify(control) + + var item1 = component.createObject(control) + compare(item1.StackView.status, StackView.Inactive) + control.push(item1) + compare(item1.StackView.status, StackView.Active) + + var item2 = component.createObject(control) + compare(item2.StackView.status, StackView.Inactive) + control.push(item2) + compare(item2.StackView.status, StackView.Activating) + compare(item1.StackView.status, StackView.Deactivating) + tryCompare(item2.StackView, "status", StackView.Active) + tryCompare(item1.StackView, "status", StackView.Inactive) + + control.pop() + compare(item2.StackView.status, StackView.Deactivating) + compare(item1.StackView.status, StackView.Activating) + tryCompare(item2.StackView, "status", StackView.Inactive) + tryCompare(item1.StackView, "status", StackView.Active) + } + + function test_index() { + var control = createTemporaryObject(stackView, testCase) + verify(control) + + var item1 = component.createObject(control) + compare(item1.StackView.index, -1) + control.push(item1, StackView.Immediate) + compare(item1.StackView.index, 0) + + var item2 = component.createObject(control) + compare(item2.StackView.index, -1) + control.push(item2, StackView.Immediate) + compare(item2.StackView.index, 1) + compare(item1.StackView.index, 0) + + control.pop(StackView.Immediate) + compare(item2.StackView.index, -1) + compare(item1.StackView.index, 0) + } + + function test_view() { + var control = createTemporaryObject(stackView, testCase) + verify(control) + + var item1 = component.createObject(control) + compare(item1.StackView.view, null) + control.push(item1, StackView.Immediate) + compare(item1.StackView.view, control) + + var item2 = component.createObject(control) + compare(item2.StackView.view, null) + control.push(item2, StackView.Immediate) + compare(item2.StackView.view, control) + compare(item1.StackView.view, control) + + control.pop(StackView.Immediate) + compare(item2.StackView.view, null) + compare(item1.StackView.view, control) + } + + function test_depth() { + var control = createTemporaryObject(stackView, testCase) + verify(control) + + var depthChanges = 0 + var emptyChanges = 0 + var depthSpy = signalSpy.createObject(control, {target: control, signalName: "depthChanged"}) + var emptySpy = signalSpy.createObject(control, {target: control, signalName: "emptyChanged"}) + verify(depthSpy.valid) + verify(emptySpy.valid) + compare(control.depth, 0) + compare(control.empty, true) + + control.push(item, StackView.Immediate) + compare(control.depth, 1) + compare(depthSpy.count, ++depthChanges) + compare(control.empty, false) + compare(emptySpy.count, ++emptyChanges) + + control.clear() + compare(control.depth, 0) + compare(depthSpy.count, ++depthChanges) + compare(control.empty, true) + compare(emptySpy.count, ++emptyChanges) + + control.push(component, StackView.Immediate) + compare(control.depth, 1) + compare(depthSpy.count, ++depthChanges) + compare(control.empty, false) + compare(emptySpy.count, ++emptyChanges) + + control.push(component, StackView.Immediate) + compare(control.depth, 2) + compare(depthSpy.count, ++depthChanges) + compare(control.empty, false) + compare(emptySpy.count, emptyChanges) + + control.replace(component, StackView.Immediate) + compare(control.depth, 2) + compare(depthSpy.count, depthChanges) + compare(control.empty, false) + compare(emptySpy.count, emptyChanges) + + control.replace([component, component], StackView.Immediate) + compare(control.depth, 3) + compare(depthSpy.count, ++depthChanges) + compare(control.empty, false) + compare(emptySpy.count, emptyChanges) + + control.pop(null, StackView.Immediate) + compare(control.depth, 1) + compare(depthSpy.count, ++depthChanges) + compare(control.empty, false) + compare(emptySpy.count, emptyChanges) + + control.pop(StackView.Immediate) // ignored + compare(control.depth, 1) + compare(depthSpy.count, depthChanges) + compare(control.empty, false) + compare(emptySpy.count, emptyChanges) + + control.clear() + compare(control.depth, 0) + compare(depthSpy.count, ++depthChanges) + compare(control.empty, true) + compare(emptySpy.count, ++emptyChanges) + + control.clear() + compare(control.depth, 0) + compare(depthSpy.count, depthChanges) + compare(control.empty, true) + compare(emptySpy.count, emptyChanges) + } + + function test_size() { + var container = createTemporaryObject(component, testCase, {width: 200, height: 200}) + verify(container) + var control = stackView.createObject(container, {width: 100, height: 100}) + verify(control) + + container.width += 10 + container.height += 20 + compare(control.width, 100) + compare(control.height, 100) + + control.push(item, StackView.Immediate) + compare(item.width, control.width) + compare(item.height, control.height) + + control.width = 200 + control.height = 200 + compare(item.width, control.width) + compare(item.height, control.height) + + control.clear() + control.width += 10 + control.height += 20 + verify(item.width !== control.width) + verify(item.height !== control.height) + + control.push(item, StackView.Immediate) + compare(item.width, control.width) + compare(item.height, control.height) + } + + function test_focus_data() { + return [ + { tag: "true", focus: true, forceActiveFocus: false }, + { tag: "false", focus: false, forceActiveFocus: false }, + { tag: "forceActiveFocus()", focus: false, forceActiveFocus: true }, + ] + } + + function test_focus(data) { + var control = createTemporaryObject(stackView, testCase, {initialItem: item, width: 200, height: 200}) + verify(control) + + if (data.focus) + control.focus = true + if (data.forceActiveFocus) + control.forceActiveFocus() + compare(control.activeFocus, data.focus || data.forceActiveFocus) + + var page = control.push(textField, StackView.Immediate) + verify(page) + compare(control.currentItem, page) + compare(page.activeFocus, control.activeFocus) + + control.pop(StackView.Immediate) + compare(control.currentItem, item) + compare(item.activeFocus, data.focus || data.forceActiveFocus) + verify(!page.activeFocus) + } + + function test_find() { + var control = createTemporaryObject(stackView, testCase) + verify(control) + + var item1 = component.createObject(control, {objectName: "1"}) + var item2 = component.createObject(control, {objectName: "2"}) + var item3 = component.createObject(control, {objectName: "3"}) + + control.push(item1, StackView.Immediate) + control.push(item2, StackView.Immediate) + control.push(item3, StackView.Immediate) + + compare(control.find(function(item, index) { return index === 0 }), item1) + compare(control.find(function(item) { return item.objectName === "1" }), item1) + + compare(control.find(function(item, index) { return index === 1 }), item2) + compare(control.find(function(item) { return item.objectName === "2" }), item2) + + compare(control.find(function(item, index) { return index === 2 }), item3) + compare(control.find(function(item) { return item.objectName === "3" }), item3) + + compare(control.find(function() { return false }), null) + compare(control.find(function() { return true }), item3) + } + + function test_get() { + var control = createTemporaryObject(stackView, testCase) + verify(control) + + control.push([item, component, component], StackView.Immediate) + + verify(control.get(0, StackView.DontLoad)) + compare(control.get(0, StackView.ForceLoad), item) + + verify(!control.get(1, StackView.DontLoad)) + + verify(control.get(2, StackView.DontLoad)) + verify(control.get(2, StackView.ForceLoad)) + } + + function test_push() { + var control = createTemporaryObject(stackView, testCase) + verify(control) + + // missing arguments + ignoreWarning(Qt.resolvedUrl("tst_stackview.qml") + ":69:9: QML StackView: push: missing arguments") + compare(control.push(), null) + + // nothing to push + ignoreWarning(Qt.resolvedUrl("tst_stackview.qml") + ":69:9: QML StackView: push: nothing to push") + compare(control.push(StackView.Immediate), null) + + // unsupported type + ignoreWarning(Qt.resolvedUrl("tst_stackview.qml") + ":69:9: QML StackView: push: QtObject is not supported. Must be Item or Component.") + control.push(Qt.createQmlObject('import QtQml; QtObject { }', control)) + + // push(item) + var item1 = component.createObject(control, {objectName:"1"}) + compare(control.push(item1, StackView.Immediate), item1) + compare(control.depth, 1) + compare(control.currentItem, item1) + + // push([item]) + var item2 = component.createObject(control, {objectName:"2"}) + compare(control.push([item2], StackView.Immediate), item2) + compare(control.depth, 2) + compare(control.currentItem, item2) + + // push(item, {properties}) + var item3 = component.createObject(control) + compare(control.push(item3, {objectName:"3"}, StackView.Immediate), item3) + compare(item3.objectName, "3") + compare(control.depth, 3) + compare(control.currentItem, item3) + + // push([item, {properties}]) + var item4 = component.createObject(control) + compare(control.push([item4, {objectName:"4"}], StackView.Immediate), item4) + compare(item4.objectName, "4") + compare(control.depth, 4) + compare(control.currentItem, item4) + + // push(component, {properties}) + var item5 = control.push(component, {objectName:"5"}, StackView.Immediate) + compare(item5.objectName, "5") + compare(control.depth, 5) + compare(control.currentItem, item5) + + // push([component, {properties}]) + var item6 = control.push([component, {objectName:"6"}], StackView.Immediate) + compare(item6.objectName, "6") + compare(control.depth, 6) + compare(control.currentItem, item6) + } + + function test_pop() { + var control = createTemporaryObject(stackView, testCase) + verify(control) + + var items = [] + for (var i = 0; i < 7; ++i) + items.push(component.createObject(control, {objectName:i})) + + control.push(items, StackView.Immediate) + + ignoreWarning(Qt.resolvedUrl("tst_stackview.qml") + ":69:9: QML StackView: pop: too many arguments") + compare(control.pop(1, 2, 3), null) + + // pop the top most item + compare(control.pop(StackView.Immediate), items[6]) + compare(control.depth, 6) + compare(control.currentItem, items[5]) + + // pop down to the current item + compare(control.pop(control.currentItem, StackView.Immediate), null) + compare(control.depth, 6) + compare(control.currentItem, items[5]) + + // pop down to (but not including) the Nth item + compare(control.pop(items[3], StackView.Immediate), items[5]) + compare(control.depth, 4) + compare(control.currentItem, items[3]) + + // pop the top most item + compare(control.pop(undefined, StackView.Immediate), items[3]) + compare(control.depth, 3) + compare(control.currentItem, items[2]) + + // don't pop non-existent item + ignoreWarning(Qt.resolvedUrl("tst_stackview.qml") + ":69:9: QML StackView: pop: unknown argument: " + testCase) + compare(control.pop(testCase, StackView.Immediate), null) + compare(control.depth, 3) + compare(control.currentItem, items[2]) + + // pop all items down to (but not including) the 1st item + control.pop(null, StackView.Immediate) + compare(control.depth, 1) + compare(control.currentItem, items[0]) + } + + function test_replace() { + var control = createTemporaryObject(stackView, testCase) + verify(control) + + // missing arguments + ignoreWarning(Qt.resolvedUrl("tst_stackview.qml") + ":69:9: QML StackView: replace: missing arguments") + compare(control.replace(), null) + + // nothing to push + ignoreWarning(Qt.resolvedUrl("tst_stackview.qml") + ":69:9: QML StackView: replace: nothing to push") + compare(control.replace(StackView.Immediate), null) + + // unsupported type + ignoreWarning(Qt.resolvedUrl("tst_stackview.qml") + ":69:9: QML StackView: replace: QtObject is not supported. Must be Item or Component.") + compare(control.replace(Qt.createQmlObject('import QtQml; QtObject { }', control)), null) + + // replace(item) + var item1 = component.createObject(control, {objectName:"1"}) + compare(control.replace(item1, StackView.Immediate), item1) + compare(control.depth, 1) + compare(control.currentItem, item1) + + // replace([item]) + var item2 = component.createObject(control, {objectName:"2"}) + compare(control.replace([item2], StackView.Immediate), item2) + compare(control.depth, 1) + compare(control.currentItem, item2) + + // replace(item, {properties}) + var item3 = component.createObject(control) + compare(control.replace(item3, {objectName:"3"}, StackView.Immediate), item3) + compare(item3.objectName, "3") + compare(control.depth, 1) + compare(control.currentItem, item3) + + // replace([item, {properties}]) + var item4 = component.createObject(control) + compare(control.replace([item4, {objectName:"4"}], StackView.Immediate), item4) + compare(item4.objectName, "4") + compare(control.depth, 1) + compare(control.currentItem, item4) + + // replace(component, {properties}) + var item5 = control.replace(component, {objectName:"5"}, StackView.Immediate) + compare(item5.objectName, "5") + compare(control.depth, 1) + compare(control.currentItem, item5) + + // replace([component, {properties}]) + var item6 = control.replace([component, {objectName:"6"}], StackView.Immediate) + compare(item6.objectName, "6") + compare(control.depth, 1) + compare(control.currentItem, item6) + + // replace the topmost item + control.push(component) + compare(control.depth, 2) + var item7 = control.replace(control.get(1), component, StackView.Immediate) + compare(control.depth, 2) + compare(control.currentItem, item7) + + // replace the item in the middle + control.push(component) + control.push(component) + control.push(component) + compare(control.depth, 5) + var item8 = control.replace(control.get(2), component, StackView.Immediate) + compare(control.depth, 3) + compare(control.currentItem, item8) + } + + function test_clear() { + var control = createTemporaryObject(stackView, testCase) + verify(control) + + control.push(component, StackView.Immediate) + + control.clear() + compare(control.depth, 0) + compare(control.busy, false) + + control.push(component, StackView.Immediate) + + control.clear(StackView.PopTransition) + compare(control.depth, 0) + compare(control.busy, true) + tryCompare(control, "busy", false) + } + + function test_visibility_data() { + return [ + {tag:"default transitions", properties: {}}, + {tag:"null transitions", properties: {pushEnter: null, pushExit: null, popEnter: null, popExit: null}} + ] + } + + function test_visibility(data) { + var control = createTemporaryObject(stackView, testCase, data.properties) + verify(control) + + var item1 = component.createObject(control) + control.push(item1, StackView.Immediate) + verify(item1.visible) + + var item2 = component.createObject(control) + control.push(item2) + tryCompare(item1, "visible", false) + verify(item2.visible) + + control.pop() + verify(item1.visible) + tryCompare(item2, "visible", false) + } + + Component { + id: transitionView + StackView { + property int popEnterRuns + property int popExitRuns + property int pushEnterRuns + property int pushExitRuns + property int replaceEnterRuns + property int replaceExitRuns + popEnter: Transition { + PauseAnimation { duration: 1 } + onRunningChanged: if (!running) ++popEnterRuns + } + popExit: Transition { + PauseAnimation { duration: 1 } + onRunningChanged: if (!running) ++popExitRuns + } + pushEnter: Transition { + PauseAnimation { duration: 1 } + onRunningChanged: if (!running) ++pushEnterRuns + } + pushExit: Transition { + PauseAnimation { duration: 1 } + onRunningChanged: if (!running) ++pushExitRuns + } + replaceEnter: Transition { + PauseAnimation { duration: 1 } + onRunningChanged: if (!running) ++replaceEnterRuns + } + replaceExit: Transition { + PauseAnimation { duration: 1 } + onRunningChanged: if (!running) ++replaceExitRuns + } + } + } + + function test_transitions_data() { + return [ + { tag: "undefined", operation: undefined, + pushEnterRuns: [0,1,1,1], pushExitRuns: [0,1,1,1], replaceEnterRuns: [0,0,1,1], replaceExitRuns: [0,0,1,1], popEnterRuns: [0,0,0,1], popExitRuns: [0,0,0,1] }, + { tag: "immediate", operation: StackView.Immediate, + pushEnterRuns: [0,0,0,0], pushExitRuns: [0,0,0,0], replaceEnterRuns: [0,0,0,0], replaceExitRuns: [0,0,0,0], popEnterRuns: [0,0,0,0], popExitRuns: [0,0,0,0] }, + { tag: "push", operation: StackView.PushTransition, + pushEnterRuns: [1,2,3,4], pushExitRuns: [0,1,2,3], replaceEnterRuns: [0,0,0,0], replaceExitRuns: [0,0,0,0], popEnterRuns: [0,0,0,0], popExitRuns: [0,0,0,0] }, + { tag: "pop", operation: StackView.PopTransition, + pushEnterRuns: [0,0,0,0], pushExitRuns: [0,0,0,0], replaceEnterRuns: [0,0,0,0], replaceExitRuns: [0,0,0,0], popEnterRuns: [1,2,3,4], popExitRuns: [0,1,2,3] }, + { tag: "replace", operation: StackView.ReplaceTransition, + pushEnterRuns: [0,0,0,0], pushExitRuns: [0,0,0,0], replaceEnterRuns: [1,2,3,4], replaceExitRuns: [0,1,2,3], popEnterRuns: [0,0,0,0], popExitRuns: [0,0,0,0] }, + ] + } + + function test_transitions(data) { + var control = createTemporaryObject(transitionView, testCase) + verify(control) + + control.push(component, data.operation) + tryCompare(control, "busy", false) + compare(control.pushEnterRuns, data.pushEnterRuns[0]) + compare(control.pushExitRuns, data.pushExitRuns[0]) + compare(control.replaceEnterRuns, data.replaceEnterRuns[0]) + compare(control.replaceExitRuns, data.replaceExitRuns[0]) + compare(control.popEnterRuns, data.popEnterRuns[0]) + compare(control.popExitRuns, data.popExitRuns[0]) + + control.push(component, data.operation) + tryCompare(control, "busy", false) + compare(control.pushEnterRuns, data.pushEnterRuns[1]) + compare(control.pushExitRuns, data.pushExitRuns[1]) + compare(control.replaceEnterRuns, data.replaceEnterRuns[1]) + compare(control.replaceExitRuns, data.replaceExitRuns[1]) + compare(control.popEnterRuns, data.popEnterRuns[1]) + compare(control.popExitRuns, data.popExitRuns[1]) + + control.replace(component, data.operation) + tryCompare(control, "busy", false) + compare(control.pushEnterRuns, data.pushEnterRuns[2]) + compare(control.pushExitRuns, data.pushExitRuns[2]) + compare(control.replaceEnterRuns, data.replaceEnterRuns[2]) + compare(control.replaceExitRuns, data.replaceExitRuns[2]) + compare(control.popEnterRuns, data.popEnterRuns[2]) + compare(control.popExitRuns, data.popExitRuns[2]) + + control.pop(data.operation) + tryCompare(control, "busy", false) + compare(control.pushEnterRuns, data.pushEnterRuns[3]) + compare(control.pushExitRuns, data.pushExitRuns[3]) + compare(control.replaceEnterRuns, data.replaceEnterRuns[3]) + compare(control.replaceExitRuns, data.replaceExitRuns[3]) + compare(control.popEnterRuns, data.popEnterRuns[3]) + compare(control.popExitRuns, data.popExitRuns[3]) + } + + TestItem { + id: indestructibleItem + } + + Component { + id: destructibleComponent + TestItem { } + } + + function test_ownership_data() { + return [ + {tag:"item, transition", arg: indestructibleItem, operation: StackView.Transition, destroyed: false}, + {tag:"item, immediate", arg: indestructibleItem, operation: StackView.Immediate, destroyed: false}, + {tag:"component, transition", arg: destructibleComponent, operation: StackView.Transition, destroyed: true}, + {tag:"component, immediate", arg: destructibleComponent, operation: StackView.Immediate, destroyed: true}, + {tag:"url, transition", arg: Qt.resolvedUrl("TestItem.qml"), operation: StackView.Transition, destroyed: true}, + {tag:"url, immediate", arg: Qt.resolvedUrl("TestItem.qml"), operation: StackView.Immediate, destroyed: true} + ] + } + + function test_ownership(data) { + var control = createTemporaryObject(transitionView, testCase, {initialItem: component}) + verify(control) + + // push-pop + control.push(data.arg, StackView.Immediate) + verify(control.currentItem) + verify(control.currentItem.hasOwnProperty("destroyedCallback")) + var destroyed = false + control.currentItem.destroyedCallback = function() { destroyed = true } + control.pop(data.operation) + tryCompare(control, "busy", false) + wait(0) // deferred delete + compare(destroyed, data.destroyed) + + // push-replace + control.push(data.arg, StackView.Immediate) + verify(control.currentItem) + verify(control.currentItem.hasOwnProperty("destroyedCallback")) + destroyed = false + control.currentItem.destroyedCallback = function() { destroyed = true } + control.replace(component, data.operation) + tryCompare(control, "busy", false) + wait(0) // deferred delete + compare(destroyed, data.destroyed) + } + + Component { + id: removeComponent + + Item { + objectName: "removeItem" + StackView.onRemoved: destroy() + } + } + + function test_destroyOnRemoved() { + var control = createTemporaryObject(stackView, testCase, { initialItem: component }) + verify(control) + + var item = removeComponent.createObject(control) + verify(item) + + var removedSpy = signalSpy.createObject(control, { target: item.StackView, signalName: "removed" }) + verify(removedSpy) + verify(removedSpy.valid) + + var destructionSpy = signalSpy.createObject(control, { target: item.Component, signalName: "destruction" }) + verify(destructionSpy) + verify(destructionSpy.valid) + + // push-pop + control.push(item, StackView.Immediate) + compare(control.currentItem, item) + control.pop(StackView.Transition) + item = null + tryCompare(removedSpy, "count", 1) + tryCompare(destructionSpy, "count", 1) + compare(control.busy, false) + + item = removeComponent.createObject(control) + verify(item) + + removedSpy.target = item.StackView + verify(removedSpy.valid) + + destructionSpy.target = item.Component + verify(destructionSpy.valid) + + // push-replace + control.push(item, StackView.Immediate) + compare(control.currentItem, item) + control.replace(component, StackView.Transition) + item = null + tryCompare(removedSpy, "count", 2) + tryCompare(destructionSpy, "count", 2) + compare(control.busy, false) + } + + function test_pushOnRemoved() { + var control = createTemporaryObject(stackView, testCase, { initialItem: component }) + verify(control) + + var item = control.push(component, StackView.Immediate) + verify(item) + + item.StackView.onRemoved.connect(function() { + ignoreWarning(/.*QML StackView: cannot push while already in the process of completing a pop/) + control.push(component, StackView.Immediate) + }) + + // don't crash (QTBUG-62153) + control.pop(StackView.Immediate) + } + + Component { + id: attachedItem + Item { + property int index: StackView.index + property StackView view: StackView.view + property int status: StackView.status + } + } + + function test_attached() { + var control = createTemporaryObject(stackView, testCase, {initialItem: attachedItem}) + + compare(control.get(0).index, 0) + compare(control.get(0).view, control) + compare(control.get(0).status, StackView.Active) + + control.push(attachedItem, StackView.Immediate) + + compare(control.get(0).index, 0) + compare(control.get(0).view, control) + compare(control.get(0).status, StackView.Inactive) + + compare(control.get(1).index, 1) + compare(control.get(1).view, control) + compare(control.get(1).status, StackView.Active) + + control.pop(StackView.Immediate) + + compare(control.get(0).index, 0) + compare(control.get(0).view, control) + compare(control.get(0).status, StackView.Active) + } + + Component { + id: testButton + Button { + property int clicks: 0 + onClicked: ++clicks + } + } + + function test_interaction() { + var control = createTemporaryObject(stackView, testCase, {initialItem: testButton, width: testCase.width, height: testCase.height}) + verify(control) + + var firstButton = control.currentItem + verify(firstButton) + + var firstClicks = 0 + var secondClicks = 0 + var thirdClicks = 0 + + // push - default transition + var secondButton = control.push(testButton) + compare(control.busy, true) + mouseClick(firstButton) // filtered while busy + mouseClick(secondButton) // filtered while busy + compare(firstButton.clicks, firstClicks) + compare(secondButton.clicks, secondClicks) + tryCompare(control, "busy", false) + mouseClick(secondButton) + compare(secondButton.clicks, ++secondClicks) + + // replace - default transition + var thirdButton = control.replace(testButton) + compare(control.busy, true) + mouseClick(secondButton) // filtered while busy + mouseClick(thirdButton) // filtered while busy + compare(secondButton.clicks, secondClicks) + compare(thirdButton.clicks, thirdClicks) + tryCompare(control, "busy", false) + secondButton = null + secondClicks = 0 + mouseClick(thirdButton) + compare(thirdButton.clicks, ++thirdClicks) + + // pop - default transition + control.pop() + compare(control.busy, true) + mouseClick(firstButton) // filtered while busy + mouseClick(thirdButton) // filtered while busy + compare(firstButton.clicks, firstClicks) + compare(thirdButton.clicks, thirdClicks) + tryCompare(control, "busy", false) + thirdButton = null + thirdClicks = 0 + mouseClick(firstButton) + compare(firstButton.clicks, ++firstClicks) + + // push - immediate operation + secondButton = control.push(testButton, StackView.Immediate) + compare(control.busy, false) + mouseClick(secondButton) + compare(secondButton.clicks, ++secondClicks) + + // replace - immediate operation + thirdButton = control.replace(testButton, StackView.Immediate) + compare(control.busy, false) + secondButton = null + secondClicks = 0 + mouseClick(thirdButton) + compare(thirdButton.clicks, ++thirdClicks) + + // pop - immediate operation + control.pop(StackView.Immediate) + compare(control.busy, false) + thirdButton = null + thirdClicks = 0 + mouseClick(firstButton) + compare(firstButton.clicks, ++firstClicks) + + // push - null transition + control.pushEnter = null + control.pushExit = null + secondButton = control.push(testButton) + compare(control.busy, false) + mouseClick(secondButton) + compare(secondButton.clicks, ++secondClicks) + + // replace - null transition + control.replaceEnter = null + control.replaceExit = null + thirdButton = control.replace(testButton) + compare(control.busy, false) + secondButton = null + secondClicks = 0 + mouseClick(thirdButton) + compare(thirdButton.clicks, ++thirdClicks) + + // pop - null transition + control.popEnter = null + control.popExit = null + control.pop() + compare(control.busy, false) + thirdButton = null + thirdClicks = 0 + mouseClick(firstButton) + compare(firstButton.clicks, ++firstClicks) + } + + Component { + id: mouseArea + MouseArea { + property int presses: 0 + property int releases: 0 + property int clicks: 0 + property int doubleClicks: 0 + property int cancels: 0 + onPressed: ++presses + onReleased: ++releases + onClicked: ++clicks + onDoubleClicked: ++doubleClicks + onCanceled: ++cancels + } + } + + // QTBUG-50305 + function test_events() { + var control = createTemporaryObject(stackView, testCase, {initialItem: mouseArea, width: testCase.width, height: testCase.height}) + verify(control) + + var testItem = control.currentItem + verify(testItem) + + testItem.doubleClicked.connect(function() { + control.push(mouseArea) // ungrab -> cancel + }) + + mouseDoubleClickSequence(testItem) + compare(testItem.presses, 2) + compare(testItem.releases, 2) + compare(testItem.clicks, 1) + compare(testItem.doubleClicks, 1) + compare(testItem.pressed, false) + compare(testItem.cancels, 0) + } + + function test_ungrab() { + var control = createTemporaryObject(stackView, testCase, {initialItem: mouseArea, width: testCase.width, height: testCase.height}) + verify(control) + + var testItem = control.currentItem + verify(testItem) + + mousePress(testItem) + control.push(mouseArea) + tryCompare(control, "busy", false) + mouseRelease(testItem) + + compare(testItem.presses, 1) + compare(testItem.releases, 0) + compare(testItem.clicks, 0) + compare(testItem.doubleClicks, 0) + compare(testItem.pressed, false) + compare(testItem.cancels, 1) + } + + function test_failures() { + var control = createTemporaryObject(stackView, testCase, {initialItem: component}) + verify(control) + + var error = Qt.resolvedUrl("non-existent.qml") + ":-1 No such file or directory" + + ignoreWarning("QQmlComponent: Component is not ready") + ignoreWarning(Qt.resolvedUrl("tst_stackview.qml") + ":69:9: QML StackView: push: " + error) + control.push(Qt.resolvedUrl("non-existent.qml")) + + ignoreWarning("QQmlComponent: Component is not ready") + ignoreWarning(Qt.resolvedUrl("tst_stackview.qml") + ":69:9: QML StackView: replace: " + error) + control.replace(Qt.resolvedUrl("non-existent.qml")) + + ignoreWarning(Qt.resolvedUrl("tst_stackview.qml") + ":69:9: QML StackView: push: invalid url: x://[v]") + control.push("x://[v]") + + ignoreWarning(Qt.resolvedUrl("tst_stackview.qml") + ":69:9: QML StackView: replace: invalid url: x://[v]") + control.replace("x://[v]") + + control.pop() + } + + Component { + id: rectangle + Rectangle { + property color initialColor + Component.onCompleted: initialColor = color + } + } + + function test_properties() { + var control = createTemporaryObject(stackView, testCase) + verify(control) + + var rect = control.push(rectangle, {color: "#ff0000"}) + compare(rect.color, "#ff0000") + compare(rect.initialColor, "#ff0000") + } + + Component { + id: signalTest + Control { + id: ctrl + property SignalSpy activatedSpy: SignalSpy { target: ctrl.StackView; signalName: "activated" } + property SignalSpy activatingSpy: SignalSpy { target: ctrl.StackView; signalName: "activating" } + property SignalSpy deactivatedSpy: SignalSpy { target: ctrl.StackView; signalName: "deactivated" } + property SignalSpy deactivatingSpy: SignalSpy { target: ctrl.StackView; signalName: "deactivating" } + } + } + + function test_signals() { + var control = createTemporaryObject(stackView, testCase) + verify(control) + + var item1 = signalTest.createObject(control) + compare(item1.StackView.status, StackView.Inactive) + control.push(item1) + compare(item1.StackView.status, StackView.Active) + compare(item1.activatedSpy.count, 1) + compare(item1.activatingSpy.count, 1) + compare(item1.deactivatedSpy.count, 0) + compare(item1.deactivatingSpy.count, 0) + + var item2 = signalTest.createObject(control) + compare(item2.StackView.status, StackView.Inactive) + control.push(item2) + compare(item2.StackView.status, StackView.Activating) + compare(item2.activatedSpy.count, 0) + compare(item2.activatingSpy.count, 1) + compare(item2.deactivatedSpy.count, 0) + compare(item2.deactivatingSpy.count, 0) + compare(item1.StackView.status, StackView.Deactivating) + compare(item1.activatedSpy.count, 1) + compare(item1.activatingSpy.count, 1) + compare(item1.deactivatedSpy.count, 0) + compare(item1.deactivatingSpy.count, 1) + tryCompare(item2.activatedSpy, "count", 1) + tryCompare(item1.deactivatedSpy, "count", 1) + + control.pop() + compare(item2.StackView.status, StackView.Deactivating) + compare(item2.activatedSpy.count, 1) + compare(item2.activatingSpy.count, 1) + compare(item2.deactivatedSpy.count, 0) + compare(item2.deactivatingSpy.count, 1) + compare(item1.StackView.status, StackView.Activating) + compare(item1.activatedSpy.count, 1) + compare(item1.activatingSpy.count, 2) + compare(item1.deactivatedSpy.count, 1) + compare(item1.deactivatingSpy.count, 1) + tryCompare(item1.activatedSpy, "count", 2) + } + + // QTBUG-56158 + function test_repeatedPop() { + var control = createTemporaryObject(stackView, testCase, {initialItem: component, width: testCase.width, height: testCase.height}) + verify(control) + + for (var i = 0; i < 12; ++i) + control.push(component) + tryCompare(control, "busy", false) + + while (control.depth > 1) { + control.pop() + wait(50) + } + tryCompare(control, "busy", false) + } + + function test_pushSameItem() { + var control = createTemporaryObject(stackView, testCase) + verify(control) + + control.push(item, StackView.Immediate) + compare(control.currentItem, item) + compare(control.depth, 1) + + // Pushing the same Item should do nothing. + ignoreWarning(Qt.resolvedUrl("tst_stackview.qml") + ":69:9: QML StackView: push: nothing to push") + control.push(item, StackView.Immediate) + compare(control.currentItem, item) + compare(control.depth, 1) + + // Push a component so that it becomes current. + var current = control.push(component, StackView.Immediate) + compare(control.currentItem, current) + compare(control.depth, 2) + + // Push a bunch of stuff. "item" is already in the stack, so it should be ignored. + current = control.push(component, item, StackView.Immediate) + verify(current !== item) + compare(control.currentItem, current) + compare(control.depth, 3) + } + + function test_visible() { + var control = createTemporaryObject(stackView, testCase) + verify(control) + + var item1 = component.createObject(control) + control.push(item1, StackView.Immediate) + compare(item1.visible, true) + compare(item1.StackView.visible, item1.visible) + + var item2 = component.createObject(control) + control.push(item2, StackView.Immediate) + compare(item1.visible, false) + compare(item2.visible, true) + compare(item1.StackView.visible, false) + compare(item2.StackView.visible, true) + + // keep explicitly visible + item2.StackView.visible = true + control.push(component, StackView.Immediate) + compare(item2.visible, true) + compare(item2.StackView.visible, true) + + // show underneath + item1.StackView.visible = true + compare(item1.visible, true) + compare(item1.StackView.visible, true) + + control.pop(StackView.Immediate) + compare(item2.visible, true) + compare(item2.StackView.visible, true) + + // hide the top-most + item2.StackView.visible = false + compare(item2.visible, false) + compare(item2.StackView.visible, false) + + // reset the top-most + item2.StackView.visible = undefined + compare(item2.visible, true) + compare(item2.StackView.visible, true) + + // reset underneath + item1.StackView.visible = undefined + compare(item1.visible, false) + compare(item1.StackView.visible, false) + + control.pop(StackView.Immediate) + compare(item1.visible, true) + compare(item1.StackView.visible, true) + } + + function test_resolveInitialItem() { + var control = createTemporaryObject(stackView, testCase, {initialItem: "TestItem.qml"}) + verify(control) + verify(control.currentItem) + } + + function test_resolve() { + var control = createTemporaryObject(stackView, testCase) + verify(control) + + var item = control.push("TestItem.qml") + compare(control.depth, 1) + verify(item) + } + + // QTBUG-65084 + function test_mouseArea() { + var ma = createTemporaryObject(mouseArea, testCase, {width: testCase.width, height: testCase.height}) + verify(ma) + + var control = stackView.createObject(ma, {width: testCase.width, height: testCase.height}) + verify(control) + + mousePress(control) + verify(ma.pressed) + + mouseRelease(control) + verify(!ma.pressed) + + var touch = touchEvent(control) + touch.press(0, control).commit() + verify(ma.pressed) + + touch.release(0, control).commit() + verify(!ma.pressed) + } + + // Separate function to ensure that the temporary value created to hold the return value of the Qt.createComponent() + // call is out of scope when the caller calls gc(). + function stackViewFactory() + { + return createTemporaryObject(stackView, testCase, {initialItem: Qt.createComponent("TestItem.qml")}) + } + + function test_initalItemOwnership() + { + var control = stackViewFactory() + verify(control) + gc() + verify(control.initialItem) + } + + // Need to use this specific structure in order to reproduce the crash. + Component { + id: clearUponDestructionContainerComponent + + Item { + id: container + objectName: "container" + + property alias control: stackView + property var onDestructionCallback + + property Component clearUponDestructionComponent: Component { + id: clearUponDestructionComponent + + Item { + objectName: "clearUponDestructionItem" + Component.onDestruction: container.onDestructionCallback(stackView) + } + } + + StackView { + id: stackView + initialItem: Item { + objectName: "initialItem" + } + } + } + } + + // QTBUG-80353 + // Tests that calling clear() in Component.onDestruction in response to that + // item being removed (e.g. via an earlier call to clear()) results in a warning and not a crash. + function test_recursiveClearClear() { + let container = createTemporaryObject(clearUponDestructionContainerComponent, testCase, + { onDestructionCallback: function(stackView) { stackView.clear(StackView.Immediate) }}) + verify(container) + + let control = container.control + control.push(container.clearUponDestructionComponent, StackView.Immediate) + + // Shouldn't crash. + ignoreWarning(/.*cannot clear while already in the process of completing a clear/) + control.clear(StackView.Immediate) + } + + function test_recursivePopClear() { + let container = createTemporaryObject(clearUponDestructionContainerComponent, testCase, + { onDestructionCallback: function(stackView) { stackView.clear(StackView.Immediate) }}) + verify(container) + + let control = container.control + control.push(container.clearUponDestructionComponent, StackView.Immediate) + + // Pop all items except the first, removing the second item we pushed in the process. + // Shouldn't crash. + ignoreWarning(/.*cannot clear while already in the process of completing a pop/) + control.pop(null, StackView.Immediate) + } + + function test_recursivePopPop() { + let container = createTemporaryObject(clearUponDestructionContainerComponent, testCase, + { onDestructionCallback: function(stackView) { stackView.pop(null, StackView.Immediate) }}) + verify(container) + + let control = container.control + // Push an extra item so that we can call pop(null) and reproduce the conditions for the crash. + control.push(component, StackView.Immediate) + control.push(container.clearUponDestructionComponent, StackView.Immediate) + + // Pop the top item, then pop down to the first item in response. + ignoreWarning(/.*cannot pop while already in the process of completing a pop/) + control.pop(StackView.Immediate) + } + + function test_recursiveReplaceClear() { + let container = createTemporaryObject(clearUponDestructionContainerComponent, testCase, + { onDestructionCallback: function(stackView) { stackView.clear(StackView.Immediate) }}) + verify(container) + + let control = container.control + control.push(container.clearUponDestructionComponent, StackView.Immediate) + + // Replace the top item, then clear in response. + ignoreWarning(/.*cannot clear while already in the process of completing a replace/) + control.replace(component, StackView.Immediate) + } + + function test_recursiveClearReplace() { + let container = createTemporaryObject(clearUponDestructionContainerComponent, testCase, + { onDestructionCallback: function(stackView) { stackView.replace(component, StackView.Immediate) }}) + verify(container) + + let control = container.control + control.push(container.clearUponDestructionComponent, StackView.Immediate) + + // Replace the top item, then clear in response. + ignoreWarning(/.*cannot replace while already in the process of completing a clear/) + control.clear(StackView.Immediate) + } + + Component { + id: rectangleComponent + Rectangle {} + } + + Component { + id: qtbug57267_StackViewComponent + + StackView { + id: stackView + + popEnter: Transition { + XAnimator { from: (stackView.mirrored ? -1 : 1) * -stackView.width; to: 0; duration: 400; easing.type: Easing.Linear } + } + popExit: Transition { + XAnimator { from: 0; to: (stackView.mirrored ? -1 : 1) * stackView.width; duration: 400; easing.type: Easing.Linear } + } + pushEnter: Transition { + XAnimator { from: (stackView.mirrored ? -1 : 1) * stackView.width; to: 0; duration: 400; easing.type: Easing.Linear } + } + pushExit: Transition { + XAnimator { from: 0; to: (stackView.mirrored ? -1 : 1) * -stackView.width; duration: 400; easing.type: Easing.Linear } + } + replaceEnter: Transition { + XAnimator { from: (stackView.mirrored ? -1 : 1) * stackView.width; to: 0; duration: 400; easing.type: Easing.Linear } + } + replaceExit: Transition { + XAnimator { from: 0; to: (stackView.mirrored ? -1 : 1) * -stackView.width; duration: 400; easing.type: Easing.Linear } + } + } + } + + function test_qtbug57267() { + let redRect = createTemporaryObject(rectangleComponent, testCase, { color: "red" }) + verify(redRect) + let blueRect = createTemporaryObject(rectangleComponent, testCase, { color: "blue" }) + verify(blueRect) + let control = createTemporaryObject(qtbug57267_StackViewComponent, testCase, + { "anchors.fill": testCase, initialItem: redRect }) + verify(control) + + control.replace(blueRect) + compare(control.currentItem, blueRect) + compare(control.depth, 1) + + // Wait until the animation has started and then interrupt it by pushing the redRect. + tryCompare(control, "busy", true) + control.replace(redRect) + // The blue rect shouldn't be visible since we replaced it and therefore interrupted its animation. + tryCompare(blueRect, "visible", false) + // We did the replace very early on, so the transition for the redRect should still be happening. + compare(control.busy, true) + compare(redRect.visible, true) + + // After finishing the transition, the red rect should still be visible. + tryCompare(control, "busy", false) + compare(redRect.visible, true) + } + + // QTBUG-84381 + function test_clearAndPushAfterDepthChange() { + var control = createTemporaryObject(stackView, testCase, { + popEnter: null, popExit: null, pushEnter: null, + pushExit: null, replaceEnter: null, replaceExit: null + }) + verify(control) + + control.depthChanged.connect(function() { + if (control.depth === 2) { + // Shouldn't assert. + ignoreWarning(/.*QML StackView: cannot clear while already in the process of completing a push/) + control.clear() + // Shouldn't crash. + ignoreWarning(/.*QML StackView: cannot push while already in the process of completing a push/) + control.push(component) + } + }) + + control.push(component) + control.push(component) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_swipedelegate.qml b/tests/auto/quickcontrols2/controls/data/tst_swipedelegate.qml new file mode 100644 index 0000000000..40030f3d67 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_swipedelegate.qml @@ -0,0 +1,1783 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + + +TestCase { + id: testCase + width: 200 + height: 200 + visible: true + when: windowShown + name: "SwipeDelegate" + + readonly property int dragDistance: Math.max(20, Qt.styleHints.startDragDistance + 5) + + Component { + id: backgroundFillComponent + SwipeDelegate { + background: Item { anchors.fill: parent } + } + } + + Component { + id: backgroundCenterInComponent + SwipeDelegate { + background: Item { anchors.centerIn: parent } + } + } + + Component { + id: backgroundLeftComponent + SwipeDelegate { + background: Item { anchors.left: parent.left } + } + } + + Component { + id: backgroundRightComponent + SwipeDelegate { + background: Item { anchors.right: parent.right } + } + } + + Component { + id: contentItemFillComponent + SwipeDelegate { + contentItem: Item { anchors.fill: parent } + } + } + + Component { + id: contentItemCenterInComponent + SwipeDelegate { + contentItem: Item { anchors.centerIn: parent } + } + } + + Component { + id: contentItemLeftComponent + SwipeDelegate { + contentItem: Item { anchors.left: parent.left } + } + } + + Component { + id: contentItemRightComponent + SwipeDelegate { + contentItem: Item { anchors.right: parent.right } + } + } + + function test_horizontalAnchors_data() { + return [ + { tag: "background, fill", component: backgroundFillComponent, itemName: "background", warningLocation: ":69:25" }, + { tag: "background, centerIn", component: backgroundCenterInComponent, itemName: "background", warningLocation: ":76:25" }, + { tag: "background, left", component: backgroundLeftComponent, itemName: "background", warningLocation: ":83:25" }, + { tag: "background, right", component: backgroundRightComponent, itemName: "background", warningLocation: ":90:25" }, + { tag: "contentItem, fill", component: contentItemFillComponent, itemName: "contentItem", warningLocation: ":97:26" }, + { tag: "contentItem, centerIn", component: contentItemCenterInComponent, itemName: "contentItem", warningLocation: ":104:26" }, + { tag: "contentItem, left", component: contentItemLeftComponent, itemName: "contentItem", warningLocation: ":111:26" }, + { tag: "contentItem, right", component: contentItemRightComponent, itemName: "contentItem", warningLocation: ":118:26" } + ]; + } + + function test_horizontalAnchors(data) { + var warningMessage = Qt.resolvedUrl("tst_swipedelegate.qml") + data.warningLocation + + ": QML QQuickItem: SwipeDelegate: cannot use horizontal anchors with " + data.itemName + "; unable to layout the item." + + ignoreWarning(warningMessage); + + var control = createTemporaryObject(data.component, testCase); + verify(control.contentItem); + } + + Component { + id: greenLeftComponent + + Rectangle { + objectName: "leftItem" + anchors.fill: parent + color: "green" + } + } + + Component { + id: redRightComponent + + Rectangle { + objectName: "rightItem" + anchors.fill: parent + color: "red" + } + } + + Component { + id: swipeDelegateComponent + + SwipeDelegate { + id: swipeDelegate + text: "SwipeDelegate" + width: 150 + swipe.left: greenLeftComponent + swipe.right: redRightComponent + } + } + + Component { + id: signalSpyComponent + + SignalSpy {} + } + + Component { + id: itemComponent + + Item {} + } + + // Assumes that the delegate is smaller than the width of the control. + function swipe(control, from, to) { + // Sanity check. + compare(control.swipe.position, from); + + var distance = (to - from) * control.width; + + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton); + mouseMove(control, control.width / 2 + distance, control.height / 2); + mouseRelease(control, control.width / 2 + distance, control.height / 2, Qt.LeftButton); + compare(control.swipe.position, to, "Expected swipe.position to be " + to + + " after swiping from " + from + ", but it's " + control.swipe.position); + + if (control.swipe.position === -1.0) { + if (control.swipe.right) + verify(control.swipe.rightItem); + else if (control.swipe.behind) + verify(control.swipe.behindItem); + } else if (control.swipe.position === 1.0) { + if (control.swipe.left) + verify(control.swipe.leftItem); + else if (control.swipe.behind) + verify(control.swipe.behindItem); + } + } + + function test_settingDelegates() { + var control = createTemporaryObject(swipeDelegateComponent, testCase); + verify(control); + + ignoreWarning(Qt.resolvedUrl("tst_swipedelegate.qml") + + ":168:9: QML SwipeDelegate: cannot set both behind and left/right properties") + control.swipe.behind = itemComponent; + + // Shouldn't be any warnings when unsetting delegates. + control.swipe.left = null; + compare(control.swipe.leftItem, null); + + // right is still set. + ignoreWarning(Qt.resolvedUrl("tst_swipedelegate.qml") + + ":168:9: QML SwipeDelegate: cannot set both behind and left/right properties") + control.swipe.behind = itemComponent; + + control.swipe.right = null; + compare(control.swipe.rightItem, null); + + control.swipe.behind = itemComponent; + + ignoreWarning(Qt.resolvedUrl("tst_swipedelegate.qml") + + ":168:9: QML SwipeDelegate: cannot set both behind and left/right properties") + control.swipe.left = itemComponent; + + ignoreWarning(Qt.resolvedUrl("tst_swipedelegate.qml") + + ":168:9: QML SwipeDelegate: cannot set both behind and left/right properties") + control.swipe.right = itemComponent; + + control.swipe.behind = null; + control.swipe.left = greenLeftComponent; + control.swipe.right = redRightComponent; + + // Test that the user is warned when attempting to set or unset left or + // right item while they're exposed. + // First, try the left item. + swipe(control, 0.0, 1.0); + + var oldLeft = control.swipe.left; + var oldLeftItem = control.swipe.leftItem; + ignoreWarning(Qt.resolvedUrl("tst_swipedelegate.qml") + + ":168:9: QML SwipeDelegate: left/right/behind properties may only be set when swipe.position is 0") + control.swipe.left = null; + compare(control.swipe.left, oldLeft); + compare(control.swipe.leftItem, oldLeftItem); + + // Try the same thing with the right item. + swipe(control, 1.0, -1.0); + + var oldRight = control.swipe.right; + var oldRightItem = control.swipe.rightItem; + ignoreWarning(Qt.resolvedUrl("tst_swipedelegate.qml") + + ":168:9: QML SwipeDelegate: left/right/behind properties may only be set when swipe.position is 0") + control.swipe.right = null; + compare(control.swipe.right, oldRight); + compare(control.swipe.rightItem, oldRightItem); + + // Return to the default position. + swipe(control, -1.0, 0.0); + + tryCompare(control.background, "x", 0, 1000); + + // Try the same thing with the behind item. + control.swipe.left = null; + verify(!control.swipe.left); + verify(!control.swipe.leftItem); + control.swipe.right = null; + verify(!control.swipe.right); + verify(!control.swipe.rightItem); + control.swipe.behind = greenLeftComponent; + verify(control.swipe.behind); + verify(!control.swipe.behindItem); + + swipe(control, 0.0, 1.0); + + var oldBehind = control.swipe.behind; + var oldBehindItem = control.swipe.behindItem; + ignoreWarning(Qt.resolvedUrl("tst_swipedelegate.qml") + + ":168:9: QML SwipeDelegate: left/right/behind properties may only be set when swipe.position is 0") + control.swipe.behind = null; + compare(control.swipe.behind, oldBehind); + compare(control.swipe.behindItem, oldBehindItem); + } + + function test_defaults() { + var control = createTemporaryObject(swipeDelegateComponent, testCase); + verify(control); + + compare(control.baselineOffset, control.contentItem.y + control.contentItem.baselineOffset); + compare(control.swipe.position, 0); + verify(!control.pressed); + verify(!control.swipe.complete); + } + + SignalSequenceSpy { + id: mouseSignalSequenceSpy + signals: ["pressed", "released", "canceled", "clicked", "doubleClicked", "pressedChanged", "pressAndHold"] + } + + function test_swipe() { + var control = createTemporaryObject(swipeDelegateComponent, testCase); + verify(control); + + var overDragDistance = Math.round(dragDistance * 1.1); + + var completedSpy = signalSpyComponent.createObject(control, { target: control.swipe, signalName: "completed" }); + verify(completedSpy); + verify(completedSpy.valid); + + var openedSpy = signalSpyComponent.createObject(control, { target: control.swipe, signalName: "opened" }); + verify(openedSpy); + verify(openedSpy.valid); + + var closedSpy = signalSpyComponent.createObject(control, { target: control.swipe, signalName: "closed" }); + verify(closedSpy); + verify(closedSpy.valid); + + mouseSignalSequenceSpy.target = control; + mouseSignalSequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], "pressed"]; + mousePress(control, control.width / 2, control.height / 2); + verify(control.pressed); + compare(control.swipe.position, 0.0); + verify(!control.swipe.complete); + compare(completedSpy.count, 0); + compare(openedSpy.count, 0); + compare(closedSpy.count, 0); + verify(mouseSignalSequenceSpy.success); + verify(!control.swipe.leftItem); + verify(!control.swipe.rightItem); + + // Drag to the right so that leftItem is created and visible. + mouseMove(control, control.width / 2 + overDragDistance, control.height / 2); + verify(control.pressed); + compare(control.swipe.position, overDragDistance / control.width); + verify(!control.swipe.complete); + compare(completedSpy.count, 0); + compare(openedSpy.count, 0); + compare(closedSpy.count, 0); + verify(control.swipe.leftItem); + verify(control.swipe.leftItem.visible); + compare(control.swipe.leftItem.parent, control); + compare(control.swipe.leftItem.objectName, "leftItem"); + verify(!control.swipe.rightItem); + + // Go back to 0. + mouseMove(control, control.width / 2, control.height / 2); + verify(control.pressed); + compare(control.swipe.position, 0.0); + verify(!control.swipe.complete); + compare(completedSpy.count, 0); + compare(openedSpy.count, 0); + compare(closedSpy.count, 0); + verify(control.swipe.leftItem); + verify(control.swipe.leftItem.visible); + compare(control.swipe.leftItem.parent, control); + compare(control.swipe.leftItem.objectName, "leftItem"); + verify(!control.swipe.rightItem); + + // Try the other direction. The right item should be created and visible, + // and the left item should be hidden. + mouseMove(control, control.width / 2 - overDragDistance, control.height / 2); + verify(control.pressed); + compare(control.swipe.position, -overDragDistance / control.width); + verify(!control.swipe.complete); + compare(completedSpy.count, 0); + compare(openedSpy.count, 0); + compare(closedSpy.count, 0); + verify(control.swipe.leftItem); + verify(!control.swipe.leftItem.visible); + verify(control.swipe.rightItem); + verify(control.swipe.rightItem.visible); + compare(control.swipe.rightItem.parent, control); + compare(control.swipe.rightItem.objectName, "rightItem"); + + // Now release outside the right edge of the control. + mouseMove(control, control.width * 1.1, control.height / 2); + verify(control.pressed); + compare(control.swipe.position, 0.6); + verify(!control.swipe.complete); + compare(completedSpy.count, 0); + compare(openedSpy.count, 0); + compare(closedSpy.count, 0); + verify(control.swipe.leftItem); + verify(control.swipe.leftItem.visible); + verify(control.swipe.rightItem); + verify(!control.swipe.rightItem.visible); + + mouseSignalSequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false }], "canceled"]; + mouseRelease(control, control.width / 2, control.height / 2); + verify(!control.pressed); + tryCompare(control.swipe, "position", 1.0); + tryCompare(control.swipe, "complete", true); + compare(completedSpy.count, 1); + compare(openedSpy.count, 1); + compare(closedSpy.count, 0); + verify(mouseSignalSequenceSpy.success); + verify(control.swipe.leftItem); + verify(control.swipe.leftItem.visible); + verify(control.swipe.rightItem); + verify(!control.swipe.rightItem.visible); + tryCompare(control.contentItem, "x", control.width + control.leftPadding); + + // Swiping from the right and releasing early should return position to 1.0. + mouseSignalSequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], "pressed"]; + mousePress(control, control.width / 2, control.height / 2); + verify(control.pressed); + compare(control.swipe.position, 1.0); + // complete should still be true, because we haven't moved yet, and hence + // haven't started grabbing behind's mouse events. + verify(control.swipe.complete); + compare(completedSpy.count, 1); + compare(openedSpy.count, 1); + compare(closedSpy.count, 0); + verify(mouseSignalSequenceSpy.success); + + mouseMove(control, control.width / 2 - overDragDistance, control.height / 2); + verify(control.pressed); + verify(!control.swipe.complete); + compare(completedSpy.count, 1); + compare(openedSpy.count, 1); + compare(closedSpy.count, 0); + compare(control.swipe.position, 1.0 - overDragDistance / control.width); + + // Since we went over the drag distance, we should expect canceled() to be emitted. + mouseSignalSequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false }], "canceled"]; + mouseRelease(control, control.width * 0.4, control.height / 2); + verify(!control.pressed); + tryCompare(control.swipe, "position", 1.0); + tryCompare(control.swipe, "complete", true); + compare(completedSpy.count, 2); + compare(openedSpy.count, 2); + compare(closedSpy.count, 0); + verify(mouseSignalSequenceSpy.success); + tryCompare(control.contentItem, "x", control.width + control.leftPadding); + + // Swiping from the right and releasing should return contents to default position. + mouseSignalSequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], "pressed"]; + mousePress(control, control.width / 2, control.height / 2); + verify(control.pressed); + compare(control.swipe.position, 1.0); + verify(control.swipe.complete); + compare(completedSpy.count, 2); + compare(openedSpy.count, 2); + compare(closedSpy.count, 0); + verify(mouseSignalSequenceSpy.success); + + mouseMove(control, control.width * -0.1, control.height / 2); + verify(control.pressed); + verify(!control.swipe.complete); + compare(completedSpy.count, 2); + compare(openedSpy.count, 2); + compare(closedSpy.count, 0); + compare(control.swipe.position, 0.4); + + mouseSignalSequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false }], "canceled"]; + mouseRelease(control, control.width * -0.1, control.height / 2); + verify(!control.pressed); + tryCompare(control.swipe, "position", 0.0); + verify(!control.swipe.complete); + compare(completedSpy.count, 2); + compare(openedSpy.count, 2); + tryCompare(closedSpy, "count", 1); + verify(mouseSignalSequenceSpy.success); + tryCompare(control.contentItem, "x", control.leftPadding); + } + + function test_swipeVelocity_data() { + return [ + { tag: "positive velocity", direction: 1 }, + { tag: "negative velocity", direction: -1 } + ]; + } + + function test_swipeVelocity(data) { + skip("QTBUG-52003"); + + var control = createTemporaryObject(swipeDelegateComponent, testCase); + verify(control); + + var distance = Math.round(dragDistance * 1.1); + if (distance >= control.width / 2) + skip("This test requires a startDragDistance that is less than half the width of the control"); + + distance *= data.direction; + + mouseSignalSequenceSpy.target = control; + mouseSignalSequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], "pressed"]; + mousePress(control, control.width / 2, control.height / 2); + verify(control.pressed); + compare(control.swipe.position, 0.0); + verify(!control.swipe.complete); + verify(mouseSignalSequenceSpy.success); + verify(!control.swipe.leftItem); + verify(!control.swipe.rightItem); + + // Swipe quickly to the side over a distance that is longer than the drag threshold, + // quicker than the expose velocity threshold, but shorter than the halfway mark. + mouseMove(control, control.width / 2 + distance, control.height / 2); + verify(control.pressed); + compare(control.swipe.position, distance / control.width); + verify(control.swipe.position < 0.5); + verify(!control.swipe.complete); + + var expectedVisibleItem; + var expectedVisibleObjectName; + var expectedHiddenItem; + var expectedContentItemX; + if (distance > 0) { + expectedVisibleObjectName = "leftItem"; + expectedVisibleItem = control.swipe.leftItem; + expectedHiddenItem = control.swipe.rightItem; + expectedContentItemX = control.width + control.leftPadding; + } else { + expectedVisibleObjectName = "rightItem"; + expectedVisibleItem = control.swipe.rightItem; + expectedHiddenItem = control.swipe.leftItem; + expectedContentItemX = -control.width + control.leftPadding; + } + verify(expectedVisibleItem); + verify(expectedVisibleItem.visible); + compare(expectedVisibleItem.parent, control); + compare(expectedVisibleItem.objectName, expectedVisibleObjectName); + verify(!expectedHiddenItem); + + mouseSignalSequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false }], "released", "clicked"]; + // Add a delay to ensure that the release event doesn't happen too quickly, + // and hence that the second timestamp isn't zero (can happen with e.g. release builds). + mouseRelease(control, control.width / 2 + distance, control.height / 2, Qt.LeftButton, Qt.NoModifier, 30); + verify(!control.pressed); + compare(control.swipe.position, data.direction); + verify(control.swipe.complete); + verify(mouseSignalSequenceSpy.success); + verify(expectedVisibleItem); + verify(expectedVisibleItem.visible); + verify(!expectedHiddenItem); + tryCompare(control.contentItem, "x", expectedContentItemX); + } + + Component { + id: swipeDelegateWithButtonComponent + SwipeDelegate { + text: "SwipeDelegate" + width: 150 + swipe.right: Button { + width: parent.width + height: parent.height + text: "Boo!" + } + } + } + + function test_eventsToLeftAndRight() { + var control = createTemporaryObject(swipeDelegateWithButtonComponent, testCase); + verify(control); + + var closedSpy = signalSpyComponent.createObject(control, { target: control.swipe, signalName: "closed" }); + verify(closedSpy); + verify(closedSpy.valid); + + // The button should be pressed instead of the SwipeDelegate. + mouseDrag(control, control.width / 2, 0, -control.width, 0); + // Mouse has been released by this stage. + verify(!control.pressed); + compare(control.swipe.position, -1.0); + verify(control.swipe.rightItem); + verify(control.swipe.rightItem.visible); + compare(control.swipe.rightItem.parent, control); + + var buttonPressedSpy = signalSpyComponent.createObject(control, { target: control.swipe.rightItem, signalName: "pressed" }); + verify(buttonPressedSpy); + verify(buttonPressedSpy.valid); + var buttonReleasedSpy = signalSpyComponent.createObject(control, { target: control.swipe.rightItem, signalName: "released" }); + verify(buttonReleasedSpy); + verify(buttonReleasedSpy.valid); + var buttonClickedSpy = signalSpyComponent.createObject(control, { target: control.swipe.rightItem, signalName: "clicked" }); + verify(buttonClickedSpy); + verify(buttonClickedSpy.valid); + + // Now press the button. + mousePress(control, control.width / 2, control.height / 2); + verify(!control.pressed); + var button = control.swipe.rightItem; + verify(button.pressed); + compare(buttonPressedSpy.count, 1); + compare(buttonReleasedSpy.count, 0); + compare(buttonClickedSpy.count, 0); + + mouseRelease(control, control.width / 2, control.height / 2); + verify(!button.pressed); + compare(buttonPressedSpy.count, 1); + compare(buttonReleasedSpy.count, 1); + compare(buttonClickedSpy.count, 1); + + // Returning back to a position of 0 and pressing on the control should + // result in the control being pressed. + mouseDrag(control, control.width / 2, 0, control.width * 0.6, 0); + tryCompare(closedSpy, "count", 1); + compare(control.swipe.position, 0); + mousePress(control, control.width / 2, control.height / 2); + verify(control.pressed); + verify(!button.pressed); + mouseRelease(control, control.width / 2, control.height / 2); + verify(!control.pressed); + } + + function test_mouseButtons() { + var control = createTemporaryObject(swipeDelegateComponent, testCase); + verify(control); + + // click + mouseSignalSequenceSpy.target = control; + mouseSignalSequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], "pressed"]; + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton); + compare(control.pressed, true); + + verify(mouseSignalSequenceSpy.success); + + mouseSignalSequenceSpy.expectedSequence = [["pressedChanged", { "pressed": false }], "released", "clicked"]; + mouseRelease(control, control.width / 2, control.height / 2, Qt.LeftButton); + compare(control.pressed, false); + verify(mouseSignalSequenceSpy.success); + + // right button + mouseSignalSequenceSpy.expectedSequence = []; + mousePress(control, control.width / 2, control.height / 2, Qt.RightButton); + compare(control.pressed, false); + + mouseRelease(control, control.width / 2, control.height / 2, Qt.RightButton); + compare(control.pressed, false); + verify(mouseSignalSequenceSpy.success); + + // double click + mouseSignalSequenceSpy.expectedSequence = [ + ["pressedChanged", { "pressed": true }], + "pressed", + ["pressedChanged", { "pressed": false }], + "released", + "clicked", + ["pressedChanged", { "pressed": true }], + "pressed", + "doubleClicked", + ["pressedChanged", { "pressed": false }], + "released" + ]; + mouseDoubleClickSequence(control, control.width / 2, control.height / 2, Qt.LeftButton); + verify(mouseSignalSequenceSpy.success); + + // press and hold + var pressAndHoldSpy = signalSpyComponent.createObject(control, { target: control, signalName: "pressAndHold" }); + verify(pressAndHoldSpy); + verify(pressAndHoldSpy.valid); + + mouseSignalSequenceSpy.expectedSequence = [ + ["pressedChanged", { "pressed": true }], + "pressed", + "pressAndHold", + ["pressedChanged", { "pressed": false }], + "released" + ]; + mousePress(control); + compare(control.pressed, true); + tryCompare(pressAndHoldSpy, "count", 1); + + mouseRelease(control); + compare(control.pressed, false); + verify(mouseSignalSequenceSpy.success); + } + + Component { + id: removableDelegatesComponent + + ListView { + id: listView + width: 100 + height: 120 + + model: ListModel { + ListElement { name: "Apple" } + ListElement { name: "Orange" } + ListElement { name: "Pear" } + } + + delegate: SwipeDelegate { + id: rootDelegate + text: modelData + width: listView.width + + property alias removeAnimation: onRemoveAnimation + + ListView.onRemove: onRemoveAnimation.start() + + SequentialAnimation { + id: onRemoveAnimation + + PropertyAction { + target: rootDelegate + property: "ListView.delayRemove" + value: true + } + NumberAnimation { + target: rootDelegate + property: "height" + to: 0 + easing.type: Easing.InOutQuad + } + PropertyAction { + target: rootDelegate; + property: "ListView.delayRemove"; + value: false + } + } + + swipe.left: Rectangle { + objectName: "rectangle" + color: SwipeDelegate.pressed ? "#333" : "#444" + anchors.fill: parent + + SwipeDelegate.onClicked: listView.model.remove(index) + + Label { + objectName: "label" + text: "Remove" + color: "white" + anchors.centerIn: parent + } + } + } + } + } + + function test_removableDelegates() { + var listView = createTemporaryObject(removableDelegatesComponent, testCase); + verify(listView); + compare(listView.count, 3); + + // Expose the remove button. + var firstItem = listView.itemAt(0, 0); + mousePress(listView, firstItem.width / 2, firstItem.height / 2); + verify(firstItem.pressed); + compare(firstItem.swipe.position, 0.0); + verify(!firstItem.swipe.complete); + verify(!firstItem.swipe.leftItem); + + mouseMove(listView, firstItem.width * 1.1, firstItem.height / 2); + verify(firstItem.pressed); + compare(firstItem.swipe.position, 0.6); + verify(!firstItem.swipe.complete); + verify(firstItem.swipe.leftItem); + verify(!firstItem.swipe.leftItem.SwipeDelegate.pressed); + + mouseRelease(listView, firstItem.width / 2, firstItem.height / 2); + verify(!firstItem.pressed); + tryCompare(firstItem.swipe, "position", 1.0); + tryCompare(firstItem.swipe, "complete", true); + compare(listView.count, 3); + + // Wait for it to settle down. + tryCompare(firstItem.contentItem, "x", firstItem.leftPadding + firstItem.width); + + var leftClickedSpy = signalSpyComponent.createObject(firstItem.swipe.leftItem, + { target: firstItem.swipe.leftItem.SwipeDelegate, signalName: "clicked" }); + verify(leftClickedSpy); + verify(leftClickedSpy.valid); + + // Click the left item to remove the delegate from the list. + var contentItemX = firstItem.contentItem.x; + mousePress(listView, firstItem.width / 2, firstItem.height / 2); + verify(firstItem.swipe.leftItem.SwipeDelegate.pressed); + compare(leftClickedSpy.count, 0); + verify(firstItem.pressed); + + mouseRelease(listView, firstItem.width / 2, firstItem.height / 2); + verify(!firstItem.swipe.leftItem.SwipeDelegate.pressed); + compare(leftClickedSpy.count, 1); + verify(!firstItem.pressed); + leftClickedSpy = null; + tryCompare(firstItem.removeAnimation, "running", true); + // There was a bug where the resizeContent() would be called because the height + // of the control was changing due to the animation. contentItem would then + // change x position and hence be visible when it shouldn't be. + verify(firstItem.removeAnimation.running); + while (1) { + wait(10) + if (firstItem && firstItem.removeAnimation && firstItem.removeAnimation.running) + compare(firstItem.contentItem.x, contentItemX); + else + break; + } + compare(listView.count, 2); + } + + Component { + id: leadingTrailingXComponent + SwipeDelegate { + id: delegate + width: 150 + text: "SwipeDelegate" + + swipe.left: Rectangle { + x: delegate.background.x - width + width: delegate.width + height: delegate.height + color: "green" + } + + swipe.right: Rectangle { + x: delegate.background.x + delegate.background.width + width: delegate.width + height: delegate.height + color: "red" + } + } + } + + Component { + id: leadingTrailingAnchorsComponent + SwipeDelegate { + id: delegate + width: 150 + text: "SwipeDelegate" + + swipe.left: Rectangle { + anchors.right: delegate.background.left + width: delegate.width + height: delegate.height + color: "green" + } + + swipe.right: Rectangle { + anchors.left: delegate.background.right + width: delegate.width + height: delegate.height + color: "red" + } + } + } + + function test_leadingTrailing_data() { + return [ + { tag: "x", component: leadingTrailingXComponent }, + { tag: "anchors", component: leadingTrailingAnchorsComponent }, + ]; + } + + function test_leadingTrailing(data) { + var control = createTemporaryObject(data.component, testCase); + verify(control); + + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton); + mouseMove(control, control.width, control.height / 2); + verify(control.swipe.leftItem); + compare(control.swipe.leftItem.x, -control.width / 2); + mouseRelease(control, control.width / 2, control.height / 2, Qt.LeftButton); + } + + function test_minMaxPosition() { + var control = createTemporaryObject(leadingTrailingXComponent, testCase); + verify(control); + + // Should be limited within the range -1.0 to 1.0. + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton); + mouseMove(control, control.width * 1.5, control.height / 2); + compare(control.swipe.position, 1.0); + mouseMove(control, control.width * 1.6, control.height / 2); + compare(control.swipe.position, 1.0); + mouseMove(control, control.width * -1.6, control.height / 2); + compare(control.swipe.position, -1.0); + mouseRelease(control, control.width / 2, control.height / 2, Qt.LeftButton); + } + + Component { + id: emptySwipeDelegateComponent + + SwipeDelegate { + text: "SwipeDelegate" + width: 150 + } + } + + Component { + id: smallLeftComponent + + Rectangle { + width: 80 + height: 40 + color: "green" + } + } + + // swipe.position should be scaled to the width of the relevant delegate, + // and it shouldn't be possible to drag past the delegate (so that content behind the control is visible). + function test_delegateWidth() { + var control = createTemporaryObject(emptySwipeDelegateComponent, testCase); + verify(control); + + control.swipe.left = smallLeftComponent; + + // Ensure that the position is scaled to the width of the currently visible delegate. + var overDragDistance = Math.round(dragDistance * 1.1); + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton); + mouseMove(control, control.width / 2 + overDragDistance, control.height / 2); + verify(control.swipe.leftItem); + compare(control.swipe.position, overDragDistance / control.swipe.leftItem.width); + + mouseMove(control, control.width / 2 + control.swipe.leftItem.width, control.height / 2); + compare(control.swipe.position, 1.0); + + // Ensure that it's not possible to drag past the (left) delegate. + mouseMove(control, control.width / 2 + control.swipe.leftItem.width + 1, control.height / 2); + compare(control.swipe.position, 1.0); + + // Now release over the right side; the position should be 1.0 and the background + // should be "anchored" to the right side of the left delegate item. + mouseMove(control, control.width / 2 + control.swipe.leftItem.width, control.height / 2); + mouseRelease(control, control.width / 2 + control.swipe.leftItem.width, control.height / 2, Qt.LeftButton); + compare(control.swipe.position, 1.0); + tryCompare(control.background, "x", control.swipe.leftItem.width, 1000); + } + + SignalSpy { + id: leftVisibleSpy + signalName: "visibleChanged" + } + + SignalSpy { + id: rightVisibleSpy + signalName: "visibleChanged" + } + + function test_positionAfterSwipeCompleted() { + var control = createTemporaryObject(swipeDelegateComponent, testCase); + verify(control); + + // Ensure that both delegates are constructed. + mousePress(control, 0, control.height / 2, Qt.LeftButton); + mouseMove(control, control.width * 1.1, control.height / 2); + verify(control.swipe.leftItem); + mouseMove(control, control.width * -0.1, control.height / 2); + verify(control.swipe.rightItem); + + // Expose the left delegate. + mouseMove(control, control.swipe.leftItem.width, control.height / 2); + mouseRelease(control, control.swipe.leftItem.width, control.height / 2); + verify(control.swipe.complete); + compare(control.swipe.position, 1.0); + + leftVisibleSpy.target = control.swipe.leftItem; + rightVisibleSpy.target = control.swipe.rightItem; + + // Swipe from right to left without exposing the right item, + // and make sure that the right item never becomes visible + // (and hence that the left item never loses visibility). + mousePress(control, control.swipe.leftItem.width - 1, control.height / 2, Qt.LeftButton); + compare(leftVisibleSpy.count, 0); + compare(rightVisibleSpy.count, 0); + var newX = control.swipe.leftItem.width - Math.round(dragDistance * 1.1) -1; + mouseMove(control, newX, control.height / 2); + compare(leftVisibleSpy.count, 0); + compare(rightVisibleSpy.count, 0); + compare(control.swipe.position, (newX + 1) / control.swipe.leftItem.width); + + mouseMove(control, 0, control.height / 2); + compare(control.swipe.position, 1 / control.swipe.leftItem.width); + // Because we move from (width - 1) to 0, so one pixel remains + + // Test swiping over a distance that is greater than the width of the left item. + mouseMove(control, -1, control.height / 2); + verify(control.swipe.rightItem); + compare(control.swipe.position, 0); + + // Now go back to 1.0. + mouseMove(control, control.swipe.leftItem.width - 1, control.height / 2); + compare(control.swipe.position, 1.0); + tryCompare(control.background, "x", control.swipe.leftItem.width, 1000); + mouseRelease(control, control.swipe.leftItem.width, control.height / 2, Qt.LeftButton); + } + + // TODO: this somehow results in the behind item having a negative width +// Component { +// id: behindSwipeDelegateComponent +// SwipeDelegate { +// anchors.centerIn: parent +// swipe.behind: Rectangle { +// onXChanged: print("x changed", x) +// anchors.left: { +// print("anchors.left expression", swipe.position) +// swipe.position < 0 ? parent.background.right : undefined +// } +// anchors.right: { +// print("anchors.right expression", swipe.position) +// swipe.position > 0 ? parent.background.left : undefined +// } +// width: parent.width +// height: parent.height +// color: "green" +// } +// swipe.left: null +// swipe.right: null +// Rectangle { +// anchors.fill: parent +// color: "transparent" +// border.color: "darkorange" +// } +// } +// } + + Component { + id: behindSwipeDelegateComponent + SwipeDelegate { + text: "SwipeDelegate" + width: 150 + anchors.centerIn: parent + swipe.behind: Rectangle { + x: swipe.position < 0 ? parent.background.x + parent.background.width + : (swipe.position > 0 ? parent.background.x - width : 0) + width: parent.width + height: parent.height + color: "green" + } + swipe.left: null + swipe.right: null + } + } + + function test_leadingTrailingBehindItem() { + var control = createTemporaryObject(behindSwipeDelegateComponent, testCase); + verify(control); + + swipe(control, 0.0, 1.0); + verify(control.swipe.behindItem.visible); + compare(control.swipe.behindItem.x, control.background.x - control.background.width); + + swipe(control, 1.0, -1.0); + verify(control.swipe.behindItem.visible); + compare(control.swipe.behindItem.x, control.background.x + control.background.width); + + swipe(control, -1.0, 1.0); + verify(control.swipe.behindItem.visible); + compare(control.swipe.behindItem.x, control.background.x - control.background.width); + + // Should be possible to "wrap" with a behind delegate specified. + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton); + mouseMove(control, control.width / 2 + control.swipe.behindItem.width * 0.8, control.height / 2); + compare(control.swipe.position, -0.2); + mouseRelease(control, control.width / 2 + control.swipe.behindItem.width * 0.8, control.height / 2, Qt.LeftButton); + tryCompare(control.swipe, "position", 0.0); + + // Try wrapping the other way. + swipe(control, 0.0, -1.0); + verify(control.swipe.behindItem.visible); + compare(control.swipe.behindItem.x, control.background.x + control.background.width); + + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton); + mouseMove(control, control.width / 2 - control.swipe.behindItem.width * 0.8, control.height / 2); + compare(control.swipe.position, 0.2); + mouseRelease(control, control.width / 2 - control.swipe.behindItem.width * 0.8, control.height / 2, Qt.LeftButton); + tryCompare(control.swipe, "position", 0.0); + } + + Component { + id: closeSwipeDelegateComponent + + SwipeDelegate { + text: "SwipeDelegate" + width: 150 + + swipe.right: Rectangle { + color: "green" + width: parent.width + height: parent.height + + SwipeDelegate.onClicked: swipe.close() + } + } + } + + function test_close() { + var control = createTemporaryObject(closeSwipeDelegateComponent, testCase); + verify(control); + + var closedSpy = signalSpyComponent.createObject(control, { target: control.swipe, signalName: "closed" }); + verify(closedSpy); + verify(closedSpy.valid); + + swipe(control, 0.0, -1.0); + compare(control.swipe.rightItem.visible, true); + // Should animate, so it shouldn't change right away. + compare(control.swipe.rightItem.x, 0); + tryCompare(control.swipe.rightItem, "x", control.background.x + control.background.width); + + mousePress(control); + verify(control.swipe.rightItem.SwipeDelegate.pressed); + + mouseRelease(control); + verify(!control.swipe.rightItem.SwipeDelegate.pressed); + tryCompare(closedSpy, "count", 1); + compare(control.swipe.position, 0); + + // Swiping after closing should work as normal. + swipe(control, 0.0, -1.0); + } + + function test_callCloseWhenAlreadyClosed() { + let control = createTemporaryObject(swipeDelegateComponent, testCase) + verify(control) + + let closedSpy = signalSpyComponent.createObject(control, { target: control.swipe, signalName: "closed" }) + verify(closedSpy) + verify(closedSpy.valid) + + // Calling close() when it's already closed should have no effect. + control.swipe.close() + compare(closedSpy.count, 0) + + // The game goes for calling close() in response to a click. + control.clicked.connect(function() { control.swipe.close() }) + mouseClick(control) + compare(closedSpy.count, 0) + } + + // Can't just connect to pressed in QML, because there is a pressed property + // that conflicts with the signal. + Component { + id: swipeDelegateCloseOnPressedComponent + + SwipeDelegate { + text: "SwipeDelegate" + width: 150 + swipe.right: Rectangle { + objectName: "rightItem" + width: parent.width / 2 + height: parent.height + color: "tomato" + } + + onPressed: swipe.close() + } + } + + /* + We don't want to support closing on pressed(); released() or clicked() + should be used instead. However, calling swipe.close() in response to + a press should still not cause closed() to be emitted. + */ + function test_closeOnPressed() { + let control = createTemporaryObject(swipeDelegateCloseOnPressedComponent, testCase) + verify(control) + + swipe(control, 0.0, -1.0) + + let closedSpy = signalSpyComponent.createObject(control, { target: control.swipe, signalName: "closed" }) + verify(closedSpy) + verify(closedSpy.valid) + + mousePress(control, control.width * 0.1) + compare(closedSpy.count, 0) + compare(control.swipe.position, -1.0) + + // Simulate a somewhat realistic delay between press and release + // to ensure that the bug is triggered. + wait(100) + mouseRelease(control, control.width * 0.1) + compare(closedSpy.count, 0) + compare(control.swipe.position, -1.0) + } + + Component { + id: multiActionSwipeDelegateComponent + + SwipeDelegate { + text: "SwipeDelegate" + width: 150 + + swipe.right: Item { + objectName: "rightItemRoot" + width: parent.width + height: parent.height + + property alias firstAction: firstAction + property alias secondAction: secondAction + + property int firstClickCount: 0 + property int secondClickCount: 0 + + Row { + anchors.fill: parent + anchors.margins: 5 + + Rectangle { + id: firstAction + width: parent.width / 2 + height: parent.height + color: "tomato" + + SwipeDelegate.onClicked: ++firstClickCount + } + Rectangle { + id: secondAction + width: parent.width / 2 + height: parent.height + color: "navajowhite" + + SwipeDelegate.onClicked: ++secondClickCount + } + } + } + } + } + + // Tests that it's possible to have multiple non-interactive items in one delegate + // (e.g. left/right/behind) that can each receive clicks. + function test_multipleClickableActions() { + var control = createTemporaryObject(multiActionSwipeDelegateComponent, testCase); + verify(control); + + swipe(control, 0.0, -1.0); + verify(control.swipe.rightItem); + tryCompare(control.swipe, "complete", true); + + var firstClickedSpy = signalSpyComponent.createObject(control, + { target: control.swipe.rightItem.firstAction.SwipeDelegate, signalName: "clicked" }); + verify(firstClickedSpy); + verify(firstClickedSpy.valid); + + // Clicked within rightItem, but not within an item using the attached properties. + mousePress(control, 2, 2); + compare(control.swipe.rightItem.firstAction.SwipeDelegate.pressed, false); + compare(firstClickedSpy.count, 0); + + mouseRelease(control, 2, 2); + compare(control.swipe.rightItem.firstAction.SwipeDelegate.pressed, false); + compare(firstClickedSpy.count, 0); + + // Click within the first item. + mousePress(control.swipe.rightItem.firstAction, 0, 0); + compare(control.swipe.rightItem.firstAction.SwipeDelegate.pressed, true); + compare(firstClickedSpy.count, 0); + + mouseRelease(control.swipe.rightItem.firstAction, 0, 0); + compare(control.swipe.rightItem.firstAction.SwipeDelegate.pressed, false); + compare(firstClickedSpy.count, 1); + compare(control.swipe.rightItem.firstClickCount, 1); + + var secondClickedSpy = signalSpyComponent.createObject(control, + { target: control.swipe.rightItem.secondAction.SwipeDelegate, signalName: "clicked" }); + verify(secondClickedSpy); + verify(secondClickedSpy.valid); + + // Click within the second item. + mousePress(control.swipe.rightItem.secondAction, 0, 0); + compare(control.swipe.rightItem.secondAction.SwipeDelegate.pressed, true); + compare(secondClickedSpy.count, 0); + + mouseRelease(control.swipe.rightItem.secondAction, 0, 0); + compare(control.swipe.rightItem.secondAction.SwipeDelegate.pressed, false); + compare(secondClickedSpy.count, 1); + compare(control.swipe.rightItem.secondClickCount, 1); + } + + // Pressing on a "side action" and then dragging should eventually + // cause the ListView to grab the mouse and start changing its contentY. + // When this happens, it will grab the mouse and hence we must clear + // that action's pressed state so that it doesn't stay pressed after releasing. + function test_dragSideAction() { + let listView = createTemporaryObject(removableDelegatesComponent, testCase); + verify(listView); + + let control = listView.itemAt(0, 0); + verify(control); + + // Expose the side action. + swipe(control, 0.0, 1.0); + verify(control.swipe.leftItem); + tryCompare(control.swipe, "complete", true); + + let pressedSpy = signalSpyComponent.createObject(control, + { target: control.swipe.leftItem.SwipeDelegate, signalName: "pressedChanged" }); + verify(pressedSpy); + verify(pressedSpy.valid); + + let movingHorizontallySpy = createTemporaryObject(signalSpyComponent, testCase, + { target: listView, signalName: "movingHorizontallyChanged" }) + verify(movingHorizontallySpy) + verify(movingHorizontallySpy.valid) + + let movingVerticallySpy = createTemporaryObject(signalSpyComponent, testCase, + { target: listView, signalName: "movingVerticallyChanged" }) + verify(movingVerticallySpy) + verify(movingVerticallySpy.valid) + + let flickingHorizontallySpy = createTemporaryObject(signalSpyComponent, testCase, + { target: listView, signalName: "flickingHorizontallyChanged" }) + verify(flickingHorizontallySpy) + verify(flickingHorizontallySpy.valid) + + let flickingVerticallySpy = createTemporaryObject(signalSpyComponent, testCase, + { target: listView, signalName: "flickingVerticallyChanged" }) + verify(flickingVerticallySpy) + verify(flickingVerticallySpy.valid) + + // Drag the ListView vertically; its contentY should change. + mouseDrag(listView, 20, 20, 0, listView.height); + compare(pressedSpy.count, 2); + + // Wait for it to stop moving. + tryCompare(listView, "flickingVertically", false) + + // 2 because it should change to true then false. + compare(movingHorizontallySpy.count, 0) + compare(movingVerticallySpy.count, 2) + compare(flickingHorizontallySpy.count, 0) + compare(flickingVerticallySpy.count, 2) + compare(control.swipe.leftItem.SwipeDelegate.pressed, false); + } + + // When the width of a SwipeDelegate changes (as it does upon portrait => landscape + // rotation, for example), the positions of the contentItem and background items + // should be updated accordingly. + function test_contentItemPosOnWidthChanged() { + var control = createTemporaryObject(swipeDelegateComponent, testCase); + verify(control); + + swipe(control, 0.0, 1.0); + + var oldContentItemX = control.contentItem.x; + var oldBackgroundX = control.background.x; + control.width += 100; + compare(control.contentItem.x, oldContentItemX + 100); + compare(control.background.x, oldBackgroundX + 100); + } + + function test_contentItemHeightOnHeightChanged() { + var control = createTemporaryObject(swipeDelegateComponent, testCase); + verify(control); + + // Try when swipe.complete is false. + var originalHeight = control.height; + var originalContentItemHeight = control.contentItem.height; + verify(control.height !== 10); + control.height = 10; + compare(control.contentItem.height, control.availableHeight); + verify(control.contentItem.height < originalContentItemHeight); + compare(control.contentItem.y, control.topPadding); + + // Try when swipe.complete is true. + control.height = originalHeight; + swipe(control, 0.0, 1.0); + control.height = 10; + compare(control.contentItem.height, control.availableHeight); + verify(control.contentItem.height < originalContentItemHeight); + compare(control.contentItem.y, control.topPadding); + } + + function test_releaseOutside_data() { + return [ + { tag: "no delegates", component: emptySwipeDelegateComponent }, + { tag: "delegates", component: swipeDelegateComponent }, + ]; + } + + function test_releaseOutside(data) { + var control = createTemporaryObject(data.component, testCase); + verify(control); + + // Press and then release below the control. + mouseSignalSequenceSpy.target = control; + mouseSignalSequenceSpy.expectedSequence = [["pressedChanged", { "pressed": true }], "pressed", ["pressedChanged", { "pressed": false }]]; + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton); + mouseMove(control, control.width / 2, control.height + 10); + verify(mouseSignalSequenceSpy.success); + + mouseSignalSequenceSpy.expectedSequence = ["canceled"]; + mouseRelease(control, control.width / 2, control.height + 10, Qt.LeftButton); + verify(mouseSignalSequenceSpy.success); + + // Press and then release to the right of the control. + var hasDelegates = control.swipe.left || control.swipe.right || control.swipe.behind; + mouseSignalSequenceSpy.target = control; + mouseSignalSequenceSpy.expectedSequence = hasDelegates + ? [["pressedChanged", { "pressed": true }], "pressed"] + : [["pressedChanged", { "pressed": true }], "pressed", ["pressedChanged", { "pressed": false }]]; + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton); + mouseMove(control, control.width + 10, control.height / 2); + if (hasDelegates) + verify(control.swipe.position > 0); + verify(mouseSignalSequenceSpy.success); + + mouseSignalSequenceSpy.expectedSequence = hasDelegates ? [["pressedChanged", { "pressed": false }], "canceled"] : ["canceled"]; + mouseRelease(control, control.width + 10, control.height / 2, Qt.LeftButton); + verify(mouseSignalSequenceSpy.success); + } + + Component { + id: leftRightWithLabelsComponent + + SwipeDelegate { + id: delegate + text: "SwipeDelegate" + width: 150 + + background.opacity: 0.5 + + swipe.left: Rectangle { + width: parent.width + height: parent.height + color: SwipeDelegate.pressed ? Qt.darker("green") : "green" + + property alias label: label + + Label { + id: label + text: "Left" + color: "white" + anchors.margins: 10 + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + } + + SwipeDelegate.onClicked: delegate.swipe.close() + } + + swipe.right: Rectangle { + width: parent.width + height: parent.height + anchors.right: parent.right + color: SwipeDelegate.pressed ? Qt.darker("green") : "red" + + property alias label: label + + Label { + id: label + text: "Right" + color: "white" + anchors.margins: 10 + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + } + + SwipeDelegate.onClicked: delegate.swipe.close() + } + } + } + + function test_beginSwipeOverRightItem() { + var control = createTemporaryObject(leftRightWithLabelsComponent, testCase); + verify(control); + + // Swipe to the left, exposing the right item. + swipe(control, 0.0, -1.0); + + // Click to close it and go back to a position of 0. + mouseClick(control); + + // TODO: Swipe to the left, with the mouse over the Label in the right item. + // The left item should not become visible at any point. + var rightLabel = control.swipe.rightItem.label; + var overDragDistance = Math.round(dragDistance * 1.1); + mousePress(rightLabel, rightLabel.width / 2, rightLabel.height / 2, Qt.rightButton); + mouseMove(rightLabel, rightLabel.width / 2 - overDragDistance, rightLabel.height / 2); + verify(!control.swipe.leftItem); + + mouseRelease(rightLabel, rightLabel.width / 2 - overDragDistance, control.height / 2, Qt.LeftButton); + verify(!control.swipe.leftItem); + } + + Component { + id: swipeDelegateDisabledComponent + + SwipeDelegate { + id: swipeDelegate + text: "SwipeDelegate" + width: parent.width + height: checked ? implicitHeight * 2 : implicitHeight + checkable: true + + swipe.enabled: false + swipe.right: Label { + text: swipeDelegate.checked ? qsTr("Expanded") : qsTr("Collapsed") + width: parent.width + height: parent.height + padding: 12 + color: "white" + verticalAlignment: Label.AlignVCenter + horizontalAlignment: Label.AlignRight + } + } + } + + function test_swipeEnabled() { + var control = createTemporaryObject(swipeDelegateDisabledComponent, testCase); + + mousePress(control, control.width / 2, control.height / 2); + verify(control.pressed); + compare(control.swipe.position, 0.0); + verify(!control.swipe.complete); + verify(!control.swipe.leftItem); + verify(!control.swipe.rightItem); + + // It shouldn't be possible to swipe. + var overDragDistance = Math.round(dragDistance * 1.1); + mouseMove(control, control.width / 2 - overDragDistance, control.height / 2); + verify(control.pressed); + compare(control.swipe.position, 0.0); + verify(!control.swipe.complete); + verify(!control.swipe.leftItem); + verify(!control.swipe.rightItem); + + // Now move outside the right edge of the control and release. + mouseMove(control, control.width * 1.1, control.height / 2); + verify(control.pressed); + compare(control.swipe.position, 0.0); + verify(!control.swipe.complete); + verify(!control.swipe.leftItem); + verify(!control.swipe.rightItem); + + mouseRelease(control, control.width / 2, control.height / 2); + verify(!control.pressed); + compare(control.swipe.position, 0.0); + verify(!control.swipe.complete); + verify(!control.swipe.leftItem); + verify(!control.swipe.rightItem); + + // Now enabled swiping so that we can swipe to the left. + control.swipe.enabled = true; + swipe(control, 0, -1); + verify(control.swipe.complete); + + // Now that the swipe is complete, disable swiping and then try to swipe again. + // It should stay at its position of -1. + control.swipe.enabled = false; + + mousePress(control, control.width / 2, control.height / 2); + verify(control.pressed); + compare(control.swipe.position, -1.0); + + mouseMove(control, control.width / 2 + overDragDistance, control.height / 2); + verify(control.pressed); + compare(control.swipe.position, -1.0); + verify(control.swipe.complete); + + mouseRelease(control, control.width / 2 + overDragDistance, control.height / 2); + verify(!control.pressed); + compare(control.swipe.position, -1.0); + verify(control.swipe.complete); + } + + function test_side() { + compare(SwipeDelegate.Left, 1.0); + compare(SwipeDelegate.Right, -1.0); + } + + function test_open_side_data() { + return [ + { tag: "left", side: SwipeDelegate.Left, position: 1, complete: true, left: greenLeftComponent, right: null, behind: null }, + { tag: "right", side: SwipeDelegate.Right, position: -1, complete: true, left: null, right: redRightComponent, behind: null }, + { tag: "behind,left", side: SwipeDelegate.Left, position: 1, complete: true, left: null, right: null, behind: greenLeftComponent }, + { tag: "behind,right", side: SwipeDelegate.Right, position: -1, complete: true, left: null, right: null, behind: redRightComponent }, + { tag: "left,behind", side: SwipeDelegate.Left, position: 1, complete: true, left: null, right: null, behind: greenLeftComponent }, + { tag: "right,behind", side: SwipeDelegate.Right, position: -1, complete: true, left: null, right: null, behind: redRightComponent }, + { tag: "left,null", side: SwipeDelegate.Left, position: 0, complete: false, left: null, right: null, behind: null }, + { tag: "right,null", side: SwipeDelegate.Right, position: 0, complete: false, left: null, right: null, behind: null }, + { tag: "invalid", side: 0, position: 0, complete: false, left: greenLeftComponent, right: null, behind: null } + ] + } + + function test_open_side(data) { + var control = createTemporaryObject(emptySwipeDelegateComponent, testCase, + {"swipe.left": data.left, "swipe.right": data.right, "swipe.behind": data.behind}); + verify(control); + + control.swipe.open(data.side); + tryCompare(control.swipe, "position", data.position); + tryCompare(control.swipe, "complete", data.complete); + } + + Component { + id: openSwipeDelegateComponent + + SwipeDelegate { + text: "SwipeDelegate" + width: 150 + + onClicked: swipe.open(SwipeDelegate.Right) + + swipe.right: Item { + width: parent.width + height: parent.height + } + } + } + + function test_open() { + var control = createTemporaryObject(openSwipeDelegateComponent, testCase); + verify(control); + + mouseClick(control); + tryCompare(control.swipe, "position", SwipeDelegate.Right); + tryCompare(control.background, "x", -control.background.width); + + // Swiping after opening should work as normal. + swipe(control, SwipeDelegate.Right, 0.0); + tryCompare(control.swipe, "position", 0.0); + tryCompare(control.background, "x", 0); + } + + Component { + id: animationSwipeDelegateComponent + + SwipeDelegate { + id: control + text: "SwipeDelegate" + width: 150 + swipe.left: greenLeftComponent + swipe.right: redRightComponent + swipe.transition: null + + property alias behavior: xBehavior + property alias animation: numberAnimation + + background: Rectangle { + color: control.down ? "#ccc" : "#fff" + + Behavior on x { + id: xBehavior + enabled: !control.down + + NumberAnimation { + id: numberAnimation + easing.type: Easing.InOutCubic + duration: 400 + } + } + } + } + } + + function test_animations() { + // Test that animations are run when releasing from a drag. + var control = createTemporaryObject(animationSwipeDelegateComponent, testCase); + verify(control); + + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton); + mouseMove(control, control.width - 1, control.height / 2); + verify(control.down); + verify(!control.behavior.enabled); + verify(!control.animation.running); + + mouseRelease(control, control.width - 1, control.height / 2, Qt.LeftButton); + verify(!control.down); + verify(control.behavior.enabled); + verify(control.animation.running); + } + + function test_spacing() { + var control = createTemporaryObject(swipeDelegateComponent, testCase, { text: "Some long, long, long text" }) + verify(control) + verify(control.contentItem.implicitWidth + control.leftPadding + control.rightPadding > control.background.implicitWidth) + + var textLabel = findChild(control.contentItem, "label") + verify(textLabel) + + // The implicitWidth of the IconLabel that all buttons use as their contentItem + // should be equal to the implicitWidth of the Text while no icon is set. + compare(control.contentItem.implicitWidth, textLabel.implicitWidth) + + // That means that spacing shouldn't affect it. + control.spacing += 100 + compare(control.contentItem.implicitWidth, textLabel.implicitWidth) + + // The implicitWidth of the SwipeDelegate itself should, therefore, also never include spacing while no icon is set. + compare(control.implicitWidth, textLabel.implicitWidth + control.leftPadding + control.rightPadding) + } + + function test_display_data() { + return [ + { "tag": "IconOnly", display: SwipeDelegate.IconOnly }, + { "tag": "TextOnly", display: SwipeDelegate.TextOnly }, + { "tag": "TextUnderIcon", display: SwipeDelegate.TextUnderIcon }, + { "tag": "TextBesideIcon", display: SwipeDelegate.TextBesideIcon }, + { "tag": "IconOnly, mirrored", display: SwipeDelegate.IconOnly, mirrored: true }, + { "tag": "TextOnly, mirrored", display: SwipeDelegate.TextOnly, mirrored: true }, + { "tag": "TextUnderIcon, mirrored", display: SwipeDelegate.TextUnderIcon, mirrored: true }, + { "tag": "TextBesideIcon, mirrored", display: SwipeDelegate.TextBesideIcon, mirrored: true } + ] + } + + function test_display(data) { + var control = createTemporaryObject(swipeDelegateComponent, testCase, { + text: "SwipeDelegate", + display: data.display, + width: 400, + "icon.source": "qrc:/qt-project.org/imports/QtQuick/Controls/Basic/images/check.png", + "LayoutMirroring.enabled": !!data.mirrored + }) + verify(control) + compare(control.icon.source, "qrc:/qt-project.org/imports/QtQuick/Controls/Basic/images/check.png") + + var iconImage = findChild(control.contentItem, "image") + var textLabel = findChild(control.contentItem, "label") + + switch (control.display) { + case SwipeDelegate.IconOnly: + verify(iconImage) + verify(!textLabel) + compare(iconImage.x, (control.availableWidth - iconImage.width) / 2) + compare(iconImage.y, (control.availableHeight - iconImage.height) / 2) + break; + case SwipeDelegate.TextOnly: + verify(!iconImage) + verify(textLabel) + compare(textLabel.x, control.mirrored ? control.availableWidth - textLabel.width : 0) + compare(textLabel.y, (control.availableHeight - textLabel.height) / 2) + break; + case SwipeDelegate.TextUnderIcon: + verify(iconImage) + verify(textLabel) + compare(iconImage.x, (control.availableWidth - iconImage.width) / 2) + compare(textLabel.x, (control.availableWidth - textLabel.width) / 2) + verify(iconImage.y < textLabel.y) + break; + case SwipeDelegate.TextBesideIcon: + verify(iconImage) + verify(textLabel) + if (control.mirrored) + verify(textLabel.x < iconImage.x) + else + verify(iconImage.x < textLabel.x) + compare(iconImage.y, (control.availableHeight - iconImage.height) / 2) + compare(textLabel.y, (control.availableHeight - textLabel.height) / 2) + break; + } + } + + function test_resizeParent() { + let container = createTemporaryObject(itemComponent, testCase, { objectName: "container", width: 100, height: 200 }) + verify(container) + + let control = swipeDelegateComponent.createObject(container, { width: Qt.binding(function() { return container.width }) }) + verify(control) + + // Resize while closed. + container.width = 200 + compare(container.width, 200) + compare(control.width, 200) + compare(control.background.width, 200) + compare(control.contentItem.width, 200 - control.leftPadding - control.rightPadding) + + // Return to original size. + container.width = 100 + compare(control.width, 100) + compare(control.background.width, 100) + compare(control.contentItem.width, 100 - control.leftPadding - control.rightPadding) + + // Swipe to the left to open. + swipe(control, 0, -1.0) + // Nothing should have changed except positions. + compare(control.width, 100) + compare(control.background.width, 100) + compare(control.contentItem.width, 100 - control.leftPadding - control.rightPadding) + + // Resize while open. + container.width = 200 + // The items should fill the width as usual. + compare(control.width, 200) + compare(control.background.width, 200) + compare(control.contentItem.width, 200 - control.leftPadding - control.rightPadding) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_swipeview.qml b/tests/auto/quickcontrols2/controls/data/tst_swipeview.qml new file mode 100644 index 0000000000..68b91c3555 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_swipeview.qml @@ -0,0 +1,626 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 200 + height: 200 + visible: true + when: windowShown + name: "SwipeView" + + Component { + id: swipeView + SwipeView { } + } + + Component { + id: page + Text { } + } + + Component { + id: signalSpy + SignalSpy { } + } + + function test_current() { + var control = createTemporaryObject(swipeView, testCase) + + var currentItemChangedSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "currentItemChanged"}) + verify(currentItemChangedSpy.valid) + + compare(control.count, 0) + compare(control.currentIndex, -1) + compare(control.currentItem, null) + + var item0 = page.createObject(control, {text: "0"}) + control.addItem(item0) + compare(control.count, 1) + compare(control.currentIndex, 0) + compare(control.currentItem.text, "0") + compare(currentItemChangedSpy.count, 1); + compare(control.contentWidth, item0.implicitWidth) + compare(control.contentHeight, item0.implicitHeight) + + var item1 = page.createObject(control, {text: "11"}) + control.addItem(item1) + compare(control.count, 2) + compare(control.currentIndex, 0) + compare(control.currentItem.text, "0") + compare(currentItemChangedSpy.count, 1); + compare(control.contentWidth, item0.implicitWidth) + compare(control.contentHeight, item0.implicitHeight) + + var item2 = page.createObject(control, {text: "222"}) + control.addItem(item2) + compare(control.count, 3) + compare(control.currentIndex, 0) + compare(control.currentItem.text, "0") + compare(currentItemChangedSpy.count, 1); + compare(control.contentWidth, item0.implicitWidth) + compare(control.contentHeight, item0.implicitHeight) + + control.currentIndex = 1 + compare(control.currentIndex, 1) + compare(control.currentItem.text, "11") + compare(currentItemChangedSpy.count, 2); + compare(control.contentWidth, item1.implicitWidth) + compare(control.contentHeight, item1.implicitHeight) + + control.currentIndex = 2 + compare(control.currentIndex, 2) + compare(control.currentItem.text, "222") + compare(currentItemChangedSpy.count, 3); + compare(control.contentWidth, item2.implicitWidth) + compare(control.contentHeight, item2.implicitHeight) + + control.decrementCurrentIndex() + compare(control.currentIndex, 1) + compare(control.currentItem.text, "11") + compare(currentItemChangedSpy.count, 4); + compare(control.contentWidth, item1.implicitWidth) + compare(control.contentHeight, item1.implicitHeight) + + control.incrementCurrentIndex() + compare(control.currentIndex, 2) + compare(control.currentItem.text, "222") + compare(currentItemChangedSpy.count, 5); + compare(control.contentWidth, item2.implicitWidth) + compare(control.contentHeight, item2.implicitHeight) + } + + Component { + id: initialCurrentSwipeView + SwipeView { + currentIndex: 1 + + property alias item0: item0 + property alias item1: item1 + + Item { + id: item0 + } + Item { + id: item1 + } + } + } + + function test_initialCurrent() { + var control = createTemporaryObject(initialCurrentSwipeView, testCase) + + compare(control.count, 2) + compare(control.currentIndex, 1) + compare(control.currentItem, control.item1) + } + + function test_addRemove() { + var control = createTemporaryObject(swipeView, testCase) + + function verifyCurrentIndexCountDiff() { + verify(control.currentIndex < control.count) + } + control.currentIndexChanged.connect(verifyCurrentIndexCountDiff) + control.countChanged.connect(verifyCurrentIndexCountDiff) + + var currentItemChangedSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "currentItemChanged"}) + verify(currentItemChangedSpy.valid) + + compare(control.count, 0) + compare(control.currentIndex, -1) + compare(control.currentItem, null) + control.addItem(page.createObject(control, {text: "1"})) + compare(control.count, 1) + compare(control.currentIndex, 0) + compare(currentItemChangedSpy.count, 1) + compare(control.currentItem.text, "1") + control.addItem(page.createObject(control, {text: "2"})) + compare(control.count, 2) + compare(control.currentIndex, 0) + compare(currentItemChangedSpy.count, 1) + compare(control.currentItem.text, "1") + compare(control.itemAt(0).text, "1") + compare(control.itemAt(1).text, "2") + + control.currentIndex = 1 + compare(currentItemChangedSpy.count, 2) + + control.insertItem(1, page.createObject(control, {text: "3"})) + compare(control.count, 3) + compare(control.currentIndex, 2) + compare(control.currentItem.text, "2") + compare(control.itemAt(0).text, "1") + compare(control.itemAt(1).text, "3") + compare(control.itemAt(2).text, "2") + + control.insertItem(0, page.createObject(control, {text: "4"})) + compare(control.count, 4) + compare(control.currentIndex, 3) + compare(control.currentItem.text, "2") + compare(control.itemAt(0).text, "4") + compare(control.itemAt(1).text, "1") + compare(control.itemAt(2).text, "3") + compare(control.itemAt(3).text, "2") + + control.insertItem(control.count, page.createObject(control, {text: "5"})) + compare(control.count, 5) + compare(control.currentIndex, 3) + compare(control.currentItem.text, "2") + compare(control.itemAt(0).text, "4") + compare(control.itemAt(1).text, "1") + compare(control.itemAt(2).text, "3") + compare(control.itemAt(3).text, "2") + compare(control.itemAt(4).text, "5") + + control.removeItem(control.itemAt(control.count - 1)) + compare(control.count, 4) + compare(control.currentIndex, 3) + compare(control.currentItem.text, "2") + compare(control.itemAt(0).text, "4") + compare(control.itemAt(1).text, "1") + compare(control.itemAt(2).text, "3") + compare(control.itemAt(3).text, "2") + + control.removeItem(control.itemAt(0)) + compare(control.count, 3) + compare(control.currentIndex, 2) + compare(control.currentItem.text, "2") + compare(control.itemAt(0).text, "1") + compare(control.itemAt(1).text, "3") + compare(control.itemAt(2).text, "2") + + control.removeItem(control.itemAt(1)) + compare(control.count, 2) + compare(control.currentIndex, 1) + compare(control.currentItem.text, "2") + compare(control.itemAt(0).text, "1") + compare(control.itemAt(1).text, "2") + + currentItemChangedSpy.clear() + + control.removeItem(control.itemAt(1)) + compare(control.count, 1) + compare(control.currentIndex, 0) + compare(currentItemChangedSpy.count, 1) + compare(control.currentItem.text, "1") + compare(control.itemAt(0).text, "1") + + control.removeItem(control.itemAt(0)) + compare(control.count, 0) + compare(control.currentIndex, -1) + compare(currentItemChangedSpy.count, 2) + } + + Component { + id: contentView + SwipeView { + QtObject { objectName: "object" } + Item { objectName: "page1" } + Timer { objectName: "timer" } + Item { objectName: "page2" } + Component { Item { } } + } + } + + function test_content() { + var control = createTemporaryObject(contentView, testCase) + + function compareObjectNames(content, names) { + if (content.length !== names.length) + return false + for (var i = 0; i < names.length; ++i) { + if (content[i].objectName !== names[i]) + return false + } + return true + } + + verify(compareObjectNames(control.contentData, ["object", "page1", "timer", "page2", ""])) + verify(compareObjectNames(control.contentChildren, ["page1", "page2"])) + + control.addItem(page.createObject(control, {objectName: "page3"})) + verify(compareObjectNames(control.contentData, ["object", "page1", "timer", "page2", "", "page3"])) + verify(compareObjectNames(control.contentChildren, ["page1", "page2", "page3"])) + + control.insertItem(0, page.createObject(control, {objectName: "page4"})) + verify(compareObjectNames(control.contentData, ["object", "page1", "timer", "page2", "", "page3", "page4"])) + verify(compareObjectNames(control.contentChildren, ["page4", "page1", "page2", "page3"])) + + control.moveItem(1, 2) + verify(compareObjectNames(control.contentData, ["object", "page1", "timer", "page2", "", "page3", "page4"])) + verify(compareObjectNames(control.contentChildren, ["page4", "page2", "page1", "page3"])) + + control.removeItem(control.itemAt(0)) + verify(compareObjectNames(control.contentData, ["object", "page1", "timer", "page2", "", "page3"])) + verify(compareObjectNames(control.contentChildren, ["page2", "page1", "page3"])) + } + + Component { + id: repeated + SwipeView { + property alias repeater: repeater + Repeater { + id: repeater + model: 5 + Item { property int idx: index } + } + } + } + + function test_repeater() { + var control = createTemporaryObject(repeated, testCase) + verify(control) + + var model = control.contentModel + verify(model) + + var repeater = control.repeater + verify(repeater) + + compare(repeater.count, 5) + compare(model.count, 5) + + for (var i = 0; i < 5; ++i) { + var item1 = control.itemAt(i) + verify(item1) + compare(item1.idx, i) + compare(model.get(i), item1) + compare(repeater.itemAt(i), item1) + } + + repeater.model = 3 + compare(repeater.count, 3) + compare(model.count, 3) + + for (var j = 0; j < 3; ++j) { + var item2 = control.itemAt(j) + verify(item2) + compare(item2.idx, j) + compare(model.get(j), item2) + compare(repeater.itemAt(j), item2) + } + } + + Component { + id: ordered + SwipeView { + id: oview + property alias repeater: repeater + Text { text: "static_1" } + Repeater { + id: repeater + model: 2 + Text { text: "repeated_" + (index + 2) } + } + Text { text: "static_4" } + Component.onCompleted: { + addItem(page.createObject(oview, {text: "dynamic_5"})) + addItem(page.createObject(oview.contentItem, {text: "dynamic_6"})) + insertItem(0, page.createObject(oview, {text: "dynamic_0"})) + } + } + } + + function test_order() { + var control = createTemporaryObject(ordered, testCase) + verify(control) + + compare(control.count, 7) + compare(control.itemAt(0).text, "dynamic_0") + compare(control.itemAt(1).text, "static_1") + compare(control.itemAt(2).text, "repeated_2") + compare(control.itemAt(3).text, "repeated_3") + compare(control.itemAt(4).text, "static_4") + compare(control.itemAt(5).text, "dynamic_5") + compare(control.itemAt(6).text, "dynamic_6") + } + + function test_move_data() { + return [ + {tag:"0->1 (0)", from: 0, to: 1, currentBefore: 0, currentAfter: 1}, + {tag:"0->1 (1)", from: 0, to: 1, currentBefore: 1, currentAfter: 0}, + {tag:"0->1 (2)", from: 0, to: 1, currentBefore: 2, currentAfter: 2}, + + {tag:"0->2 (0)", from: 0, to: 2, currentBefore: 0, currentAfter: 2}, + {tag:"0->2 (1)", from: 0, to: 2, currentBefore: 1, currentAfter: 0}, + {tag:"0->2 (2)", from: 0, to: 2, currentBefore: 2, currentAfter: 1}, + + {tag:"1->0 (0)", from: 1, to: 0, currentBefore: 0, currentAfter: 1}, + {tag:"1->0 (1)", from: 1, to: 0, currentBefore: 1, currentAfter: 0}, + {tag:"1->0 (2)", from: 1, to: 0, currentBefore: 2, currentAfter: 2}, + + {tag:"1->2 (0)", from: 1, to: 2, currentBefore: 0, currentAfter: 0}, + {tag:"1->2 (1)", from: 1, to: 2, currentBefore: 1, currentAfter: 2}, + {tag:"1->2 (2)", from: 1, to: 2, currentBefore: 2, currentAfter: 1}, + + {tag:"2->0 (0)", from: 2, to: 0, currentBefore: 0, currentAfter: 1}, + {tag:"2->0 (1)", from: 2, to: 0, currentBefore: 1, currentAfter: 2}, + {tag:"2->0 (2)", from: 2, to: 0, currentBefore: 2, currentAfter: 0}, + + {tag:"2->1 (0)", from: 2, to: 1, currentBefore: 0, currentAfter: 0}, + {tag:"2->1 (1)", from: 2, to: 1, currentBefore: 1, currentAfter: 2}, + {tag:"2->1 (2)", from: 2, to: 1, currentBefore: 2, currentAfter: 1}, + + {tag:"0->0", from: 0, to: 0, currentBefore: 0, currentAfter: 0}, + {tag:"-1->0", from: 0, to: 0, currentBefore: 1, currentAfter: 1}, + {tag:"0->-1", from: 0, to: 0, currentBefore: 2, currentAfter: 2}, + {tag:"1->10", from: 0, to: 0, currentBefore: 0, currentAfter: 0}, + {tag:"10->2", from: 0, to: 0, currentBefore: 1, currentAfter: 1}, + {tag:"10->-1", from: 0, to: 0, currentBefore: 2, currentAfter: 2} + ] + } + + Component { + id: pageAttached + + Text { + property int index: SwipeView.index + property SwipeView view: SwipeView.view + property bool isCurrentItem: SwipeView.isCurrentItem + property bool isNextItem: SwipeView.isNextItem + property bool isPreviousItem: SwipeView.isPreviousItem + } + } + + function test_move(data) { + var control = createTemporaryObject(swipeView, testCase) + + compare(control.count, 0) + var titles = ["1", "2", "3"] + + var i = 0; + for (i = 0; i < titles.length; ++i) { + var item = pageAttached.createObject(control, {text: titles[i]}) + control.addItem(item) + } + + compare(control.count, titles.length) + for (i = 0; i < control.count; ++i) { + compare(control.itemAt(i).text, titles[i]) + compare(control.itemAt(i).SwipeView.index, i) + compare(control.itemAt(i).SwipeView.isCurrentItem, i === 0) + compare(control.itemAt(i).SwipeView.isNextItem, i === 1) + compare(control.itemAt(i).SwipeView.isPreviousItem, false) + } + + control.currentIndex = data.currentBefore + for (i = 0; i < control.count; ++i) { + compare(control.itemAt(i).SwipeView.isCurrentItem, i === data.currentBefore) + compare(control.itemAt(i).SwipeView.isNextItem, i === data.currentBefore + 1) + compare(control.itemAt(i).SwipeView.isPreviousItem, i === data.currentBefore - 1) + } + + control.moveItem(data.from, data.to) + + compare(control.count, titles.length) + compare(control.currentIndex, data.currentAfter) + + var title = titles[data.from] + titles.splice(data.from, 1) + titles.splice(data.to, 0, title) + + compare(control.count, titles.length) + for (i = 0; i < control.count; ++i) { + compare(control.itemAt(i).text, titles[i]) + compare(control.itemAt(i).SwipeView.index, i); + compare(control.itemAt(i).SwipeView.isCurrentItem, i === data.currentAfter) + compare(control.itemAt(i).SwipeView.isNextItem, i === data.currentAfter + 1) + compare(control.itemAt(i).SwipeView.isPreviousItem, i === data.currentAfter - 1) + } + } + + Component { + id: dynamicView + SwipeView { + id: dview + Text { text: "static" } + Component.onCompleted: { + addItem(page.createObject(dview, {text: "added"})) + insertItem(0, page.createObject(dview, {text: "inserted"})) + page.createObject(dview, {text: "dynamic"}) + } + } + } + + function test_dynamic() { + var control = createTemporaryObject(dynamicView, testCase) + + // insertItem(), addItem(), createObject() and static page {} + compare(control.count, 4) + compare(control.itemAt(0).text, "inserted") + + var tab = page.createObject(control, {text: "dying"}) + compare(control.count, 5) + compare(control.itemAt(4).text, "dying") + + // TODO: fix crash in QQuickItemView +// tab.destroy() +// wait(0) +// compare(control.count, 4) + } + + function test_attachedParent() { + var control = createTemporaryObject(swipeView, testCase); + + var page = createTemporaryObject(pageAttached, testCase); + compare(page.view, null); + compare(page.index, -1); + compare(page.isCurrentItem, false); + compare(page.isNextItem, false); + compare(page.isPreviousItem, false); + page.destroy(); + + page = createTemporaryObject(pageAttached, null); + compare(page.view, null); + compare(page.index, -1); + compare(page.isCurrentItem, false); + compare(page.isNextItem, false); + compare(page.isPreviousItem, false); + + control.insertItem(0, page); + compare(control.count, 1); + compare(page.parent, control.contentItem.contentItem); + compare(page.view, control); + compare(page.index, 0); + compare(page.isCurrentItem, true); + compare(page.isNextItem, false); + compare(page.isPreviousItem, false); + + control.removeItem(control.itemAt(0)); + compare(control.count, 0); + compare(page.parent, null); + compare(page.view, null); + compare(page.index, -1); + compare(page.isCurrentItem, false); + compare(page.isNextItem, false); + compare(page.isPreviousItem, false); + } + + function test_orientation() { + var control = createTemporaryObject(swipeView, testCase, {width: 200, height: 200}) + verify(control) + + for (var i = 0; i < 3; ++i) + control.addItem(page.createObject(control, {text: i})) + + compare(control.orientation, Qt.Horizontal) + compare(control.horizontal, true) + compare(control.vertical, false) + + for (i = 0; i < control.count; ++i) { + control.currentIndex = i + compare(control.itemAt(i).y, 0) + } + + control.orientation = Qt.Vertical + compare(control.orientation, Qt.Vertical) + compare(control.horizontal, false) + compare(control.vertical, true) + + for (i = 0; i < control.count; ++i) { + control.currentIndex = i + compare(control.itemAt(i).x, 0) + } + } + + Component { + id: focusSwipeViewComponent + + SwipeView { + id: swipeView + anchors.fill: parent + focus: true + + property int pressCount + property int releaseCount + property int rectanglePressCount + property int rectangleReleaseCount + + Rectangle { + focus: true + + Keys.onPressed: ++swipeView.rectanglePressCount + Keys.onReleased: ++swipeView.rectangleReleaseCount + } + + Keys.onPressed: ++pressCount + Keys.onReleased: ++releaseCount + } + } + + function test_focus() { + if (Qt.styleHints.tabFocusBehavior !== Qt.TabFocusAllControls) + skip("This platform only allows tab focus for text controls") + + var control = createTemporaryObject(focusSwipeViewComponent, testCase) + verify(control) + compare(control.focus, true) + compare(control.contentItem.focus, true) + compare(control.itemAt(0).focus, true) + compare(control.itemAt(0).activeFocus, true) + + keyPress(Qt.Key_A) + compare(control.pressCount, 1) + compare(control.releaseCount, 0) + compare(control.rectanglePressCount, 1) + compare(control.rectangleReleaseCount, 0) + + keyRelease(Qt.Key_A) + compare(control.pressCount, 1) + compare(control.releaseCount, 1) + compare(control.rectanglePressCount, 1) + compare(control.rectangleReleaseCount, 1) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_switch.qml b/tests/auto/quickcontrols2/controls/data/tst_switch.qml new file mode 100644 index 0000000000..9f6bb002c4 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_switch.qml @@ -0,0 +1,620 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 200 + height: 200 + visible: true + when: windowShown + name: "Switch" + + Component { + id: swtch + Switch { } + } + + Component { + id: signalSequenceSpy + SignalSequenceSpy { + signals: ["pressed", "released", "canceled", "clicked", "toggled", "pressedChanged", "checkedChanged"] + } + } + + function test_text() { + var control = createTemporaryObject(swtch, testCase) + verify(control) + + compare(control.text, "") + control.text = "Switch" + compare(control.text, "Switch") + control.text = "" + compare(control.text, "") + } + + function test_checked() { + var control = createTemporaryObject(swtch, testCase) + verify(control) + + compare(control.checked, false) + + var spy = signalSequenceSpy.createObject(control, {target: control}) + spy.expectedSequence = [["checkedChanged", { "checked": true }]] + control.checked = true + compare(control.checked, true) + verify(spy.success) + + spy.expectedSequence = [["checkedChanged", { "checked": false }]] + control.checked = false + compare(control.checked, false) + verify(spy.success) + } + + function test_pressed_data() { + return [ + { tag: "indicator", x: 15 }, + { tag: "background", x: 5 } + ] + } + + function test_pressed(data) { + var control = createTemporaryObject(swtch, testCase, {padding: 10}) + verify(control) + + // stays pressed when dragged outside + compare(control.pressed, false) + mousePress(control, data.x, control.height / 2, Qt.LeftButton) + compare(control.pressed, true) + mouseMove(control, -1, control.height / 2) + compare(control.pressed, true) + mouseRelease(control, -1, control.height / 2, Qt.LeftButton) + compare(control.pressed, false) + } + + function test_mouse() { + var control = createTemporaryObject(swtch, testCase) + verify(control) + + // check + var spy = signalSequenceSpy.createObject(control, {target: control}) + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], + "pressed"] + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.pressed, true) + verify(spy.success) + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": false }], + ["checkedChanged", { "pressed": false, "checked": true }], + "toggled", + "released", + "clicked"] + mouseRelease(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.checked, true) + compare(control.pressed, false) + verify(spy.success) + + // uncheck + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], + "pressed"] + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.pressed, true) + verify(spy.success) + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": true }], + ["checkedChanged", { "pressed": false, "checked": false }], + "toggled", + "released", + "clicked"] + mouseRelease(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.checked, false) + compare(control.pressed, false) + verify(spy.success) + + // release on the right + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], + "pressed"] + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.pressed, true) + verify(spy.success) + mouseMove(control, control.width * 2, control.height / 2, 0) + compare(control.pressed, true) + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": false }], + ["checkedChanged", { "pressed": false, "checked": true }], + "toggled", + "released", + "clicked"] + mouseRelease(control, control.width * 2, control.height / 2, Qt.LeftButton) + compare(control.checked, true) + compare(control.pressed, false) + verify(spy.success) + + // release on the left + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], + "pressed"] + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.pressed, true) + verify(spy.success) + mouseMove(control, -control.width, control.height / 2, 0) + compare(control.pressed, true) + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": true }], + ["checkedChanged", { "pressed": false, "checked": false }], + "toggled", + "released", + "clicked"] + mouseRelease(control, -control.width, control.height / 2, Qt.LeftButton) + compare(control.checked, false) + compare(control.pressed, false) + verify(spy.success) + + // release in the middle + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], + "pressed"] + mousePress(control, 0, 0, Qt.LeftButton) + compare(control.pressed, true) + verify(spy.success) + mouseMove(control, control.width / 2, control.height / 2, 0, Qt.LeftButton) + compare(control.pressed, true) + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": false }], + "released", + "clicked"] + mouseRelease(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.checked, false) + compare(control.pressed, false) + tryCompare(control, "position", 0) // QTBUG-57944 + verify(spy.success) + + // right button + spy.expectedSequence = [] + mousePress(control, control.width / 2, control.height / 2, Qt.RightButton) + compare(control.pressed, false) + verify(spy.success) + mouseRelease(control, control.width / 2, control.height / 2, Qt.RightButton) + compare(control.checked, false) + compare(control.pressed, false) + verify(spy.success) + } + + function test_touch() { + var control = createTemporaryObject(swtch, testCase) + verify(control) + + var touch = touchEvent(control) + + // check + var spy = signalSequenceSpy.createObject(control, {target: control}) + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], + "pressed"] + touch.press(0, control, control.width / 2, control.height / 2).commit() + compare(control.pressed, true) + verify(spy.success) + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": false }], + ["checkedChanged", { "pressed": false, "checked": true }], + "toggled", + "released", + "clicked"] + touch.release(0, control, control.width / 2, control.height / 2).commit() + compare(control.checked, true) + compare(control.pressed, false) + verify(spy.success) + + // uncheck + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], + "pressed"] + // Don't want to double-click. + wait(Qt.styleHints.mouseDoubleClickInterval + 50) + touch.press(0, control, control.width / 2, control.height / 2).commit() + compare(control.pressed, true) + verify(spy.success) + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": true }], + ["checkedChanged", { "pressed": false, "checked": false }], + "toggled", + "released", + "clicked"] + touch.release(0, control, control.width / 2, control.height / 2).commit() + compare(control.checked, false) + compare(control.pressed, false) + verify(spy.success) + + // release on the right + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], + "pressed"] + wait(Qt.styleHints.mouseDoubleClickInterval + 50) + touch.press(0, control, control.width / 2, control.height / 2).commit() + compare(control.pressed, true) + verify(spy.success) + touch.move(0, control, control.width * 2, control.height / 2).commit() + compare(control.pressed, true) + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": false }], + ["checkedChanged", { "pressed": false, "checked": true }], + "toggled", + "released", + "clicked"] + touch.release(0, control, control.width * 2, control.height / 2).commit() + compare(control.checked, true) + compare(control.pressed, false) + verify(spy.success) + + // release on the left + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], + "pressed"] + wait(Qt.styleHints.mouseDoubleClickInterval + 50) + touch.press(0, control, control.width / 2, control.height / 2).commit() + compare(control.pressed, true) + verify(spy.success) + touch.move(0, control, -control.width, control.height / 2).commit() + compare(control.pressed, true) + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": true }], + ["checkedChanged", { "pressed": false, "checked": false }], + "toggled", + "released", + "clicked"] + touch.release(0, control, -control.width, control.height / 2).commit() + compare(control.checked, false) + compare(control.pressed, false) + verify(spy.success) + + // release in the middle + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], + "pressed"] + wait(Qt.styleHints.mouseDoubleClickInterval + 50) + touch.press(0, control, 0, 0).commit() + compare(control.pressed, true) + verify(spy.success) + touch.move(0, control, control.width / 2, control.height / 2).commit() + compare(control.pressed, true) + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": false }], + "released", + "clicked"] + touch.release(0, control, control.width / 2, control.height / 2).commit() + compare(control.checked, false) + compare(control.pressed, false) + tryCompare(control, "position", 0) // QTBUG-57944 + verify(spy.success) + } + + function test_mouseDrag() { + var control = createTemporaryObject(swtch, testCase, {leftPadding: 100, rightPadding: 100}) + verify(control) + + var spy = signalSequenceSpy.createObject(control, {target: control}) + compare(control.position, 0.0) + compare(control.checked, false) + compare(control.pressed, false) + + // press-drag-release inside the indicator + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], + "pressed"] + mousePress(control.indicator, 0) + compare(control.position, 0.0) + compare(control.checked, false) + compare(control.pressed, true) + verify(spy.success) + + mouseMove(control.indicator, control.width - 1) + compare(control.position, 1.0) + compare(control.checked, false) + compare(control.pressed, true) + + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": false }], + ["checkedChanged", { "pressed": false, "checked": true }], + "toggled", + "released", + "clicked"] + mouseRelease(control.indicator, control.indicator.width - 1) + compare(control.position, 1.0) + compare(control.checked, true) + compare(control.pressed, false) + verify(spy.success) + + // press-drag-release outside the indicator + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], + "pressed"] + mousePress(control, 0) + compare(control.position, 1.0) + compare(control.checked, true) + compare(control.pressed, true) + verify(spy.success) + + mouseMove(control, control.width - control.rightPadding) + compare(control.position, 1.0) + compare(control.checked, true) + compare(control.pressed, true) + + mouseMove(control, control.width / 2) + compare(control.position, 0.5) + compare(control.checked, true) + compare(control.pressed, true) + + mouseMove(control, control.leftPadding) + compare(control.position, 0.0) + compare(control.checked, true) + compare(control.pressed, true) + + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": true }], + ["checkedChanged", { "pressed": false, "checked": false }], + "toggled", + "released", + "clicked"] + mouseRelease(control, control.width - 1) + compare(control.position, 0.0) + compare(control.checked, false) + compare(control.pressed, false) + verify(spy.success) + + // press-drag-release from and to outside the indicator + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], + "pressed"] + mousePress(control, control.width - 1) + compare(control.position, 0.0) + compare(control.checked, false) + compare(control.pressed, true) + verify(spy.success) + + mouseMove(control, control.width - control.rightPadding) + compare(control.position, 0.0) + compare(control.checked, false) + compare(control.pressed, true) + + mouseMove(control, control.width / 2) + compare(control.position, 0.5) + compare(control.checked, false) + compare(control.pressed, true) + + mouseMove(control, control.width - control.rightPadding) + compare(control.position, 1.0) + compare(control.checked, false) + compare(control.pressed, true) + + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": false }], + ["checkedChanged", { "pressed": false, "checked": true }], + "toggled", + "released", + "clicked"] + mouseRelease(control, control.width - 1) + compare(control.position, 1.0) + compare(control.checked, true) + compare(control.pressed, false) + verify(spy.success) + } + + function test_touchDrag() { + var control = createTemporaryObject(swtch, testCase, {leftPadding: 100, rightPadding: 100}) + verify(control) + + var touch = touchEvent(control) + + var spy = signalSequenceSpy.createObject(control, {target: control}) + compare(control.position, 0.0) + compare(control.checked, false) + compare(control.pressed, false) + + // press-drag-release inside the indicator + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], + "pressed"] + touch.press(0, control.indicator, 0).commit() + compare(control.position, 0.0) + compare(control.checked, false) + compare(control.pressed, true) + verify(spy.success) + + touch.move(0, control.indicator, control.width).commit() + compare(control.position, 1.0) + compare(control.checked, false) + compare(control.pressed, true) + + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": false }], + ["checkedChanged", { "pressed": false, "checked": true }], + "toggled", + "released", + "clicked"] + touch.release(0, control.indicator, control.indicator.width).commit() + compare(control.position, 1.0) + compare(control.checked, true) + compare(control.pressed, false) + verify(spy.success) + + // press-drag-release outside the indicator + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], + "pressed"] + // Don't want to double-click. + wait(Qt.styleHints.mouseDoubleClickInterval + 50) + touch.press(0, control, 0).commit() + compare(control.position, 1.0) + compare(control.checked, true) + compare(control.pressed, true) + verify(spy.success) + + touch.move(0, control, control.width - control.rightPadding).commit() + compare(control.position, 1.0) + compare(control.checked, true) + compare(control.pressed, true) + + touch.move(0, control, control.width / 2).commit() + compare(control.position, 0.5) + compare(control.checked, true) + compare(control.pressed, true) + + touch.move(0, control, control.leftPadding).commit() + compare(control.position, 0.0) + compare(control.checked, true) + compare(control.pressed, true) + + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": true }], + ["checkedChanged", { "pressed": false, "checked": false }], + "toggled", + "released", + "clicked"] + touch.release(0, control, control.width).commit() + compare(control.position, 0.0) + compare(control.checked, false) + compare(control.pressed, false) + verify(spy.success) + + // press-drag-release from and to outside the indicator + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], + "pressed"] + wait(Qt.styleHints.mouseDoubleClickInterval + 50) + touch.press(0, control, control.width - 1).commit() + compare(control.position, 0.0) + compare(control.checked, false) + compare(control.pressed, true) + verify(spy.success) + + touch.move(0, control, control.width - control.rightPadding).commit() + compare(control.position, 0.0) + compare(control.checked, false) + compare(control.pressed, true) + + touch.move(0, control, control.width / 2).commit() + compare(control.position, 0.5) + compare(control.checked, false) + compare(control.pressed, true) + + touch.move(0, control, control.width - control.rightPadding).commit() + compare(control.position, 1.0) + compare(control.checked, false) + compare(control.pressed, true) + + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": false }], + ["checkedChanged", { "pressed": false, "checked": true }], + "toggled", + "released", + "clicked"] + touch.release(0, control, control.width).commit() + compare(control.position, 1.0) + compare(control.checked, true) + compare(control.pressed, false) + verify(spy.success) + } + + function test_keys() { + var control = createTemporaryObject(swtch, testCase) + verify(control) + + control.forceActiveFocus() + verify(control.activeFocus) + + // check + var spy = signalSequenceSpy.createObject(control, {target: control}) + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], + "pressed", + ["pressedChanged", { "pressed": false, "checked": false }], + ["checkedChanged", { "pressed": false, "checked": true }], + "toggled", + "released", + "clicked"] + keyClick(Qt.Key_Space) + compare(control.checked, true) + verify(spy.success) + + // uncheck + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], + "pressed", + ["pressedChanged", { "pressed": false, "checked": true }], + ["checkedChanged", { "pressed": false, "checked": false }], + "toggled", + "released", + "clicked"] + keyClick(Qt.Key_Space) + compare(control.checked, false) + verify(spy.success) + + // no change + spy.expectedSequence = [] + var keys = [Qt.Key_Enter, Qt.Key_Return, Qt.Key_Escape, Qt.Key_Tab] + for (var i = 0; i < keys.length; ++i) { + keyClick(keys[i]) + compare(control.checked, false) + verify(spy.success) + } + } + + Component { + id: twoSwitches + Item { + property Switch sw1: Switch { id: sw1 } + property Switch sw2: Switch { id: sw2; checked: sw1.checked; enabled: false } + } + } + + function test_binding() { + var container = createTemporaryObject(twoSwitches, testCase) + verify(container) + + compare(container.sw1.checked, false) + compare(container.sw2.checked, false) + + container.sw1.checked = true + compare(container.sw1.checked, true) + compare(container.sw2.checked, true) + + container.sw1.checked = false + compare(container.sw1.checked, false) + compare(container.sw2.checked, false) + } + + function test_baseline() { + var control = createTemporaryObject(swtch, testCase) + verify(control) + compare(control.baselineOffset, control.contentItem.y + control.contentItem.baselineOffset) + } + + function test_focus() { + var control = createTemporaryObject(swtch, testCase) + verify(control) + + verify(!control.activeFocus) + mouseClick(control.indicator) + // should not get activeFocus on mouseClick on macOS + compare(control.activeFocus, Qt.platform.os !== "osx" && Qt.platform.os !== "macos") + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_switchdelegate.qml b/tests/auto/quickcontrols2/controls/data/tst_switchdelegate.qml new file mode 100644 index 0000000000..98e4d147a4 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_switchdelegate.qml @@ -0,0 +1,615 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 200 + height: 200 + visible: true + when: windowShown + name: "SwitchDelegate" + + Component { + id: switchDelegate + SwitchDelegate {} + } + + Component { + id: signalSequenceSpy + SignalSequenceSpy { + signals: ["pressed", "released", "canceled", "clicked", "toggled", "pressedChanged", "checkedChanged"] + } + } + + // TODO: data-fy tst_checkbox (rename to tst_check?) so we don't duplicate its tests here? + + function test_defaults() { + var control = createTemporaryObject(switchDelegate, testCase); + verify(control); + verify(!control.checked); + } + + function test_checked() { + var control = createTemporaryObject(switchDelegate, testCase); + verify(control); + + mouseClick(control); + verify(control.checked); + + mouseClick(control); + verify(!control.checked); + } + + function test_baseline() { + var control = createTemporaryObject(switchDelegate, testCase); + verify(control); + compare(control.baselineOffset, control.contentItem.y + control.contentItem.baselineOffset); + } + + function test_pressed_data() { + return [ + { tag: "indicator", x: 15 }, + { tag: "background", x: 5 } + ] + } + + function test_pressed(data) { + var control = createTemporaryObject(switchDelegate, testCase, {padding: 10}) + verify(control) + + // stays pressed when dragged outside + compare(control.pressed, false) + mousePress(control, data.x, control.height / 2, Qt.LeftButton) + compare(control.pressed, true) + mouseMove(control, -1, control.height / 2) + compare(control.pressed, true) + mouseRelease(control, -1, control.height / 2, Qt.LeftButton) + compare(control.pressed, false) + } + + function test_mouse() { + var control = createTemporaryObject(switchDelegate, testCase) + verify(control) + + // check + var spy = signalSequenceSpy.createObject(control, {target: control}) + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], + "pressed"] + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.pressed, true) + verify(spy.success) + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": false }], + ["checkedChanged", { "pressed": false, "checked": true }], + "toggled", + "released", + "clicked"] + mouseRelease(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.checked, true) + compare(control.pressed, false) + verify(spy.success) + + // uncheck + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], + "pressed"] + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.pressed, true) + verify(spy.success) + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": true }], + ["checkedChanged", { "pressed": false, "checked": false }], + "toggled", + "released", + "clicked"] + mouseRelease(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.checked, false) + compare(control.pressed, false) + verify(spy.success) + + // release on the right + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], + "pressed"] + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.pressed, true) + verify(spy.success) + mouseMove(control, control.width * 2, control.height / 2, 0) + compare(control.pressed, true) + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": false }], + ["checkedChanged", { "pressed": false, "checked": true }], + "toggled", + "released", + "clicked"] + mouseRelease(control, control.width * 2, control.height / 2, Qt.LeftButton) + compare(control.checked, true) + compare(control.pressed, false) + verify(spy.success) + + // release on the left + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], + "pressed"] + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.pressed, true) + verify(spy.success) + mouseMove(control, -control.width, control.height / 2, 0) + compare(control.pressed, true) + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": true }], + ["checkedChanged", { "pressed": false, "checked": false }], + "toggled", + "released", + "clicked"] + mouseRelease(control, -control.width, control.height / 2, Qt.LeftButton) + compare(control.checked, false) + compare(control.pressed, false) + verify(spy.success) + + // release in the middle + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], + "pressed"] + mousePress(control, 0, 0, Qt.LeftButton) + compare(control.pressed, true) + verify(spy.success) + mouseMove(control, control.width / 2, control.height / 2, 0, Qt.LeftButton) + compare(control.pressed, true) + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": false }], + "released", + "clicked"] + mouseRelease(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(control.checked, false) + compare(control.pressed, false) + tryCompare(control, "position", 0) // QTBUG-57944 + verify(spy.success) + + // right button + spy.expectedSequence = [] + mousePress(control, control.width / 2, control.height / 2, Qt.RightButton) + compare(control.pressed, false) + verify(spy.success) + mouseRelease(control, control.width / 2, control.height / 2, Qt.RightButton) + compare(control.checked, false) + compare(control.pressed, false) + verify(spy.success) + } + + function test_touch() { + var control = createTemporaryObject(switchDelegate, testCase) + verify(control) + + var touch = touchEvent(control) + + // check + var spy = signalSequenceSpy.createObject(control, {target: control}) + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], + "pressed"] + touch.press(0, control, control.width / 2, control.height / 2).commit() + compare(control.pressed, true) + verify(spy.success) + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": false }], + ["checkedChanged", { "pressed": false, "checked": true }], + "toggled", + "released", + "clicked"] + touch.release(0, control, control.width / 2, control.height / 2).commit() + compare(control.checked, true) + compare(control.pressed, false) + verify(spy.success) + + // uncheck + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], + "pressed"] + // Don't want to double-click. + wait(Qt.styleHints.mouseDoubleClickInterval + 50) + touch.press(0, control, control.width / 2, control.height / 2).commit() + compare(control.pressed, true) + verify(spy.success) + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": true }], + ["checkedChanged", { "pressed": false, "checked": false }], + "toggled", + "released", + "clicked"] + touch.release(0, control, control.width / 2, control.height / 2).commit() + compare(control.checked, false) + compare(control.pressed, false) + verify(spy.success) + + // release on the right + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], + "pressed"] + wait(Qt.styleHints.mouseDoubleClickInterval + 50) + touch.press(0, control, control.width / 2, control.height / 2).commit() + compare(control.pressed, true) + verify(spy.success) + touch.move(0, control, control.width * 2, control.height / 2).commit() + compare(control.pressed, true) + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": false }], + ["checkedChanged", { "pressed": false, "checked": true }], + "toggled", + "released", + "clicked"] + touch.release(0, control, control.width * 2, control.height / 2).commit() + compare(control.checked, true) + compare(control.pressed, false) + verify(spy.success) + + // release on the left + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], + "pressed"] + wait(Qt.styleHints.mouseDoubleClickInterval + 50) + touch.press(0, control, control.width / 2, control.height / 2).commit() + compare(control.pressed, true) + verify(spy.success) + touch.move(0, control, -control.width, control.height / 2).commit() + compare(control.pressed, true) + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": true }], + ["checkedChanged", { "pressed": false, "checked": false }], + "toggled", + "released", + "clicked"] + touch.release(0, control, -control.width, control.height / 2).commit() + compare(control.checked, false) + compare(control.pressed, false) + verify(spy.success) + + // release in the middle + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], + "pressed"] + wait(Qt.styleHints.mouseDoubleClickInterval + 50) + touch.press(0, control, 0, 0).commit() + compare(control.pressed, true) + verify(spy.success) + touch.move(0, control, control.width / 2, control.height / 2).commit() + compare(control.pressed, true) + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": false }], + "released", + "clicked"] + touch.release(0, control, control.width / 2, control.height / 2).commit() + compare(control.checked, false) + compare(control.pressed, false) + tryCompare(control, "position", 0) // QTBUG-57944 + verify(spy.success) + } + + function test_mouseDrag() { + var control = createTemporaryObject(switchDelegate, testCase, {leftPadding: 100, rightPadding: 100}) + verify(control) + + var spy = signalSequenceSpy.createObject(control, {target: control}) + compare(control.position, 0.0) + compare(control.checked, false) + compare(control.pressed, false) + + // press-drag-release inside the indicator + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], + "pressed"] + mousePress(control.indicator, 0) + compare(control.position, 0.0) + compare(control.checked, false) + compare(control.pressed, true) + verify(spy.success) + + mouseMove(control.indicator, control.width) + compare(control.position, 1.0) + compare(control.checked, false) + compare(control.pressed, true) + + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": false }], + ["checkedChanged", { "pressed": false, "checked": true }], + "toggled", + "released", + "clicked"] + mouseRelease(control.indicator, control.indicator.width) + compare(control.position, 1.0) + compare(control.checked, true) + compare(control.pressed, false) + verify(spy.success) + + // press-drag-release outside the indicator + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], + "pressed"] + mousePress(control, 0) + compare(control.position, 1.0) + compare(control.checked, true) + compare(control.pressed, true) + verify(spy.success) + + mouseMove(control, control.width - control.rightPadding) + compare(control.position, 1.0) + compare(control.checked, true) + compare(control.pressed, true) + + mouseMove(control, control.width / 2) + compare(control.position, 0.5) + compare(control.checked, true) + compare(control.pressed, true) + + mouseMove(control, control.leftPadding) + compare(control.position, 0.0) + compare(control.checked, true) + compare(control.pressed, true) + + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": true }], + ["checkedChanged", { "pressed": false, "checked": false }], + "toggled", + "released", + "clicked"] + mouseRelease(control, control.width) + compare(control.position, 0.0) + compare(control.checked, false) + compare(control.pressed, false) + verify(spy.success) + + // press-drag-release from and to outside the indicator + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], + "pressed"] + mousePress(control, control.width - 1) + compare(control.position, 0.0) + compare(control.checked, false) + compare(control.pressed, true) + verify(spy.success) + + mouseMove(control, control.width - control.rightPadding) + compare(control.position, 0.0) + compare(control.checked, false) + compare(control.pressed, true) + + mouseMove(control, control.width / 2) + compare(control.position, 0.5) + compare(control.checked, false) + compare(control.pressed, true) + + mouseMove(control, control.width - control.rightPadding) + compare(control.position, 1.0) + compare(control.checked, false) + compare(control.pressed, true) + + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": false }], + ["checkedChanged", { "pressed": false, "checked": true }], + "toggled", + "released", + "clicked"] + mouseRelease(control, control.width) + compare(control.position, 1.0) + compare(control.checked, true) + compare(control.pressed, false) + verify(spy.success) + } + + function test_touchDrag() { + var control = createTemporaryObject(switchDelegate, testCase, {leftPadding: 100, rightPadding: 100}) + verify(control) + + var touch = touchEvent(control) + + var spy = signalSequenceSpy.createObject(control, {target: control}) + compare(control.position, 0.0) + compare(control.checked, false) + compare(control.pressed, false) + + // press-drag-release inside the indicator + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], + "pressed"] + touch.press(0, control.indicator, 0).commit() + compare(control.position, 0.0) + compare(control.checked, false) + compare(control.pressed, true) + verify(spy.success) + + touch.move(0, control.indicator, control.width).commit() + compare(control.position, 1.0) + compare(control.checked, false) + compare(control.pressed, true) + + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": false }], + ["checkedChanged", { "pressed": false, "checked": true }], + "toggled", + "released", + "clicked"] + touch.release(0, control.indicator, control.indicator.width).commit() + compare(control.position, 1.0) + compare(control.checked, true) + compare(control.pressed, false) + verify(spy.success) + + // press-drag-release outside the indicator + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": true }], + "pressed"] + // Don't want to double-click. + wait(Qt.styleHints.mouseDoubleClickInterval + 50) + touch.press(0, control, 0).commit() + compare(control.position, 1.0) + compare(control.checked, true) + compare(control.pressed, true) + verify(spy.success) + + touch.move(0, control, control.width - control.rightPadding).commit() + compare(control.position, 1.0) + compare(control.checked, true) + compare(control.pressed, true) + + touch.move(0, control, control.width / 2).commit() + compare(control.position, 0.5) + compare(control.checked, true) + compare(control.pressed, true) + + touch.move(0, control, control.leftPadding).commit() + compare(control.position, 0.0) + compare(control.checked, true) + compare(control.pressed, true) + + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": true }], + ["checkedChanged", { "pressed": false, "checked": false }], + "toggled", + "released", + "clicked"] + touch.release(0, control, control.width).commit() + compare(control.position, 0.0) + compare(control.checked, false) + compare(control.pressed, false) + verify(spy.success) + + // press-drag-release from and to outside the indicator + spy.expectedSequence = [["pressedChanged", { "pressed": true, "checked": false }], + "pressed"] + wait(Qt.styleHints.mouseDoubleClickInterval + 50) + touch.press(0, control, control.width - 1).commit() + compare(control.position, 0.0) + compare(control.checked, false) + compare(control.pressed, true) + verify(spy.success) + + touch.move(0, control, control.width - control.rightPadding).commit() + compare(control.position, 0.0) + compare(control.checked, false) + compare(control.pressed, true) + + touch.move(0, control, control.width / 2).commit() + compare(control.position, 0.5) + compare(control.checked, false) + compare(control.pressed, true) + + touch.move(0, control, control.width - control.rightPadding).commit() + compare(control.position, 1.0) + compare(control.checked, false) + compare(control.pressed, true) + + spy.expectedSequence = [["pressedChanged", { "pressed": false, "checked": false }], + ["checkedChanged", { "pressed": false, "checked": true }], + "toggled", + "released", + "clicked"] + touch.release(0, control, control.width).commit() + compare(control.position, 1.0) + compare(control.checked, true) + compare(control.pressed, false) + verify(spy.success) + } + + function test_spacing() { + var control = createTemporaryObject(switchDelegate, testCase, { text: "Some long, long, long text" }) + verify(control) + verify(control.contentItem.implicitWidth + control.leftPadding + control.rightPadding > control.background.implicitWidth) + + var textLabel = findChild(control.contentItem, "label") + verify(textLabel) + + // The implicitWidth of the IconLabel that all buttons use as their contentItem should be + // equal to the implicitWidth of the Text and the switch indicator + spacing while no icon is set. + compare(control.contentItem.implicitWidth, textLabel.implicitWidth + control.indicator.width + control.spacing) + + control.spacing += 100 + compare(control.contentItem.implicitWidth, textLabel.implicitWidth + control.indicator.width + control.spacing) + + compare(control.implicitWidth, textLabel.implicitWidth + control.indicator.width + control.spacing + control.leftPadding + control.rightPadding) + } + + function test_display_data() { + return [ + { "tag": "IconOnly", display: SwitchDelegate.IconOnly }, + { "tag": "TextOnly", display: SwitchDelegate.TextOnly }, + { "tag": "TextUnderIcon", display: SwitchDelegate.TextUnderIcon }, + { "tag": "TextBesideIcon", display: SwitchDelegate.TextBesideIcon }, + { "tag": "IconOnly, mirrored", display: SwitchDelegate.IconOnly, mirrored: true }, + { "tag": "TextOnly, mirrored", display: SwitchDelegate.TextOnly, mirrored: true }, + { "tag": "TextUnderIcon, mirrored", display: SwitchDelegate.TextUnderIcon, mirrored: true }, + { "tag": "TextBesideIcon, mirrored", display: SwitchDelegate.TextBesideIcon, mirrored: true } + ] + } + + function test_display(data) { + var control = createTemporaryObject(switchDelegate, testCase, { + text: "SwitchDelegate", + display: data.display, + width: 400, + "icon.source": "qrc:/qt-project.org/imports/QtQuick/Controls/Basic/images/check.png", + "LayoutMirroring.enabled": !!data.mirrored + }) + verify(control) + compare(control.icon.source, "qrc:/qt-project.org/imports/QtQuick/Controls/Basic/images/check.png") + + var iconImage = findChild(control.contentItem, "image") + var textLabel = findChild(control.contentItem, "label") + + var availableWidth = control.availableWidth - control.indicator.width - control.spacing + var indicatorOffset = control.mirrored ? control.indicator.width + control.spacing : 0 + + switch (control.display) { + case SwitchDelegate.IconOnly: + verify(iconImage) + verify(!textLabel) + compare(iconImage.x, indicatorOffset + (availableWidth - iconImage.width) / 2) + compare(iconImage.y, (control.availableHeight - iconImage.height) / 2) + break; + case SwitchDelegate.TextOnly: + verify(!iconImage) + verify(textLabel) + compare(textLabel.x, control.mirrored ? control.availableWidth - textLabel.width : 0) + compare(textLabel.y, (control.availableHeight - textLabel.height) / 2) + break; + case SwitchDelegate.TextUnderIcon: + verify(iconImage) + verify(textLabel) + compare(iconImage.x, indicatorOffset + (availableWidth - iconImage.width) / 2) + compare(textLabel.x, indicatorOffset + (availableWidth - textLabel.width) / 2) + verify(iconImage.y < textLabel.y) + break; + case SwitchDelegate.TextBesideIcon: + verify(iconImage) + verify(textLabel) + if (control.mirrored) + verify(textLabel.x < iconImage.x) + else + verify(iconImage.x < textLabel.x) + compare(iconImage.y, (control.availableHeight - iconImage.height) / 2) + compare(textLabel.y, (control.availableHeight - textLabel.height) / 2) + break; + } + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_tabbar.qml b/tests/auto/quickcontrols2/controls/data/tst_tabbar.qml new file mode 100644 index 0000000000..3659a0abab --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_tabbar.qml @@ -0,0 +1,726 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 200 + height: 200 + visible: true + when: windowShown + name: "TabBar" + + Component { + id: tabButton + TabButton { } + } + + Component { + id: tabBar + TabBar { } + } + + Component { + id: tabBarStaticTabs + TabBar { + TabButton { + text: "0" + } + TabButton { + text: "1" + } + } + } + + Component { + id: tabBarStaticTabsCurrent + TabBar { + currentIndex: 1 + TabButton { + text: "0" + } + TabButton { + text: "1" + } + } + } + + Component { + id: signalSpy + SignalSpy { } + } + + function test_defaults() { + var control = createTemporaryObject(tabBar, testCase) + verify(control) + compare(control.count, 0) + compare(control.currentIndex, -1) + compare(control.currentItem, null) + } + + function test_current() { + var control = createTemporaryObject(tabBar, testCase) + + compare(control.count, 0) + compare(control.currentIndex, -1) + compare(control.currentItem, null) + + control.addItem(tabButton.createObject(control, {text: "0"})) + compare(control.count, 1) + compare(control.currentIndex, 0) + compare(control.currentItem.text, "0") + compare(control.currentItem.checked, true) + + control.addItem(tabButton.createObject(control, {text: "1"})) + compare(control.count, 2) + compare(control.currentIndex, 0) + compare(control.currentItem.text, "0") + compare(control.currentItem.checked, true) + + control.addItem(tabButton.createObject(control, {text: "2"})) + compare(control.count, 3) + compare(control.currentIndex, 0) + compare(control.currentItem.text, "0") + compare(control.currentItem.checked, true) + + control.currentIndex = 1 + compare(control.currentIndex, 1) + compare(control.currentItem.text, "1") + compare(control.currentItem.checked, true) + + control.currentIndex = 2 + compare(control.currentIndex, 2) + compare(control.currentItem.text, "2") + compare(control.currentItem.checked, true) + + control.decrementCurrentIndex() + compare(control.currentIndex, 1) + compare(control.currentItem.text, "1") + compare(control.currentItem.checked, true) + + control.incrementCurrentIndex() + compare(control.currentIndex, 2) + compare(control.currentItem.text, "2") + compare(control.currentItem.checked, true) + } + + function test_current_static() { + var control = createTemporaryObject(tabBarStaticTabs, testCase) + + compare(control.count, 2) + compare(control.currentIndex, 0) + compare(control.currentItem.text, "0") + compare(control.currentItem.checked, true) + + control = createTemporaryObject(tabBarStaticTabsCurrent, testCase) + + compare(control.count, 2) + compare(control.currentIndex, 1) + compare(control.currentItem.text, "1") + compare(control.currentItem.checked, true) + } + + function test_addRemove() { + var control = createTemporaryObject(tabBar, testCase) + + function verifyCurrentIndexCountDiff() { + verify(control.currentIndex < control.count) + } + control.currentIndexChanged.connect(verifyCurrentIndexCountDiff) + control.countChanged.connect(verifyCurrentIndexCountDiff) + + var contentChildrenSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "contentChildrenChanged"}) + verify(contentChildrenSpy.valid) + + compare(control.count, 0) + compare(control.currentIndex, -1) + control.addItem(tabButton.createObject(control, {text: "1"})) + compare(control.count, 1) + compare(control.currentIndex, 0) + compare(control.currentItem.text, "1") + compare(contentChildrenSpy.count, 1) + + control.addItem(tabButton.createObject(control, {text: "2"})) + compare(control.count, 2) + compare(control.currentIndex, 0) + compare(control.currentItem.text, "1") + compare(control.itemAt(0).text, "1") + compare(control.itemAt(1).text, "2") + compare(contentChildrenSpy.count, 2) + + control.currentIndex = 1 + + control.insertItem(1, tabButton.createObject(control, {text: "3"})) + compare(control.count, 3) + compare(control.currentIndex, 2) + compare(control.currentItem.text, "2") + compare(control.itemAt(0).text, "1") + compare(control.itemAt(1).text, "3") + compare(control.itemAt(2).text, "2") + compare(contentChildrenSpy.count, 4) // append + insert->move + + control.insertItem(0, tabButton.createObject(control, {text: "4"})) + compare(control.count, 4) + compare(control.currentIndex, 3) + compare(control.currentItem.text, "2") + compare(control.itemAt(0).text, "4") + compare(control.itemAt(1).text, "1") + compare(control.itemAt(2).text, "3") + compare(control.itemAt(3).text, "2") + compare(contentChildrenSpy.count, 6) // append + insert->move + + control.insertItem(control.count, tabButton.createObject(control, {text: "5"})) + compare(control.count, 5) + compare(control.currentIndex, 3) + compare(control.currentItem.text, "2") + compare(control.itemAt(0).text, "4") + compare(control.itemAt(1).text, "1") + compare(control.itemAt(2).text, "3") + compare(control.itemAt(3).text, "2") + compare(control.itemAt(4).text, "5") + compare(contentChildrenSpy.count, 7) + + control.removeItem(control.itemAt(control.count - 1)) + compare(control.count, 4) + compare(control.currentIndex, 3) + compare(control.currentItem.text, "2") + compare(control.itemAt(0).text, "4") + compare(control.itemAt(1).text, "1") + compare(control.itemAt(2).text, "3") + compare(control.itemAt(3).text, "2") + compare(contentChildrenSpy.count, 8) + + control.removeItem(control.itemAt(0)) + compare(control.count, 3) + compare(control.currentIndex, 2) + compare(control.currentItem.text, "2") + compare(control.itemAt(0).text, "1") + compare(control.itemAt(1).text, "3") + compare(control.itemAt(2).text, "2") + compare(contentChildrenSpy.count, 9) + + control.removeItem(control.itemAt(1)) + compare(control.count, 2) + compare(control.currentIndex, 1) + compare(control.currentItem.text, "2") + compare(control.itemAt(0).text, "1") + compare(control.itemAt(1).text, "2") + compare(contentChildrenSpy.count, 10) + + control.removeItem(control.itemAt(1)) + compare(control.count, 1) + compare(control.currentIndex, 0) + compare(control.currentItem.text, "1") + compare(control.itemAt(0).text, "1") + compare(contentChildrenSpy.count, 11) + + control.removeItem(control.itemAt(0)) + compare(control.count, 0) + compare(control.currentIndex, -1) + compare(contentChildrenSpy.count, 12) + } + + function test_removeCurrent() { + var control = createTemporaryObject(tabBar, testCase) + + control.addItem(tabButton.createObject(control, {text: "1"})) + control.addItem(tabButton.createObject(control, {text: "2"})) + control.addItem(tabButton.createObject(control, {text: "3"})) + control.currentIndex = 1 + compare(control.count, 3) + compare(control.currentIndex, 1) + + control.removeItem(control.itemAt(1)) + compare(control.count, 2) + compare(control.currentIndex, 0) + + control.removeItem(control.itemAt(0)) + compare(control.count, 1) + compare(control.currentIndex, 0) + + control.removeItem(control.itemAt(0)) + compare(control.count, 0) + compare(control.currentIndex, -1) + } + + Component { + id: contentBar + TabBar { + QtObject { objectName: "object" } + TabButton { objectName: "button1" } + Timer { objectName: "timer" } + TabButton { objectName: "button2" } + Component { TabButton { } } + } + } + + function test_content() { + var control = createTemporaryObject(contentBar, testCase) + + function compareObjectNames(content, names) { + if (content.length !== names.length) + return false + for (var i = 0; i < names.length; ++i) { + if (content[i].objectName !== names[i]) + return false + } + return true + } + + var contentChildrenSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "contentChildrenChanged"}) + verify(contentChildrenSpy.valid) + + verify(compareObjectNames(control.contentData, ["object", "button1", "timer", "button2", ""])) + verify(compareObjectNames(control.contentChildren, ["button1", "button2"])) + + control.addItem(tabButton.createObject(control, {objectName: "button3"})) + verify(compareObjectNames(control.contentData, ["object", "button1", "timer", "button2", "", "button3"])) + verify(compareObjectNames(control.contentChildren, ["button1", "button2", "button3"])) + compare(contentChildrenSpy.count, 1) + + control.insertItem(0, tabButton.createObject(control, {objectName: "button4"})) + verify(compareObjectNames(control.contentData, ["object", "button1", "timer", "button2", "", "button3", "button4"])) + verify(compareObjectNames(control.contentChildren, ["button4", "button1", "button2", "button3"])) + compare(contentChildrenSpy.count, 3) // append + insert->move + + control.moveItem(1, 2) + verify(compareObjectNames(control.contentData, ["object", "button1", "timer", "button2", "", "button3", "button4"])) + verify(compareObjectNames(control.contentChildren, ["button4", "button2", "button1", "button3"])) + compare(contentChildrenSpy.count, 4) + + control.removeItem(control.itemAt(0)) + verify(compareObjectNames(control.contentData, ["object", "button1", "timer", "button2", "", "button3"])) + verify(compareObjectNames(control.contentChildren, ["button2", "button1", "button3"])) + compare(contentChildrenSpy.count, 5) + } + + Component { + id: repeated + TabBar { + property alias repeater: repeater + Repeater { + id: repeater + model: 5 + TabButton { property int idx: index } + } + } + } + + function test_repeater() { + var control = createTemporaryObject(repeated, testCase) + verify(control) + + var model = control.contentModel + verify(model) + + var repeater = control.repeater + verify(repeater) + + compare(repeater.count, 5) + compare(model.count, 5) + + for (var i = 0; i < 5; ++i) { + var item1 = control.itemAt(i) + verify(item1) + compare(item1.idx, i) + compare(model.get(i), item1) + compare(repeater.itemAt(i), item1) + } + + repeater.model = 3 + compare(repeater.count, 3) + compare(model.count, 3) + + for (var j = 0; j < 3; ++j) { + var item2 = control.itemAt(j) + verify(item2) + compare(item2.idx, j) + compare(model.get(j), item2) + compare(repeater.itemAt(j), item2) + } + } + + Component { + id: ordered + TabBar { + id: obar + property alias repeater: repeater + TabButton { text: "static_1" } + Repeater { + id: repeater + model: 2 + TabButton { text: "repeated_" + (index + 2) } + } + TabButton { text: "static_4" } + Component.onCompleted: { + addItem(tabButton.createObject(obar, {text: "dynamic_5"})) + addItem(tabButton.createObject(obar.contentItem, {text: "dynamic_6"})) + insertItem(0, tabButton.createObject(obar, {text: "dynamic_0"})) + } + } + } + + function test_order() { + var control = createTemporaryObject(ordered, testCase) + verify(control) + + compare(control.count, 7) + compare(control.itemAt(0).text, "dynamic_0") + compare(control.itemAt(1).text, "static_1") + compare(control.itemAt(2).text, "repeated_2") + compare(control.itemAt(3).text, "repeated_3") + compare(control.itemAt(4).text, "static_4") + compare(control.itemAt(5).text, "dynamic_5") + compare(control.itemAt(6).text, "dynamic_6") + } + + function test_move_data() { + return [ + {tag:"0->1 (0)", from: 0, to: 1, currentBefore: 0, currentAfter: 1}, + {tag:"0->1 (1)", from: 0, to: 1, currentBefore: 1, currentAfter: 0}, + {tag:"0->1 (2)", from: 0, to: 1, currentBefore: 2, currentAfter: 2}, + + {tag:"0->2 (0)", from: 0, to: 2, currentBefore: 0, currentAfter: 2}, + {tag:"0->2 (1)", from: 0, to: 2, currentBefore: 1, currentAfter: 0}, + {tag:"0->2 (2)", from: 0, to: 2, currentBefore: 2, currentAfter: 1}, + + {tag:"1->0 (0)", from: 1, to: 0, currentBefore: 0, currentAfter: 1}, + {tag:"1->0 (1)", from: 1, to: 0, currentBefore: 1, currentAfter: 0}, + {tag:"1->0 (2)", from: 1, to: 0, currentBefore: 2, currentAfter: 2}, + + {tag:"1->2 (0)", from: 1, to: 2, currentBefore: 0, currentAfter: 0}, + {tag:"1->2 (1)", from: 1, to: 2, currentBefore: 1, currentAfter: 2}, + {tag:"1->2 (2)", from: 1, to: 2, currentBefore: 2, currentAfter: 1}, + + {tag:"2->0 (0)", from: 2, to: 0, currentBefore: 0, currentAfter: 1}, + {tag:"2->0 (1)", from: 2, to: 0, currentBefore: 1, currentAfter: 2}, + {tag:"2->0 (2)", from: 2, to: 0, currentBefore: 2, currentAfter: 0}, + + {tag:"2->1 (0)", from: 2, to: 1, currentBefore: 0, currentAfter: 0}, + {tag:"2->1 (1)", from: 2, to: 1, currentBefore: 1, currentAfter: 2}, + {tag:"2->1 (2)", from: 2, to: 1, currentBefore: 2, currentAfter: 1}, + + {tag:"0->0", from: 0, to: 0, currentBefore: 0, currentAfter: 0}, + {tag:"-1->0", from: 0, to: 0, currentBefore: 1, currentAfter: 1}, + {tag:"0->-1", from: 0, to: 0, currentBefore: 2, currentAfter: 2}, + {tag:"1->10", from: 0, to: 0, currentBefore: 0, currentAfter: 0}, + {tag:"10->2", from: 0, to: 0, currentBefore: 1, currentAfter: 1}, + {tag:"10->-1", from: 0, to: 0, currentBefore: 2, currentAfter: 2} + ] + } + + function test_move(data) { + var control = createTemporaryObject(tabBar, testCase) + + compare(control.count, 0) + var titles = ["1", "2", "3"] + + var i = 0; + for (i = 0; i < titles.length; ++i) + control.addItem(tabButton.createObject(control, {text: titles[i]})) + + compare(control.count, titles.length) + for (i = 0; i < control.count; ++i) + compare(control.itemAt(i).text, titles[i]) + + control.currentIndex = data.currentBefore + control.moveItem(data.from, data.to) + + compare(control.count, titles.length) + compare(control.currentIndex, data.currentAfter) + + var title = titles[data.from] + titles.splice(data.from, 1) + titles.splice(data.to, 0, title) + + compare(control.count, titles.length) + for (i = 0; i < control.count; ++i) + compare(control.itemAt(i).text, titles[i]) + } + + Component { + id: dynamicBar + TabBar { + id: dbar + TabButton { text: "static" } + Component.onCompleted: { + addItem(tabButton.createObject(dbar, {text: "added"})) + insertItem(0, tabButton.createObject(dbar, {text: "inserted"})) + tabButton.createObject(dbar, {text: "dynamic"}) + } + } + } + + function test_dynamic() { + var control = createTemporaryObject(dynamicBar, testCase) + + // insertItem(), addItem(), createObject() and static TabButton {} + compare(control.count, 4) + compare(control.itemAt(0).text, "inserted") + + var tab = tabButton.createObject(control, {text: "dying"}) + compare(control.count, 5) + compare(control.itemAt(4).text, "dying") + + // TODO: fix crash in QQuickItemView +// tab.destroy() +// wait(0) +// compare(control.count, 4) + } + + function test_layout_data() { + return [ + { tag: "spacing:0", spacing: 0 }, + { tag: "spacing:1", spacing: 1 }, + { tag: "spacing:10", spacing: 10 }, + ] + } + + function test_layout(data) { + var control = createTemporaryObject(tabBar, testCase, {spacing: data.spacing, width: 200}) + + // remove the background so that it won't affect the implicit size of the tabbar, + // so the implicit sizes tested below are entirely based on the content size + control.background = null + + var tab1 = tabButton.createObject(control, {text: "First"}) + control.addItem(tab1) + tryCompare(tab1, "width", control.width) + compare(tab1.height, control.height) + compare(control.implicitContentWidth, tab1.implicitWidth) + compare(control.implicitContentHeight, tab1.implicitHeight) + compare(control.contentWidth, control.implicitContentWidth) + compare(control.contentHeight, control.implicitContentHeight) + compare(control.implicitWidth, control.contentWidth + control.leftPadding + control.rightPadding) + compare(control.implicitHeight, control.contentHeight + control.topPadding + control.bottomPadding) + + var tab2 = tabButton.createObject(control, {implicitHeight: tab1.implicitHeight + 10, text: "Second"}) + control.addItem(tab2) + tryCompare(tab1, "width", (control.width - data.spacing) / 2) + compare(tab1.height, control.height) + compare(tab2.width, (control.width - data.spacing) / 2) + compare(tab2.height, control.height) + compare(control.implicitContentWidth, tab1.implicitWidth + tab2.implicitWidth + data.spacing) + compare(control.implicitContentHeight, tab2.implicitHeight) + compare(control.contentWidth, control.implicitContentWidth) + compare(control.contentHeight, control.implicitContentHeight) + compare(control.implicitWidth, control.contentWidth + control.leftPadding + control.rightPadding) + compare(control.implicitHeight, control.contentHeight + control.topPadding + control.bottomPadding) + + var tab3 = tabButton.createObject(control, {width: 50, height: tab1.implicitHeight - 10, text: "Third"}) + control.addItem(tab3) + tryCompare(tab1, "width", (control.width - 2 * data.spacing - 50) / 2) + compare(tab1.y, 0) + compare(tab1.height, control.height) + compare(tab2.y, 0) + compare(tab2.width, (control.width - 2 * data.spacing - 50) / 2) + compare(tab2.height, control.height) + verify(tab3.y > 0) + compare(tab3.y, (control.height - tab3.height) / 2) + compare(tab3.width, 50) + compare(tab3.height, tab1.implicitHeight - 10) + compare(control.implicitContentWidth, tab1.implicitWidth + tab2.implicitWidth + tab3.width + 2 * data.spacing) + compare(control.implicitContentHeight, tab2.implicitHeight) + compare(control.contentWidth, control.implicitContentWidth) + compare(control.contentHeight, control.implicitContentHeight) + compare(control.implicitWidth, control.contentWidth + control.leftPadding + control.rightPadding) + compare(control.implicitHeight, control.contentHeight + control.topPadding + control.bottomPadding) + + var expectedWidth = tab3.contentItem.implicitWidth + tab3.leftPadding + tab3.rightPadding + tab3.width = tab3.implicitWidth + tab3.height = tab3.implicitHeight + tryCompare(tab1, "width", (control.width - 2 * data.spacing - expectedWidth) / 2) + compare(tab1.height, control.height) + compare(tab2.width, (control.width - 2 * data.spacing - expectedWidth) / 2) + compare(tab2.height, control.height) + compare(tab3.width, expectedWidth) + compare(tab3.height, tab3.implicitHeight) + compare(control.implicitContentWidth, tab1.implicitWidth + tab2.implicitWidth + tab3.implicitWidth + 2 * data.spacing) + compare(control.implicitContentHeight, tab2.implicitHeight) + compare(control.contentWidth, control.implicitContentWidth) + compare(control.contentHeight, control.implicitContentHeight) + compare(control.implicitWidth, control.contentWidth + control.leftPadding + control.rightPadding) + compare(control.implicitHeight, control.contentHeight + control.topPadding + control.bottomPadding) + + tab3.width = undefined + tab3.height = undefined + control.width = undefined + + control.contentWidth = 300 + control.contentHeight = 50 + expectedWidth = (control.contentWidth - 2 * data.spacing) / 3 + tryCompare(tab1, "width", expectedWidth) + compare(tab2.width, expectedWidth) + compare(tab3.width, expectedWidth) + compare(tab1.height, control.contentHeight) + compare(tab2.height, control.contentHeight) + compare(tab3.height, control.contentHeight) + } + + Component { + id: attachedButton + TabButton { + property int index: TabBar.index + property TabBar tabBar: TabBar.tabBar + property int position: TabBar.position + } + } + + function test_attached() { + var control = createTemporaryObject(tabBar, testCase, {position: TabBar.Footer}) + + // append + var tab1 = createTemporaryObject(attachedButton, testCase) + compare(tab1.index, -1) + compare(tab1.tabBar, null) + compare(tab1.position, TabBar.Header) + + control.addItem(tab1) + compare(tab1.index, 0) + compare(tab1.tabBar, control) + compare(tab1.position, TabBar.Footer) + + // insert in the beginning + var tab2 = createTemporaryObject(attachedButton, testCase) + compare(tab2.index, -1) + compare(tab2.tabBar, null) + compare(tab2.position, TabBar.Header) + + control.insertItem(0, tab2) + compare(tab2.index, 0) + compare(tab2.tabBar, control) + compare(tab2.position, TabBar.Footer) + + compare(tab1.index, 1) + + // insert in the middle + var tab3 = createTemporaryObject(attachedButton, testCase) + compare(tab3.index, -1) + compare(tab3.tabBar, null) + compare(tab3.position, TabBar.Header) + + control.insertItem(1, tab3) + compare(tab3.index, 1) + compare(tab3.tabBar, control) + compare(tab3.position, TabBar.Footer) + + compare(tab2.index, 0) + compare(tab1.index, 2) + + // insert in the end + var tab4 = createTemporaryObject(attachedButton, testCase) + compare(tab4.index, -1) + compare(tab4.tabBar, null) + compare(tab4.position, TabBar.Header) + + control.insertItem(-1, tab4) + compare(tab4.index, 3) + compare(tab4.tabBar, control) + compare(tab4.position, TabBar.Footer) + + compare(tab2.index, 0) + compare(tab3.index, 1) + compare(tab1.index, 2) + + // move forwards + control.moveItem(0, 1) + compare(tab3.index, 0) + compare(tab2.index, 1) + compare(tab1.index, 2) + compare(tab4.index, 3) + + control.moveItem(0, 2) + compare(tab2.index, 0) + compare(tab1.index, 1) + compare(tab3.index, 2) + compare(tab4.index, 3) + + control.moveItem(1, 3) + compare(tab2.index, 0) + compare(tab3.index, 1) + compare(tab4.index, 2) + compare(tab1.index, 3) + + // move backwards + control.moveItem(3, 2) + compare(tab2.index, 0) + compare(tab3.index, 1) + compare(tab1.index, 2) + compare(tab4.index, 3) + + control.moveItem(3, 1) + compare(tab2.index, 0) + compare(tab4.index, 1) + compare(tab3.index, 2) + compare(tab1.index, 3) + + // remove from the beginning + control.removeItem(control.itemAt(0)) + compare(tab2.index, -1) + compare(tab2.tabBar, null) + compare(tab2.position, TabBar.Header) + + compare(tab4.index, 0) + compare(tab3.index, 1) + compare(tab1.index, 2) + + // remove from the middle + control.removeItem(control.itemAt(1)) + compare(tab3.index, -1) + compare(tab3.tabBar, null) + compare(tab3.position, TabBar.Header) + + compare(tab4.index, 0) + compare(tab1.index, 1) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_tabbutton.qml b/tests/auto/quickcontrols2/controls/data/tst_tabbutton.qml new file mode 100644 index 0000000000..3f8092099b --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_tabbutton.qml @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 200 + height: 200 + visible: true + when: windowShown + name: "TabButton" + + Component { + id: tabButton + TabButton { } + } + + Component { + id: repeater + Column { + Repeater { + model: 3 + delegate: TabButton { } + } + } + } + + function test_autoExclusive() { + var container = createTemporaryObject(repeater, testCase) + + for (var i = 0; i < 3; ++i) { + container.children[i].checked = true + compare(container.children[i].checked, true) + + // check that all other buttons are unchecked + for (var j = 0; j < 3; ++j) { + if (j !== i) + compare(container.children[j].checked, false) + } + } + } + + function test_baseline() { + var control = createTemporaryObject(tabButton, testCase) + verify(control) + compare(control.baselineOffset, control.contentItem.y + control.contentItem.baselineOffset) + } + + function test_spacing() { + var control = createTemporaryObject(tabButton, testCase, { text: "Some long, long, long text" }) + verify(control) + if (control.background) + verify(control.contentItem.implicitWidth + control.leftPadding + control.rightPadding > control.background.implicitWidth) + + var textLabel = findChild(control.contentItem, "label") + verify(textLabel) + + // The implicitWidth of the IconLabel that all buttons use as their contentItem + // should be equal to the implicitWidth of the Text while no icon is set. + compare(control.contentItem.implicitWidth, textLabel.implicitWidth) + + // That means that spacing shouldn't affect it. + control.spacing += 100 + compare(control.contentItem.implicitWidth, textLabel.implicitWidth) + + // The implicitWidth of the TabButton itself should, therefore, also never include spacing while no icon is set. + compare(control.implicitWidth, textLabel.implicitWidth + control.leftPadding + control.rightPadding) + } + + function test_display_data() { + return [ + { "tag": "IconOnly", display: TabButton.IconOnly }, + { "tag": "TextOnly", display: TabButton.TextOnly }, + { "tag": "TextUnderIcon", display: TabButton.TextUnderIcon }, + { "tag": "TextBesideIcon", display: TabButton.TextBesideIcon }, + { "tag": "IconOnly, mirrored", display: TabButton.IconOnly, mirrored: true }, + { "tag": "TextOnly, mirrored", display: TabButton.TextOnly, mirrored: true }, + { "tag": "TextUnderIcon, mirrored", display: TabButton.TextUnderIcon, mirrored: true }, + { "tag": "TextBesideIcon, mirrored", display: TabButton.TextBesideIcon, mirrored: true } + ] + } + + function test_display(data) { + var control = createTemporaryObject(tabButton, testCase, { + text: "TabButton", + display: data.display, + "icon.source": "qrc:/qt-project.org/imports/QtQuick/Controls/Basic/images/check.png", + "LayoutMirroring.enabled": !!data.mirrored + }) + verify(control) + compare(control.icon.source, "qrc:/qt-project.org/imports/QtQuick/Controls/Basic/images/check.png") + + var iconImage = findChild(control.contentItem, "image") + var textLabel = findChild(control.contentItem, "label") + + switch (control.display) { + case TabButton.IconOnly: + verify(iconImage) + verify(!textLabel) + compare(iconImage.x, (control.availableWidth - iconImage.width) / 2) + compare(iconImage.y, (control.availableHeight - iconImage.height) / 2) + break; + case TabButton.TextOnly: + verify(!iconImage) + verify(textLabel) + compare(textLabel.x, (control.availableWidth - textLabel.width) / 2) + compare(textLabel.y, (control.availableHeight - textLabel.height) / 2) + break; + case TabButton.TextUnderIcon: + verify(iconImage) + verify(textLabel) + compare(iconImage.x, (control.availableWidth - iconImage.width) / 2) + compare(textLabel.x, (control.availableWidth - textLabel.width) / 2) + verify(iconImage.y < textLabel.y) + break; + case TabButton.TextBesideIcon: + verify(iconImage) + verify(textLabel) + if (control.mirrored) + verify(textLabel.x < iconImage.x) + else + verify(iconImage.x < textLabel.x) + compare(iconImage.y, (control.availableHeight - iconImage.height) / 2) + compare(textLabel.y, (control.availableHeight - textLabel.height) / 2) + break; + } + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_textarea.qml b/tests/auto/quickcontrols2/controls/data/tst_textarea.qml new file mode 100644 index 0000000000..7f0b52fc10 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_textarea.qml @@ -0,0 +1,804 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "TextArea" + + Component { + id: textArea + TextArea { background: Item { } } + } + + Component { + id: flickable + Flickable { + width: 200 + height: 200 + TextArea.flickable: TextArea { } + } + } + + Component { + id: flickableCustomBackground + Flickable { + width: 200 + height: 200 + TextArea.flickable: TextArea { + background: Rectangle { + color: "green" + } + } + } + } + + Component { + id: signalSpy + SignalSpy { } + } + + Component { + id: rectangle + Rectangle { } + } + + FontMetrics { + id: defaultFontMetrics + } + + function test_creation() { + var control = createTemporaryObject(textArea, testCase) + verify(control) + } + + function test_implicitSize() { + var control = createTemporaryObject(textArea, testCase) + verify(control) + + var implicitWidthSpy = signalSpy.createObject(control, { target: control, signalName: "implicitWidthChanged"} ) + verify(implicitWidthSpy.valid) + + var implicitHeightSpy = signalSpy.createObject(control, { target: control, signalName: "implicitHeightChanged"} ) + verify(implicitHeightSpy.valid) + + var implicitBackgroundWidthSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "implicitBackgroundWidthChanged"}) + verify(implicitBackgroundWidthSpy.valid) + + var implicitBackgroundHeightSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "implicitBackgroundHeightChanged"}) + verify(implicitBackgroundHeightSpy.valid) + + var implicitWidthChanges = 0 + var implicitHeightChanges = 0 + var implicitBackgroundWidthChanges = 0 + var implicitBackgroundHeightChanges = 0 + + verify(control.implicitWidth >= control.leftPadding + control.rightPadding) + verify(control.implicitHeight >= control.contentHeight + control.topPadding + control.bottomPadding) + compare(control.implicitBackgroundWidth, 0) + compare(control.implicitBackgroundHeight, 0) + + control.background = rectangle.createObject(control, {implicitWidth: 400, implicitHeight: 200}) + compare(control.implicitWidth, 400) + compare(control.implicitHeight, 200) + compare(control.implicitBackgroundWidth, 400) + compare(control.implicitBackgroundHeight, 200) + compare(implicitWidthSpy.count, ++implicitWidthChanges) + compare(implicitHeightSpy.count, ++implicitHeightChanges) + compare(implicitBackgroundWidthSpy.count, ++implicitBackgroundWidthChanges) + compare(implicitBackgroundHeightSpy.count, ++implicitBackgroundHeightChanges) + + control.background = null + compare(control.implicitWidth, control.leftPadding + control.rightPadding) + verify(control.implicitHeight >= control.contentHeight + control.topPadding + control.bottomPadding) + compare(control.implicitBackgroundWidth, 0) + compare(control.implicitBackgroundHeight, 0) + compare(implicitWidthSpy.count, ++implicitWidthChanges) + compare(implicitHeightSpy.count, ++implicitHeightChanges) + compare(implicitBackgroundWidthSpy.count, ++implicitBackgroundWidthChanges) + compare(implicitBackgroundHeightSpy.count, ++implicitBackgroundHeightChanges) + + control.text = "TextArea" + compare(control.implicitWidth, control.contentWidth + control.leftPadding + control.rightPadding) + verify(control.implicitHeight >= control.contentHeight + control.topPadding + control.bottomPadding) + compare(control.implicitBackgroundWidth, 0) + compare(control.implicitBackgroundHeight, 0) + compare(implicitWidthSpy.count, ++implicitWidthChanges) + + defaultFontMetrics.font = control.font + var leading = defaultFontMetrics.leading + var ascent = defaultFontMetrics.ascent + var descent = defaultFontMetrics.descent + + var leadingOverflow = Math.ceil(ascent + descent) < Math.ceil(ascent + descent + leading) + + // If the font in use triggers QTBUG-83894, it is possible that this will cause + // the following compare to fail if the implicitHeight from the TextEdit is ued. + // Unfortunately, since some styles override implicitHeight, we cannot guarantee + // that it will fail, so we need to simply skip the test for these cases. + if (!leadingOverflow) + compare(implicitHeightSpy.count, implicitHeightChanges) + compare(implicitBackgroundWidthSpy.count, implicitBackgroundWidthChanges) + compare(implicitBackgroundHeightSpy.count, implicitBackgroundHeightChanges) + + control.placeholderText = "..." + compare(control.implicitWidth, control.contentWidth + control.leftPadding + control.rightPadding) + verify(control.implicitHeight >= control.contentHeight + control.topPadding + control.bottomPadding) + compare(control.implicitBackgroundWidth, 0) + compare(control.implicitBackgroundHeight, 0) + compare(implicitWidthSpy.count, implicitWidthChanges) + if (!leadingOverflow) + compare(implicitHeightSpy.count, implicitHeightChanges) + compare(implicitBackgroundWidthSpy.count, implicitBackgroundWidthChanges) + compare(implicitBackgroundHeightSpy.count, implicitBackgroundHeightChanges) + } + + function test_alignment_data() { + return [ + { tag: "empty", text: "", placeholderText: "", textAlignment: undefined, placeholderAlignment: Qt.AlignLeft }, + { tag: "empty,left", text: "", placeholderText: "", textAlignment: Qt.AlignLeft, placeholderAlignment: Qt.AlignLeft }, + { tag: "empty,center", text: "", placeholderText: "", textAlignment: Qt.AlignHCenter, placeholderAlignment: Qt.AlignHCenter }, + { tag: "empty,right", text: "", placeholderText: "", textAlignment: Qt.AlignRight, placeholderAlignment: Qt.AlignRight }, + + { tag: "empty,ltr", text: "", placeholderText: "Search", textAlignment: undefined, placeholderAlignment: Qt.AlignLeft }, + { tag: "empty,ltr,left", text: "", placeholderText: "Search", textAlignment: Qt.AlignLeft, placeholderAlignment: Qt.AlignLeft }, + { tag: "empty,ltr,center", text: "", placeholderText: "Search", textAlignment: Qt.AlignHCenter, placeholderAlignment: Qt.AlignHCenter }, + { tag: "empty,ltr,right", text: "", placeholderText: "Search", textAlignment: Qt.AlignRight, placeholderAlignment: Qt.AlignRight }, + + { tag: "empty,rtl", text: "", placeholderText: "بØØ«", textAlignment: undefined, placeholderAlignment: Qt.AlignRight }, + { tag: "empty,rtl,left", text: "", placeholderText: "بØØ«", textAlignment: Qt.AlignLeft, placeholderAlignment: Qt.AlignLeft }, + { tag: "empty,rtl,center", text: "", placeholderText: "بØØ«", textAlignment: Qt.AlignHCenter, placeholderAlignment: Qt.AlignHCenter }, + { tag: "empty,rtl,right", text: "", placeholderText: "بØØ«", textAlignment: Qt.AlignRight, placeholderAlignment: Qt.AlignRight }, + + { tag: "ltr,empty", text: "Text", placeholderText: "", textAlignment: undefined, placeholderAlignment: Qt.AlignLeft }, + { tag: "ltr,empty,left", text: "Text", placeholderText: "", textAlignment: Qt.AlignLeft, placeholderAlignment: Qt.AlignLeft }, + { tag: "ltr,empty,center", text: "Text", placeholderText: "", textAlignment: Qt.AlignHCenter, placeholderAlignment: Qt.AlignHCenter }, + { tag: "ltr,empty,right", text: "Text", placeholderText: "", textAlignment: Qt.AlignRight, placeholderAlignment: Qt.AlignRight }, + + { tag: "ltr,ltr", text: "Text", placeholderText: "Search", textAlignment: undefined, placeholderAlignment: Qt.AlignLeft }, + { tag: "ltr,ltr,left", text: "Text", placeholderText: "Search", textAlignment: Qt.AlignLeft, placeholderAlignment: Qt.AlignLeft }, + { tag: "ltr,ltr,center", text: "Text", placeholderText: "Search", textAlignment: Qt.AlignHCenter, placeholderAlignment: Qt.AlignHCenter }, + { tag: "ltr,ltr,right", text: "Text", placeholderText: "Search", textAlignment: Qt.AlignRight, placeholderAlignment: Qt.AlignRight }, + + { tag: "ltr,rtl", text: "Text", placeholderText: "بØØ«", textAlignment: undefined, placeholderAlignment: Qt.AlignRight }, + { tag: "ltr,rtl,left", text: "Text", placeholderText: "بØØ«", textAlignment: Qt.AlignLeft, placeholderAlignment: Qt.AlignLeft }, + { tag: "ltr,rtl,center", text: "Text", placeholderText: "بØØ«", textAlignment: Qt.AlignHCenter, placeholderAlignment: Qt.AlignHCenter }, + { tag: "ltr,rtl,right", text: "Text", placeholderText: "بØØ«", textAlignment: Qt.AlignRight, placeholderAlignment: Qt.AlignRight }, + + { tag: "rtl,empty", text: "نص", placeholderText: "", textAlignment: undefined, placeholderAlignment: Qt.AlignLeft }, + { tag: "rtl,empty,left", text: "نص", placeholderText: "", textAlignment: Qt.AlignLeft, placeholderAlignment: Qt.AlignLeft }, + { tag: "rtl,empty,center", text: "نص", placeholderText: "", textAlignment: Qt.AlignHCenter, placeholderAlignment: Qt.AlignHCenter }, + { tag: "rtl,empty,right", text: "نص", placeholderText: "", textAlignment: Qt.AlignRight, placeholderAlignment: Qt.AlignRight }, + + { tag: "rtl,ltr", text: "نص", placeholderText: "Search", textAlignment: undefined, placeholderAlignment: Qt.AlignLeft }, + { tag: "rtl,ltr,left", text: "نص", placeholderText: "Search", textAlignment: Qt.AlignLeft, placeholderAlignment: Qt.AlignLeft }, + { tag: "rtl,ltr,center", text: "نص", placeholderText: "Search", textAlignment: Qt.AlignHCenter, placeholderAlignment: Qt.AlignHCenter }, + { tag: "rtl,ltr,right", text: "نص", placeholderText: "Search", textAlignment: Qt.AlignRight, placeholderAlignment: Qt.AlignRight }, + + { tag: "rtl,rtl", text: "نص", placeholderText: "بØØ«", textAlignment: undefined, placeholderAlignment: Qt.AlignRight }, + { tag: "rtl,rtl,left", text: "نص", placeholderText: "بØØ«", textAlignment: Qt.AlignLeft, placeholderAlignment: Qt.AlignLeft }, + { tag: "rtl,rtl,center", text: "نص", placeholderText: "بØØ«", textAlignment: Qt.AlignHCenter, placeholderAlignment: Qt.AlignHCenter }, + { tag: "rtl,rtl,right", text: "نص", placeholderText: "بØØ«", textAlignment: Qt.AlignRight, placeholderAlignment: Qt.AlignRight }, + ] + } + + function test_alignment(data) { + var control = createTemporaryObject(textArea, testCase, {text: data.text, placeholderText: data.placeholderText, horizontalAlignment: data.textAlignment}) + + if (data.textAlignment !== undefined) + compare(control.horizontalAlignment, data.textAlignment) + for (var i = 0; i < control.children.length; ++i) { + if (control.children[i].hasOwnProperty("horizontalAlignment")) + compare(control.children[i].effectiveHorizontalAlignment, data.placeholderAlignment) // placeholder + } + + control.verticalAlignment = TextArea.AlignBottom + compare(control.verticalAlignment, TextArea.AlignBottom) + for (var j = 0; j < control.children.length; ++j) { + if (control.children[j].hasOwnProperty("verticalAlignment")) + compare(control.children[j].verticalAlignment, Text.AlignBottom) // placeholder + } + } + + function test_font_explicit_attributes_data() { + return [ + {tag: "bold", value: true}, + {tag: "capitalization", value: Font.Capitalize}, + {tag: "family", value: "Courier"}, + {tag: "italic", value: true}, + {tag: "strikeout", value: true}, + {tag: "underline", value: true}, + {tag: "weight", value: Font.Black}, + {tag: "wordSpacing", value: 55} + ] + } + + function test_font_explicit_attributes(data) { + var control = createTemporaryObject(textArea, testCase) + verify(control) + + var child = textArea.createObject(control) + verify(child) + + var controlSpy = signalSpy.createObject(control, {target: control, signalName: "fontChanged"}) + verify(controlSpy.valid) + + var childSpy = signalSpy.createObject(child, {target: child, signalName: "fontChanged"}) + verify(childSpy.valid) + + var defaultValue = control.font[data.tag] + child.font[data.tag] = defaultValue + + compare(child.font[data.tag], defaultValue) + compare(childSpy.count, 0) + + control.font[data.tag] = data.value + + compare(control.font[data.tag], data.value) + compare(controlSpy.count, 1) + + compare(child.font[data.tag], defaultValue) + compare(childSpy.count, 0) + } + + function test_flickable() { + var control = createTemporaryObject(flickable, testCase, {text:"line0", selectByMouse: true}) + verify(control) + + var textArea = control.TextArea.flickable + verify(textArea) + + if (textArea.background) + compare(textArea.background.parent, control) + + for (var i = 1; i <= 100; ++i) + textArea.text += "line\n" + i + + verify(textArea.contentWidth > 0) + verify(textArea.contentHeight > 200) + + compare(control.contentWidth, textArea.contentWidth + textArea.leftPadding + textArea.rightPadding) + compare(control.contentHeight, textArea.contentHeight + textArea.topPadding + textArea.bottomPadding) + + compare(textArea.cursorPosition, 0) + + var center = textArea.positionAt(control.width / 2, control.height / 2) + verify(center > 0) + mouseClick(textArea, control.width / 2, control.height / 2) + compare(textArea.cursorPosition, center) + + // click inside text area, but below flickable + var below = textArea.positionAt(control.width / 2, control.height + 1) + verify(below > center) + mouseClick(textArea, control.width / 2, control.height + 1) + compare(textArea.cursorPosition, center) // no change + + // scroll down + control.contentY = -(control.contentHeight - control.height) / 2 + + // click inside textarea, but above flickable + var above = textArea.positionAt(control.width / 2, textArea.topPadding) + verify(above > 0 && above < center) + mouseClick(textArea, control.width / 2, 0) + compare(textArea.cursorPosition, center) // no change + } + + function test_flickableCustomBackground() { + // Test that the TextArea background item is parented out of the + // TextArea and into the Flicable, and that it has the same size + // as the flickable. + var flickable = createTemporaryObject(flickableCustomBackground, testCase) + verify(flickable) + + var textArea = flickable.TextArea.flickable + verify(textArea) + verify(textArea.background) + compare(textArea.background.width, flickable.width) + compare(textArea.background.height, flickable.height) + } + + function test_warning() { + ignoreWarning(Qt.resolvedUrl("tst_textarea.qml") + ":55:1: QML TestCase: TextArea must be attached to a Flickable") + testCase.TextArea.flickable = null + } + + function test_hover_data() { + return [ + { tag: "enabled", hoverEnabled: true }, + { tag: "disabled", hoverEnabled: false }, + ] + } + + function test_hover(data) { + var control = createTemporaryObject(textArea, testCase, {text: "TextArea", hoverEnabled: data.hoverEnabled}) + verify(control) + + compare(control.hovered, false) + + mouseMove(control, control.width / 2, control.height / 2) + compare(control.hovered, data.hoverEnabled) + + mouseMove(control, -1, -1) + compare(control.hovered, false) + } + + function test_pressedReleased_data() { + return [ + { + tag: "pressed outside", x: -1, y: -1, button: Qt.LeftButton, + controlPressEvent: null, + controlReleaseEvent: null, + parentPressEvent: { + x: 0, y: 0, button: Qt.LeftButton, buttons: Qt.LeftButton, modifiers: Qt.NoModifier, wasHeld: false, isClick: false + }, + parentReleaseEvent: { + x: 0, y: 0, button: Qt.LeftButton, buttons: Qt.NoButton, modifiers: Qt.NoModifier, wasHeld: false, isClick: false + }, + }, + { + tag: "left click", x: 0, y: 0, button: Qt.LeftButton, + controlPressEvent: { + x: 0, y: 0, button: Qt.LeftButton, buttons: Qt.LeftButton, modifiers: Qt.NoModifier, wasHeld: false, isClick: false + }, + controlReleaseEvent: { + x: 0, y: 0, button: Qt.LeftButton, buttons: Qt.NoButton, modifiers: Qt.NoModifier, wasHeld: false, isClick: false + }, + parentPressEvent: null, + parentReleaseEvent: null, + }, + { + tag: "right click", x: 0, y: 0, button: Qt.RightButton, + controlPressEvent: { + x: 0, y: 0, button: Qt.RightButton, buttons: Qt.RightButton, modifiers: Qt.NoModifier, wasHeld: false, isClick: false + }, + controlReleaseEvent: { + x: 0, y: 0, button: Qt.RightButton, buttons: Qt.NoButton, modifiers: Qt.NoModifier, wasHeld: false, isClick: false + }, + parentPressEvent: null, + parentReleaseEvent: null, + }, + ]; + } + + Component { + id: mouseAreaComponent + MouseArea { + anchors.fill: parent + } + } + + function checkMouseEvent(event, expectedEvent) { + compare(event.x, expectedEvent.x) + compare(event.y, expectedEvent.y) + compare(event.button, expectedEvent.button) + compare(event.buttons, expectedEvent.buttons) + } + + function test_pressedReleased(data) { + var mouseArea = createTemporaryObject(mouseAreaComponent, testCase) + verify(mouseArea) + var control = textArea.createObject(mouseArea, {text: "TextArea"}) + verify(control) + + // Give enough room to check presses outside of the control and on the parent. + control.x = 1; + control.y = 1; + + function checkControlPressEvent(event) { + checkMouseEvent(event, data.controlPressEvent) + } + function checkControlReleaseEvent(event) { + checkMouseEvent(event, data.controlReleaseEvent) + } + function checkParentPressEvent(event) { + checkMouseEvent(event, data.parentPressEvent) + } + function checkParentReleaseEvent(event) { + checkMouseEvent(event, data.parentReleaseEvent) + } + + // Can't use signalArguments, because the event won't live that long. + if (data.controlPressEvent) + control.onPressed.connect(checkControlPressEvent) + if (data.controlReleaseEvent) + control.onReleased.connect(checkControlReleaseEvent) + if (data.parentPressEvent) + control.onPressed.connect(checkParentPressEvent) + if (data.parentReleaseEvent) + control.onReleased.connect(checkParentReleaseEvent) + + var controlPressedSpy = signalSpy.createObject(control, { target: control, signalName: "pressed" }) + verify(controlPressedSpy.valid) + var controlReleasedSpy = signalSpy.createObject(control, { target: control, signalName: "released" }) + verify(controlReleasedSpy.valid) + var parentPressedSpy = signalSpy.createObject(mouseArea, { target: mouseArea, signalName: "pressed" }) + verify(parentPressedSpy.valid) + var parentReleasedSpy = signalSpy.createObject(mouseArea, { target: mouseArea, signalName: "released" }) + verify(parentReleasedSpy.valid) + + mousePress(control, data.x, data.y, data.button) + compare(controlPressedSpy.count, data.controlPressEvent ? 1 : 0) + compare(parentPressedSpy.count, data.parentPressEvent ? 1 : 0) + mouseRelease(control, data.x, data.y, data.button) + compare(controlReleasedSpy.count, data.controlReleaseEvent ? 1 : 0) + compare(parentReleasedSpy.count, data.parentReleaseEvent ? 1 : 0) + } + + Component { + id: ignoreTextArea + + TextArea { + property bool ignorePress: false + property bool ignoreRelease: false + + onPressed: if (ignorePress) event.accepted = false + onReleased: if (ignoreRelease) event.accepted = false + } + } + + function checkEventAccepted(event) { + compare(event.accepted, true) + } + + function checkEventIgnored(event) { + compare(event.accepted, false) + } + + function test_ignorePressRelease() { + var mouseArea = createTemporaryObject(mouseAreaComponent, testCase) + verify(mouseArea) + var control = ignoreTextArea.createObject(mouseArea) + verify(control) + + var controlPressedSpy = signalSpy.createObject(control, { target: control, signalName: "pressed" }) + verify(controlPressedSpy.valid) + var controlReleasedSpy = signalSpy.createObject(control, { target: control, signalName: "released" }) + verify(controlReleasedSpy.valid) + var parentPressedSpy = signalSpy.createObject(mouseArea, { target: mouseArea, signalName: "pressed" }) + verify(parentPressedSpy.valid) + var parentReleasedSpy = signalSpy.createObject(mouseArea, { target: mouseArea, signalName: "released" }) + verify(parentReleasedSpy.valid) + + // Ignore only press events. + control.onPressed.connect(checkEventIgnored) + control.ignorePress = true + mousePress(control, 0, 0, data.button) + // The control will still get the signal, it just won't accept the event. + compare(controlPressedSpy.count, 1) + compare(parentPressedSpy.count, 1) + mouseRelease(control, 0, 0, data.button) + compare(controlReleasedSpy.count, 0) + compare(parentReleasedSpy.count, 1) + control.onPressed.disconnect(checkEventIgnored) + + // Ignore only release events. + control.onPressed.connect(checkEventAccepted) + control.onReleased.connect(checkEventIgnored) + control.ignorePress = false + control.ignoreRelease = true + mousePress(control, 0, 0, data.button) + compare(controlPressedSpy.count, 2) + compare(parentPressedSpy.count, 1) + mouseRelease(control, 0, 0, data.button) + compare(controlReleasedSpy.count, 1) + compare(parentReleasedSpy.count, 1) + control.onPressed.disconnect(checkEventAccepted) + control.onReleased.disconnect(checkEventIgnored) + } + + function test_multiClick() { + var control = createTemporaryObject(textArea, testCase, {text: "Qt Quick Controls 2 TextArea", selectByMouse: true}) + verify(control) + + waitForRendering(control) + control.width = control.contentWidth + var rect = control.positionToRectangle(12) + + // double click -> select word + mouseDoubleClickSequence(control, rect.x + rect.width / 2, rect.y + rect.height / 2) + compare(control.selectedText, "Controls") + + // tripple click -> select whole line + mouseClick(control, rect.x + rect.width / 2, rect.y + rect.height / 2) + compare(control.selectedText, "Qt Quick Controls 2 TextArea") + } + + Component { + id: scrollView + ScrollView { + TextArea { } + } + } + + function test_scrollView() { + var control = createTemporaryObject(scrollView, testCase) + verify(control) + + // don't crash (QTBUG-62292) + control.destroy() + wait(0) + } + + function test_placeholderTextColor() { + var control = createTemporaryObject(textArea, testCase) + verify(control) + + // usually default value should not be pure opacue black + verify(control.placeholderTextColor !== "#ff000000") + control.placeholderTextColor = "#12345678" + compare(control.placeholderTextColor, "#12345678") + + for (var i = 0; i < control.children.length; ++i) { + if (control.children[i].hasOwnProperty("text")) + compare(control.children[i].color, control.placeholderTextColor) // placeholder.color + } + } + + function test_inset() { + var control = createTemporaryObject(textArea, testCase, {background: rectangle.createObject(control)}) + verify(control) + + var topInsetSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "topInsetChanged"}) + verify(topInsetSpy.valid) + + var leftInsetSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "leftInsetChanged"}) + verify(leftInsetSpy.valid) + + var rightInsetSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "rightInsetChanged"}) + verify(rightInsetSpy.valid) + + var bottomInsetSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "bottomInsetChanged"}) + verify(bottomInsetSpy.valid) + + var topInsetChanges = 0 + var leftInsetChanges = 0 + var rightInsetChanges = 0 + var bottomInsetChanges = 0 + + compare(control.topInset, 0) + compare(control.leftInset, 0) + compare(control.rightInset, 0) + compare(control.bottomInset, 0) + + control.width = 100 + control.height = 100 + compare(control.background.x, 0) + compare(control.background.y, 0) + compare(control.background.width, 100) + compare(control.background.height, 100) + + control.topInset = 10 + compare(control.topInset, 10) + compare(control.leftInset, 0) + compare(control.rightInset, 0) + compare(control.bottomInset, 0) + compare(topInsetSpy.count, ++topInsetChanges) + compare(leftInsetSpy.count, leftInsetChanges) + compare(rightInsetSpy.count, rightInsetChanges) + compare(bottomInsetSpy.count, bottomInsetChanges) + compare(control.background.x, 0) + compare(control.background.y, 10) + compare(control.background.width, 100) + compare(control.background.height, 90) + + control.leftInset = 20 + compare(control.topInset, 10) + compare(control.leftInset, 20) + compare(control.rightInset, 0) + compare(control.bottomInset, 0) + compare(topInsetSpy.count, topInsetChanges) + compare(leftInsetSpy.count, ++leftInsetChanges) + compare(rightInsetSpy.count, rightInsetChanges) + compare(bottomInsetSpy.count, bottomInsetChanges) + compare(control.background.x, 20) + compare(control.background.y, 10) + compare(control.background.width, 80) + compare(control.background.height, 90) + + control.rightInset = 30 + compare(control.topInset, 10) + compare(control.leftInset, 20) + compare(control.rightInset, 30) + compare(control.bottomInset, 0) + compare(topInsetSpy.count, topInsetChanges) + compare(leftInsetSpy.count, leftInsetChanges) + compare(rightInsetSpy.count, ++rightInsetChanges) + compare(bottomInsetSpy.count, bottomInsetChanges) + compare(control.background.x, 20) + compare(control.background.y, 10) + compare(control.background.width, 50) + compare(control.background.height, 90) + + control.bottomInset = 40 + compare(control.topInset, 10) + compare(control.leftInset, 20) + compare(control.rightInset, 30) + compare(control.bottomInset, 40) + compare(topInsetSpy.count, topInsetChanges) + compare(leftInsetSpy.count, leftInsetChanges) + compare(rightInsetSpy.count, rightInsetChanges) + compare(bottomInsetSpy.count, ++bottomInsetChanges) + compare(control.background.x, 20) + compare(control.background.y, 10) + compare(control.background.width, 50) + compare(control.background.height, 50) + + control.topInset = undefined + compare(control.topInset, 0) + compare(control.leftInset, 20) + compare(control.rightInset, 30) + compare(control.bottomInset, 40) + compare(topInsetSpy.count, ++topInsetChanges) + compare(leftInsetSpy.count, leftInsetChanges) + compare(rightInsetSpy.count, rightInsetChanges) + compare(bottomInsetSpy.count, bottomInsetChanges) + compare(control.background.x, 20) + compare(control.background.y, 0) + compare(control.background.width, 50) + compare(control.background.height, 60) + + control.leftInset = undefined + compare(control.topInset, 0) + compare(control.leftInset, 0) + compare(control.rightInset, 30) + compare(control.bottomInset, 40) + compare(topInsetSpy.count, topInsetChanges) + compare(leftInsetSpy.count, ++leftInsetChanges) + compare(rightInsetSpy.count, rightInsetChanges) + compare(bottomInsetSpy.count, bottomInsetChanges) + compare(control.background.x, 0) + compare(control.background.y, 0) + compare(control.background.width, 70) + compare(control.background.height, 60) + + control.rightInset = undefined + compare(control.topInset, 0) + compare(control.leftInset, 0) + compare(control.rightInset, 0) + compare(control.bottomInset, 40) + compare(topInsetSpy.count, topInsetChanges) + compare(leftInsetSpy.count, leftInsetChanges) + compare(rightInsetSpy.count, ++rightInsetChanges) + compare(bottomInsetSpy.count, bottomInsetChanges) + compare(control.background.x, 0) + compare(control.background.y, 0) + compare(control.background.width, 100) + compare(control.background.height, 60) + + control.bottomInset = undefined + compare(control.topInset, 0) + compare(control.leftInset, 0) + compare(control.rightInset, 0) + compare(control.bottomInset, 0) + compare(topInsetSpy.count, topInsetChanges) + compare(leftInsetSpy.count, leftInsetChanges) + compare(rightInsetSpy.count, rightInsetChanges) + compare(bottomInsetSpy.count, ++bottomInsetChanges) + compare(control.background.x, 0) + compare(control.background.y, 0) + compare(control.background.width, 100) + compare(control.background.height, 100) + } + + // QTBUG-76369 + Component { + id: testResizeBackground + Item { + width: 200 + height: 200 + property alias textArea: textArea + ScrollView { + anchors.fill: parent + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + TextArea { + id: textArea + // workaround test failing due to default insets on Imagine + topInset: undefined + leftInset: undefined + rightInset: undefined + bottomInset: undefined + wrapMode : TextEdit.WordWrap + readOnly: false + selectByMouse: true + focus: true + text: "test message" + + background: Rectangle { + y: parent.height - height - textArea.bottomPadding / 2 + implicitWidth: 120 + height: textArea.activeFocus ? 2 : 1 + } + } + } + } + } + + function test_resize_background() { + var control = createTemporaryObject(testResizeBackground, testCase) + + compare(control.textArea.background.width, control.width) + compare(control.textArea.background.height, 1) + control.width = 400 + control.height = 400 + compare(control.textArea.background.width, control.width) + compare(control.textArea.background.height, 1) + control.width = 200 + control.height = 200 + compare(control.textArea.background.width, control.width) + compare(control.textArea.background.height, 1) + + // hasBackgroundWidth=true + control.textArea.background.width = 1 + compare(control.textArea.background.width, 1) + compare(control.textArea.background.height, 1) + control.width = 400 + control.height = 400 + compare(control.textArea.background.width, 1) + compare(control.textArea.background.height, 1) + // hasBackgroundHeight=false + control.textArea.background.height = undefined + compare(control.textArea.background.width, 1) + compare(control.textArea.background.height, 0) + control.textArea.background.y = 0 + compare(control.textArea.background.width, 1) + compare(control.textArea.background.height, control.height) + control.width = 200 + control.height = 200 + compare(control.textArea.background.width, 1) + compare(control.textArea.background.height, control.height) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_textfield.qml b/tests/auto/quickcontrols2/controls/data/tst_textfield.qml new file mode 100644 index 0000000000..f9b0587fa2 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_textfield.qml @@ -0,0 +1,663 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls +import QtQuick.Layouts + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "TextField" + + Component { + id: textField + TextField { } + } + + Component { + id: rectangle + Rectangle { } + } + + Component { + id: signalSpy + SignalSpy { } + } + + function test_creation() { + var control = createTemporaryObject(textField, testCase) + verify(control) + } + + function test_implicitSize() { + var control = createTemporaryObject(textField, testCase) + verify(control) + + var implicitWidthSpy = signalSpy.createObject(control, { target: control, signalName: "implicitWidthChanged"} ) + verify(implicitWidthSpy.valid) + + var implicitHeightSpy = signalSpy.createObject(control, { target: control, signalName: "implicitHeightChanged"} ) + verify(implicitHeightSpy.valid) + + var implicitBackgroundWidthSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "implicitBackgroundWidthChanged"}) + verify(implicitBackgroundWidthSpy.valid) + + var implicitBackgroundHeightSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "implicitBackgroundHeightChanged"}) + verify(implicitBackgroundHeightSpy.valid) + + var implicitWidthChanges = 0 + var implicitHeightChanges = 0 + var implicitBackgroundWidthChanges = 0 + var implicitBackgroundHeightChanges = 0 + + verify(control.implicitWidth >= control.leftPadding + control.rightPadding) + verify(control.implicitHeight >= control.contentHeight + control.topPadding + control.bottomPadding) + compare(control.implicitBackgroundWidth, control.background.implicitWidth) + compare(control.implicitBackgroundHeight, control.background.implicitHeight) + + control.background = rectangle.createObject(control, {implicitWidth: 400, implicitHeight: 200}) + compare(control.implicitWidth, 400) + compare(control.implicitHeight, 200) + compare(control.implicitBackgroundWidth, 400) + compare(control.implicitBackgroundHeight, 200) + compare(implicitWidthSpy.count, ++implicitWidthChanges) + compare(implicitHeightSpy.count, ++implicitHeightChanges) + compare(implicitBackgroundWidthSpy.count, ++implicitBackgroundWidthChanges) + compare(implicitBackgroundHeightSpy.count, ++implicitBackgroundHeightChanges) + + control.background = null + compare(control.implicitWidth, control.leftPadding + control.rightPadding) + compare(control.implicitHeight, control.contentHeight + control.topPadding + control.bottomPadding) + compare(control.implicitBackgroundWidth, 0) + compare(control.implicitBackgroundHeight, 0) + compare(implicitWidthSpy.count, ++implicitWidthChanges) + compare(implicitHeightSpy.count, ++implicitHeightChanges) + compare(implicitBackgroundWidthSpy.count, ++implicitBackgroundWidthChanges) + compare(implicitBackgroundHeightSpy.count, ++implicitBackgroundHeightChanges) + + control.text = "TextField" + compare(control.implicitWidth, control.contentWidth + control.leftPadding + control.rightPadding) + compare(control.implicitHeight, control.contentHeight + control.topPadding + control.bottomPadding) + compare(control.implicitBackgroundWidth, 0) + compare(control.implicitBackgroundHeight, 0) + compare(implicitWidthSpy.count, ++implicitWidthChanges) + compare(implicitHeightSpy.count, implicitHeightChanges) + compare(implicitBackgroundWidthSpy.count, implicitBackgroundWidthChanges) + compare(implicitBackgroundHeightSpy.count, implicitBackgroundHeightChanges) + + control.placeholderText = "..." + compare(control.implicitWidth, control.contentWidth + control.leftPadding + control.rightPadding) + compare(control.implicitHeight, control.contentHeight + control.topPadding + control.bottomPadding) + compare(control.implicitBackgroundWidth, 0) + compare(control.implicitBackgroundHeight, 0) + compare(implicitWidthSpy.count, implicitWidthChanges) + compare(implicitHeightSpy.count, implicitHeightChanges) + compare(implicitBackgroundWidthSpy.count, implicitBackgroundWidthChanges) + compare(implicitBackgroundHeightSpy.count, implicitBackgroundHeightChanges) + } + + function test_alignment_data() { + return [ + { tag: "empty", text: "", placeholderText: "", textAlignment: undefined, placeholderAlignment: Qt.AlignLeft }, + { tag: "empty,left", text: "", placeholderText: "", textAlignment: Qt.AlignLeft, placeholderAlignment: Qt.AlignLeft }, + { tag: "empty,center", text: "", placeholderText: "", textAlignment: Qt.AlignHCenter, placeholderAlignment: Qt.AlignHCenter }, + { tag: "empty,right", text: "", placeholderText: "", textAlignment: Qt.AlignRight, placeholderAlignment: Qt.AlignRight }, + + { tag: "empty,ltr", text: "", placeholderText: "Search", textAlignment: undefined, placeholderAlignment: Qt.AlignLeft }, + { tag: "empty,ltr,left", text: "", placeholderText: "Search", textAlignment: Qt.AlignLeft, placeholderAlignment: Qt.AlignLeft }, + { tag: "empty,ltr,center", text: "", placeholderText: "Search", textAlignment: Qt.AlignHCenter, placeholderAlignment: Qt.AlignHCenter }, + { tag: "empty,ltr,right", text: "", placeholderText: "Search", textAlignment: Qt.AlignRight, placeholderAlignment: Qt.AlignRight }, + + { tag: "empty,rtl", text: "", placeholderText: "بØØ«", textAlignment: undefined, placeholderAlignment: Qt.AlignRight }, + { tag: "empty,rtl,left", text: "", placeholderText: "بØØ«", textAlignment: Qt.AlignLeft, placeholderAlignment: Qt.AlignLeft }, + { tag: "empty,rtl,center", text: "", placeholderText: "بØØ«", textAlignment: Qt.AlignHCenter, placeholderAlignment: Qt.AlignHCenter }, + { tag: "empty,rtl,right", text: "", placeholderText: "بØØ«", textAlignment: Qt.AlignRight, placeholderAlignment: Qt.AlignRight }, + + { tag: "ltr,empty", text: "Text", placeholderText: "", textAlignment: undefined, placeholderAlignment: Qt.AlignLeft }, + { tag: "ltr,empty,left", text: "Text", placeholderText: "", textAlignment: Qt.AlignLeft, placeholderAlignment: Qt.AlignLeft }, + { tag: "ltr,empty,center", text: "Text", placeholderText: "", textAlignment: Qt.AlignHCenter, placeholderAlignment: Qt.AlignHCenter }, + { tag: "ltr,empty,right", text: "Text", placeholderText: "", textAlignment: Qt.AlignRight, placeholderAlignment: Qt.AlignRight }, + + { tag: "ltr,ltr", text: "Text", placeholderText: "Search", textAlignment: undefined, placeholderAlignment: Qt.AlignLeft }, + { tag: "ltr,ltr,left", text: "Text", placeholderText: "Search", textAlignment: Qt.AlignLeft, placeholderAlignment: Qt.AlignLeft }, + { tag: "ltr,ltr,center", text: "Text", placeholderText: "Search", textAlignment: Qt.AlignHCenter, placeholderAlignment: Qt.AlignHCenter }, + { tag: "ltr,ltr,right", text: "Text", placeholderText: "Search", textAlignment: Qt.AlignRight, placeholderAlignment: Qt.AlignRight }, + + { tag: "ltr,rtl", text: "Text", placeholderText: "بØØ«", textAlignment: undefined, placeholderAlignment: Qt.AlignRight }, + { tag: "ltr,rtl,left", text: "Text", placeholderText: "بØØ«", textAlignment: Qt.AlignLeft, placeholderAlignment: Qt.AlignLeft }, + { tag: "ltr,rtl,center", text: "Text", placeholderText: "بØØ«", textAlignment: Qt.AlignHCenter, placeholderAlignment: Qt.AlignHCenter }, + { tag: "ltr,rtl,right", text: "Text", placeholderText: "بØØ«", textAlignment: Qt.AlignRight, placeholderAlignment: Qt.AlignRight }, + + { tag: "rtl,empty", text: "نص", placeholderText: "", textAlignment: undefined, placeholderAlignment: Qt.AlignLeft }, + { tag: "rtl,empty,left", text: "نص", placeholderText: "", textAlignment: Qt.AlignLeft, placeholderAlignment: Qt.AlignLeft }, + { tag: "rtl,empty,center", text: "نص", placeholderText: "", textAlignment: Qt.AlignHCenter, placeholderAlignment: Qt.AlignHCenter }, + { tag: "rtl,empty,right", text: "نص", placeholderText: "", textAlignment: Qt.AlignRight, placeholderAlignment: Qt.AlignRight }, + + { tag: "rtl,ltr", text: "نص", placeholderText: "Search", textAlignment: undefined, placeholderAlignment: Qt.AlignLeft }, + { tag: "rtl,ltr,left", text: "نص", placeholderText: "Search", textAlignment: Qt.AlignLeft, placeholderAlignment: Qt.AlignLeft }, + { tag: "rtl,ltr,center", text: "نص", placeholderText: "Search", textAlignment: Qt.AlignHCenter, placeholderAlignment: Qt.AlignHCenter }, + { tag: "rtl,ltr,right", text: "نص", placeholderText: "Search", textAlignment: Qt.AlignRight, placeholderAlignment: Qt.AlignRight }, + + { tag: "rtl,rtl", text: "نص", placeholderText: "بØØ«", textAlignment: undefined, placeholderAlignment: Qt.AlignRight }, + { tag: "rtl,rtl,left", text: "نص", placeholderText: "بØØ«", textAlignment: Qt.AlignLeft, placeholderAlignment: Qt.AlignLeft }, + { tag: "rtl,rtl,center", text: "نص", placeholderText: "بØØ«", textAlignment: Qt.AlignHCenter, placeholderAlignment: Qt.AlignHCenter }, + { tag: "rtl,rtl,right", text: "نص", placeholderText: "بØØ«", textAlignment: Qt.AlignRight, placeholderAlignment: Qt.AlignRight }, + ] + } + + function test_alignment(data) { + var control = createTemporaryObject(textField, testCase, {text: data.text, placeholderText: data.placeholderText, horizontalAlignment: data.textAlignment}) + + if (data.textAlignment !== undefined) + compare(control.horizontalAlignment, data.textAlignment) + for (var i = 0; i < control.children.length; ++i) { + if (control.children[i].hasOwnProperty("text") && control.children[i].hasOwnProperty("horizontalAlignment")) + compare(control.children[i].effectiveHorizontalAlignment, data.placeholderAlignment) // placeholder + } + + control.verticalAlignment = TextField.AlignBottom + compare(control.verticalAlignment, TextField.AlignBottom) + for (var j = 0; j < control.children.length; ++j) { + if (control.children[j].hasOwnProperty("text") && control.children[j].hasOwnProperty("verticalAlignment")) + compare(control.children[j].verticalAlignment, Text.AlignBottom) // placeholder + } + } + + function test_font_explicit_attributes_data() { + return [ + {tag: "bold", value: true}, + {tag: "capitalization", value: Font.Capitalize}, + {tag: "family", value: "Courier"}, + {tag: "italic", value: true}, + {tag: "strikeout", value: true}, + {tag: "underline", value: true}, + {tag: "weight", value: Font.Black}, + {tag: "wordSpacing", value: 55} + ] + } + + function test_font_explicit_attributes(data) { + var control = createTemporaryObject(textField, testCase) + verify(control) + + var child = textField.createObject(control) + verify(child) + + var controlSpy = signalSpy.createObject(control, {target: control, signalName: "fontChanged"}) + verify(controlSpy.valid) + + var childSpy = signalSpy.createObject(child, {target: child, signalName: "fontChanged"}) + verify(childSpy.valid) + + var defaultValue = control.font[data.tag] + child.font[data.tag] = defaultValue + + compare(child.font[data.tag], defaultValue) + compare(childSpy.count, 0) + + control.font[data.tag] = data.value + + compare(control.font[data.tag], data.value) + compare(controlSpy.count, 1) + + compare(child.font[data.tag], defaultValue) + compare(childSpy.count, 0) + } + + function test_hover_data() { + return [ + { tag: "enabled", hoverEnabled: true }, + { tag: "disabled", hoverEnabled: false }, + ] + } + + function test_hover(data) { + var control = createTemporaryObject(textField, testCase, {hoverEnabled: data.hoverEnabled}) + verify(control) + + compare(control.hovered, false) + + mouseMove(control, control.width / 2, control.height / 2) + compare(control.hovered, data.hoverEnabled) + + mouseMove(control, -1, -1) + compare(control.hovered, false) + } + + function test_pressedReleased_data() { + return [ + { + tag: "pressed outside", x: -1, y: -1, button: Qt.LeftButton, + controlPressEvent: null, + controlReleaseEvent: null, + parentPressEvent: { + x: 0, y: 0, button: Qt.LeftButton, buttons: Qt.LeftButton, modifiers: Qt.NoModifier, wasHeld: false, isClick: false + }, + parentReleaseEvent: { + x: 0, y: 0, button: Qt.LeftButton, buttons: Qt.NoButton, modifiers: Qt.NoModifier, wasHeld: false, isClick: false + }, + }, + { + tag: "left click", x: 0, y: 0, button: Qt.LeftButton, + controlPressEvent: { + x: 0, y: 0, button: Qt.LeftButton, buttons: Qt.LeftButton, modifiers: Qt.NoModifier, wasHeld: false, isClick: false + }, + controlReleaseEvent: { + x: 0, y: 0, button: Qt.LeftButton, buttons: Qt.NoButton, modifiers: Qt.NoModifier, wasHeld: false, isClick: false + }, + parentPressEvent: null, + parentReleaseEvent: null, + }, + { + tag: "right click", x: 0, y: 0, button: Qt.RightButton, + controlPressEvent: { + x: 0, y: 0, button: Qt.RightButton, buttons: Qt.RightButton, modifiers: Qt.NoModifier, wasHeld: false, isClick: false + }, + controlReleaseEvent: { + x: 0, y: 0, button: Qt.RightButton, buttons: Qt.NoButton, modifiers: Qt.NoModifier, wasHeld: false, isClick: false + }, + parentPressEvent: null, + parentReleaseEvent: null, + }, + ]; + } + + Component { + id: mouseAreaComponent + MouseArea { + anchors.fill: parent + } + } + + function checkMouseEvent(event, expectedEvent) { + compare(event.x, expectedEvent.x) + compare(event.y, expectedEvent.y) + compare(event.button, expectedEvent.button) + compare(event.buttons, expectedEvent.buttons) + } + + function test_pressedReleased(data) { + var mouseArea = createTemporaryObject(mouseAreaComponent, testCase) + verify(mouseArea) + var control = textField.createObject(mouseArea) + verify(control) + + // Give enough room to check presses outside of the control and on the parent. + control.x = 1; + control.y = 1; + + function checkControlPressEvent(event) { + checkMouseEvent(event, data.controlPressEvent) + } + function checkControlReleaseEvent(event) { + checkMouseEvent(event, data.controlReleaseEvent) + } + function checkParentPressEvent(event) { + checkMouseEvent(event, data.parentPressEvent) + } + function checkParentReleaseEvent(event) { + checkMouseEvent(event, data.parentReleaseEvent) + } + + // Can't use signalArguments, because the event won't live that long. + if (data.controlPressEvent) + control.onPressed.connect(checkControlPressEvent) + if (data.controlReleaseEvent) + control.onReleased.connect(checkControlReleaseEvent) + if (data.parentPressEvent) + control.onPressed.connect(checkParentPressEvent) + if (data.parentReleaseEvent) + control.onReleased.connect(checkParentReleaseEvent) + + var controlPressedSpy = signalSpy.createObject(control, { target: control, signalName: "pressed" }) + verify(controlPressedSpy.valid) + var controlReleasedSpy = signalSpy.createObject(control, { target: control, signalName: "released" }) + verify(controlReleasedSpy.valid) + var parentPressedSpy = signalSpy.createObject(mouseArea, { target: mouseArea, signalName: "pressed" }) + verify(parentPressedSpy.valid) + var parentReleasedSpy = signalSpy.createObject(mouseArea, { target: mouseArea, signalName: "released" }) + verify(parentReleasedSpy.valid) + + mousePress(control, data.x, data.y, data.button) + compare(controlPressedSpy.count, data.controlPressEvent ? 1 : 0) + compare(parentPressedSpy.count, data.parentPressEvent ? 1 : 0) + mouseRelease(control, data.x, data.y, data.button) + compare(controlReleasedSpy.count, data.controlReleaseEvent ? 1 : 0) + compare(parentReleasedSpy.count, data.parentReleaseEvent ? 1 : 0) + } + + Component { + id: ignoreTextField + + TextField { + property bool ignorePress: false + property bool ignoreRelease: false + + onPressed: if (ignorePress) event.accepted = false + onReleased: if (ignoreRelease) event.accepted = false + } + } + + function checkEventAccepted(event) { + compare(event.accepted, true) + } + + function checkEventIgnored(event) { + compare(event.accepted, false) + } + + function test_ignorePressRelease() { + var mouseArea = createTemporaryObject(mouseAreaComponent, testCase) + verify(mouseArea) + var control = ignoreTextField.createObject(mouseArea) + verify(control) + + var controlPressedSpy = signalSpy.createObject(control, { target: control, signalName: "pressed" }) + verify(controlPressedSpy.valid) + var controlReleasedSpy = signalSpy.createObject(control, { target: control, signalName: "released" }) + verify(controlReleasedSpy.valid) + var parentPressedSpy = signalSpy.createObject(mouseArea, { target: mouseArea, signalName: "pressed" }) + verify(parentPressedSpy.valid) + var parentReleasedSpy = signalSpy.createObject(mouseArea, { target: mouseArea, signalName: "released" }) + verify(parentReleasedSpy.valid) + + // Ignore only press events. + control.onPressed.connect(checkEventIgnored) + control.ignorePress = true + mousePress(control, 0, 0, data.button) + // The control will still get the signal, it just won't accept the event. + compare(controlPressedSpy.count, 1) + compare(parentPressedSpy.count, 1) + mouseRelease(control, 0, 0, data.button) + compare(controlReleasedSpy.count, 0) + compare(parentReleasedSpy.count, 1) + control.onPressed.disconnect(checkEventIgnored) + + // Ignore only release events. + control.onPressed.connect(checkEventAccepted) + control.onReleased.connect(checkEventIgnored) + control.ignorePress = false + control.ignoreRelease = true + mousePress(control, 0, 0, data.button) + compare(controlPressedSpy.count, 2) + compare(parentPressedSpy.count, 1) + mouseRelease(control, 0, 0, data.button) + compare(controlReleasedSpy.count, 1) + compare(parentReleasedSpy.count, 1) + control.onPressed.disconnect(checkEventAccepted) + control.onReleased.disconnect(checkEventIgnored) + } + + function test_multiClick() { + var control = createTemporaryObject(textField, testCase, {text: "Qt Quick Controls 2 TextArea", selectByMouse: true}) + verify(control) + + waitForRendering(control) + control.width = control.contentWidth + var rect = control.positionToRectangle(12) + + // double click -> select word + mouseDoubleClickSequence(control, rect.x + rect.width / 2, rect.y + rect.height / 2) + compare(control.selectedText, "Controls") + + // tripple click -> select whole line + mouseClick(control, rect.x + rect.width / 2, rect.y + rect.height / 2) + compare(control.selectedText, "Qt Quick Controls 2 TextArea") + } + + // QTBUG-64048 + function test_rightClick() { + var control = createTemporaryObject(textField, testCase, {text: "TextField", selectByMouse: true}) + verify(control) + + control.selectAll() + compare(control.selectedText, "TextField") + + mouseClick(control, control.width / 2, control.height / 2, Qt.RightButton) + compare(control.selectedText, "TextField") + + mouseClick(control, control.width / 2, control.height / 2, Qt.LeftButton | Qt.RightButton) + compare(control.selectedText, "") + } + + // QTBUG-66260 + function test_placeholderTextColor() { + var control = createTemporaryObject(textField, testCase) + verify(control) + + // usually default value should not be pure opacue black + verify(control.placeholderTextColor !== "#ff000000") + control.placeholderTextColor = "#12345678" + compare(control.placeholderTextColor, "#12345678") + + for (var i = 0; i < control.children.length; ++i) { + if (control.children[i].hasOwnProperty("text")) + compare(control.children[i].color, control.placeholderTextColor) // placeholder.color + } + } + + function test_inset() { + var control = createTemporaryObject(textField, testCase, {background: rectangle.createObject(control)}) + verify(control) + + var topInsetSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "topInsetChanged"}) + verify(topInsetSpy.valid) + + var leftInsetSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "leftInsetChanged"}) + verify(leftInsetSpy.valid) + + var rightInsetSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "rightInsetChanged"}) + verify(rightInsetSpy.valid) + + var bottomInsetSpy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: "bottomInsetChanged"}) + verify(bottomInsetSpy.valid) + + var topInsetChanges = 0 + var leftInsetChanges = 0 + var rightInsetChanges = 0 + var bottomInsetChanges = 0 + + compare(control.topInset, 0) + compare(control.leftInset, 0) + compare(control.rightInset, 0) + compare(control.bottomInset, 0) + + control.width = 100 + control.height = 100 + compare(control.background.x, 0) + compare(control.background.y, 0) + compare(control.background.width, 100) + compare(control.background.height, 100) + + control.topInset = 10 + compare(control.topInset, 10) + compare(control.leftInset, 0) + compare(control.rightInset, 0) + compare(control.bottomInset, 0) + compare(topInsetSpy.count, ++topInsetChanges) + compare(leftInsetSpy.count, leftInsetChanges) + compare(rightInsetSpy.count, rightInsetChanges) + compare(bottomInsetSpy.count, bottomInsetChanges) + compare(control.background.x, 0) + compare(control.background.y, 10) + compare(control.background.width, 100) + compare(control.background.height, 90) + + control.leftInset = 20 + compare(control.topInset, 10) + compare(control.leftInset, 20) + compare(control.rightInset, 0) + compare(control.bottomInset, 0) + compare(topInsetSpy.count, topInsetChanges) + compare(leftInsetSpy.count, ++leftInsetChanges) + compare(rightInsetSpy.count, rightInsetChanges) + compare(bottomInsetSpy.count, bottomInsetChanges) + compare(control.background.x, 20) + compare(control.background.y, 10) + compare(control.background.width, 80) + compare(control.background.height, 90) + + control.rightInset = 30 + compare(control.topInset, 10) + compare(control.leftInset, 20) + compare(control.rightInset, 30) + compare(control.bottomInset, 0) + compare(topInsetSpy.count, topInsetChanges) + compare(leftInsetSpy.count, leftInsetChanges) + compare(rightInsetSpy.count, ++rightInsetChanges) + compare(bottomInsetSpy.count, bottomInsetChanges) + compare(control.background.x, 20) + compare(control.background.y, 10) + compare(control.background.width, 50) + compare(control.background.height, 90) + + control.bottomInset = 40 + compare(control.topInset, 10) + compare(control.leftInset, 20) + compare(control.rightInset, 30) + compare(control.bottomInset, 40) + compare(topInsetSpy.count, topInsetChanges) + compare(leftInsetSpy.count, leftInsetChanges) + compare(rightInsetSpy.count, rightInsetChanges) + compare(bottomInsetSpy.count, ++bottomInsetChanges) + compare(control.background.x, 20) + compare(control.background.y, 10) + compare(control.background.width, 50) + compare(control.background.height, 50) + + control.topInset = undefined + compare(control.topInset, 0) + compare(control.leftInset, 20) + compare(control.rightInset, 30) + compare(control.bottomInset, 40) + compare(topInsetSpy.count, ++topInsetChanges) + compare(leftInsetSpy.count, leftInsetChanges) + compare(rightInsetSpy.count, rightInsetChanges) + compare(bottomInsetSpy.count, bottomInsetChanges) + compare(control.background.x, 20) + compare(control.background.y, 0) + compare(control.background.width, 50) + compare(control.background.height, 60) + + control.leftInset = undefined + compare(control.topInset, 0) + compare(control.leftInset, 0) + compare(control.rightInset, 30) + compare(control.bottomInset, 40) + compare(topInsetSpy.count, topInsetChanges) + compare(leftInsetSpy.count, ++leftInsetChanges) + compare(rightInsetSpy.count, rightInsetChanges) + compare(bottomInsetSpy.count, bottomInsetChanges) + compare(control.background.x, 0) + compare(control.background.y, 0) + compare(control.background.width, 70) + compare(control.background.height, 60) + + control.rightInset = undefined + compare(control.topInset, 0) + compare(control.leftInset, 0) + compare(control.rightInset, 0) + compare(control.bottomInset, 40) + compare(topInsetSpy.count, topInsetChanges) + compare(leftInsetSpy.count, leftInsetChanges) + compare(rightInsetSpy.count, ++rightInsetChanges) + compare(bottomInsetSpy.count, bottomInsetChanges) + compare(control.background.x, 0) + compare(control.background.y, 0) + compare(control.background.width, 100) + compare(control.background.height, 60) + + control.bottomInset = undefined + compare(control.topInset, 0) + compare(control.leftInset, 0) + compare(control.rightInset, 0) + compare(control.bottomInset, 0) + compare(topInsetSpy.count, topInsetChanges) + compare(leftInsetSpy.count, leftInsetChanges) + compare(rightInsetSpy.count, rightInsetChanges) + compare(bottomInsetSpy.count, ++bottomInsetChanges) + compare(control.background.x, 0) + compare(control.background.y, 0) + compare(control.background.width, 100) + compare(control.background.height, 100) + } + + Component { + id: layoutComponent + + ColumnLayout { + anchors.fill: parent + + property alias textField: textField + + TextField { + id: textField + placeholderText: "Placeholder" + Layout.fillWidth: true + } + } + } + + function test_inLayout() { + var layout = createTemporaryObject(layoutComponent, testCase) + verify(layout) + + var control = layout.textField + verify(control) + + compare(control.width, control.parent.width) + compare(control.background.width, control.width) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_toolbar.qml b/tests/auto/quickcontrols2/controls/data/tst_toolbar.qml new file mode 100644 index 0000000000..b35a927d3e --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_toolbar.qml @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "ToolBar" + + Component { + id: toolBar + ToolBar { } + } + + Component { + id: oneChildBar + ToolBar { + Item { + implicitWidth: 100 + implicitHeight: 30 + } + } + } + + Component { + id: twoChildrenBar + ToolBar { + Item { + implicitWidth: 100 + implicitHeight: 30 + } + Item { + implicitWidth: 200 + implicitHeight: 60 + } + } + } + + Component { + id: contentBar + ToolBar { + contentItem: Item { + implicitWidth: 100 + implicitHeight: 30 + } + } + } + + function test_empty() { + var control = createTemporaryObject(toolBar, testCase) + verify(control) + + verify(control.contentItem) + compare(control.contentWidth, 0) + compare(control.contentHeight, 0) + compare(control.implicitContentWidth, 0) + compare(control.implicitContentHeight, 0) + } + + function test_oneChild() { + var control = createTemporaryObject(oneChildBar, testCase) + verify(control) + + compare(control.contentWidth, 100) + compare(control.contentHeight, 30) + compare(control.implicitContentWidth, 100) + compare(control.implicitContentHeight, 30) + verify(control.implicitWidth >= 100) + verify(control.implicitHeight >= 30) + } + + function test_twoChildren() { + var control = createTemporaryObject(twoChildrenBar, testCase) + verify(control) + + compare(control.contentWidth, 0) + compare(control.contentHeight, 0) + compare(control.implicitContentWidth, 0) + compare(control.implicitContentHeight, 0) + verify(control.implicitWidth >= 0) + verify(control.implicitHeight >= 0) + } + + function test_contentItem() { + var control = createTemporaryObject(contentBar, testCase) + verify(control) + + compare(control.contentWidth, 100) + compare(control.contentHeight, 30) + compare(control.implicitContentWidth, 100) + compare(control.implicitContentHeight, 30) + verify(control.implicitWidth >= 100) + verify(control.implicitHeight >= 30) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_toolbutton.qml b/tests/auto/quickcontrols2/controls/data/tst_toolbutton.qml new file mode 100644 index 0000000000..a7acd05ae4 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_toolbutton.qml @@ -0,0 +1,243 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 200 + height: 200 + visible: true + when: windowShown + name: "ToolButton" + + Component { + id: signalSpy + SignalSpy { } + } + + Component { + id: toolButton + ToolButton { } + } + + function test_text() { + var control = createTemporaryObject(toolButton, testCase) + verify(control) + + compare(control.text, "") + control.text = "ToolButton" + compare(control.text, "ToolButton") + control.text = "" + compare(control.text, "") + } + + function test_mouse() { + var control = createTemporaryObject(toolButton, testCase) + verify(control) + + var pressedSpy = signalSpy.createObject(control, {target: control, signalName: "pressedChanged"}) + verify(pressedSpy.valid) + + var downSpy = signalSpy.createObject(control, {target: control, signalName: "downChanged"}) + verify(downSpy.valid) + + var clickedSpy = signalSpy.createObject(control, {target: control, signalName: "clicked"}) + verify(clickedSpy.valid) + + // check + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(pressedSpy.count, 1) + compare(downSpy.count, 1) + compare(control.pressed, true) + compare(control.down, true) + mouseRelease(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(clickedSpy.count, 1) + compare(pressedSpy.count, 2) + compare(downSpy.count, 2) + compare(control.pressed, false) + compare(control.down, false) + + // uncheck + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(pressedSpy.count, 3) + compare(downSpy.count, 3) + compare(control.pressed, true) + compare(control.down, true) + mouseRelease(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(clickedSpy.count, 2) + compare(pressedSpy.count, 4) + compare(downSpy.count, 4) + compare(control.pressed, false) + compare(control.down, false) + + // release outside + mousePress(control, control.width / 2, control.height / 2, Qt.LeftButton) + compare(pressedSpy.count, 5) + compare(downSpy.count, 5) + compare(control.pressed, true) + compare(control.down, true) + mouseMove(control, control.width * 2, control.height * 2) + compare(control.pressed, false) + compare(control.down, false) + mouseRelease(control, control.width * 2, control.height * 2, Qt.LeftButton) + compare(clickedSpy.count, 2) + compare(pressedSpy.count, 6) + compare(downSpy.count, 6) + compare(control.pressed, false) + compare(control.down, false) + + // right button + mousePress(control, control.width / 2, control.height / 2, Qt.RightButton) + compare(pressedSpy.count, 6) + compare(downSpy.count, 6) + compare(control.pressed, false) + compare(control.down, false) + mouseRelease(control, control.width / 2, control.height / 2, Qt.RightButton) + compare(clickedSpy.count, 2) + compare(pressedSpy.count, 6) + compare(downSpy.count, 6) + compare(control.pressed, false) + compare(control.down, false) + } + + function test_keys() { + var control = createTemporaryObject(toolButton, testCase) + verify(control) + + var clickedSpy = signalSpy.createObject(control, {target: control, signalName: "clicked"}) + verify(clickedSpy.valid) + + control.forceActiveFocus() + verify(control.activeFocus) + + // check + keyClick(Qt.Key_Space) + compare(clickedSpy.count, 1) + + // uncheck + keyClick(Qt.Key_Space) + compare(clickedSpy.count, 2) + + // no change + var keys = [Qt.Key_Enter, Qt.Key_Return, Qt.Key_Escape, Qt.Key_Tab] + for (var i = 0; i < keys.length; ++i) { + keyClick(keys[i]) + compare(clickedSpy.count, 2) + } + } + + function test_baseline() { + var control = createTemporaryObject(toolButton, testCase) + verify(control) + compare(control.baselineOffset, control.contentItem.y + control.contentItem.baselineOffset) + } + + function test_display_data() { + return [ + { "tag": "IconOnly", display: ToolButton.IconOnly }, + { "tag": "TextOnly", display: ToolButton.TextOnly }, + { "tag": "TextUnderIcon", display: ToolButton.TextUnderIcon }, + { "tag": "TextBesideIcon", display: ToolButton.TextBesideIcon }, + { "tag": "IconOnly, mirrored", display: ToolButton.IconOnly, mirrored: true }, + { "tag": "TextOnly, mirrored", display: ToolButton.TextOnly, mirrored: true }, + { "tag": "TextUnderIcon, mirrored", display: ToolButton.TextUnderIcon, mirrored: true }, + { "tag": "TextBesideIcon, mirrored", display: ToolButton.TextBesideIcon, mirrored: true } + ] + } + + function test_display(data) { + var control = createTemporaryObject(toolButton, testCase, { + text: "ToolButton", + display: data.display, + "icon.source": "qrc:/qt-project.org/imports/QtQuick/Controls/Basic/images/check.png", + "LayoutMirroring.enabled": !!data.mirrored + }) + verify(control) + compare(control.icon.source, "qrc:/qt-project.org/imports/QtQuick/Controls/Basic/images/check.png") + + var iconImage = findChild(control.contentItem, "image") + var textLabel = findChild(control.contentItem, "label") + + switch (control.display) { + case ToolButton.IconOnly: + verify(iconImage) + verify(!textLabel) + compare(iconImage.x, (control.availableWidth - iconImage.width) / 2) + compare(iconImage.y, (control.availableHeight - iconImage.height) / 2) + break; + case ToolButton.TextOnly: + verify(!iconImage) + verify(textLabel) + compare(textLabel.x, (control.availableWidth - textLabel.width) / 2) + compare(textLabel.y, (control.availableHeight - textLabel.height) / 2) + break; + case ToolButton.TextUnderIcon: + verify(iconImage) + verify(textLabel) + compare(iconImage.x, (control.availableWidth - iconImage.width) / 2) + compare(textLabel.x, (control.availableWidth - textLabel.width) / 2) + verify(iconImage.y < textLabel.y) + break; + case ToolButton.TextBesideIcon: + verify(iconImage) + verify(textLabel) + if (control.mirrored) + verify(textLabel.x < iconImage.x) + else + verify(iconImage.x < textLabel.x) + compare(iconImage.y, (control.availableHeight - iconImage.height) / 2) + compare(textLabel.y, (control.availableHeight - textLabel.height) / 2) + break; + } + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_toolseparator.qml b/tests/auto/quickcontrols2/controls/data/tst_toolseparator.qml new file mode 100644 index 0000000000..41c161dd4b --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_toolseparator.qml @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + name: "ToolSeparator" + + Component { + id: toolSeparator + ToolSeparator {} + } + + function test_size() { + var control = createTemporaryObject(toolSeparator, testCase); + verify(control); + verify(control.width > 1); + verify(control.height > 1); + } + + Component { + id: signalSpyComponent + SignalSpy {} + } + + function test_orientation() { + var control = createTemporaryObject(toolSeparator, testCase); + verify(control); + compare(control.horizontal, false); + compare(control.vertical, true); + + var orientationSpy = signalSpyComponent.createObject(control, { target: control, signalName: "orientationChanged" }); + + var originalWidth = control.width; + var originalHeight = control.height; + control.orientation = Qt.Horizontal; + compare(control.orientation, Qt.Horizontal); + compare(control.width, originalHeight); + compare(control.height, originalWidth); + compare(control.horizontal, true); + compare(control.vertical, false); + compare(orientationSpy.count, 1); + + control.orientation = Qt.Vertical; + compare(control.orientation, Qt.Vertical); + compare(control.width, originalWidth); + compare(control.height, originalHeight); + compare(control.horizontal, false); + compare(control.vertical, true); + compare(orientationSpy.count, 2); + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_tooltip.qml b/tests/auto/quickcontrols2/controls/data/tst_tooltip.qml new file mode 100644 index 0000000000..2f98a5f3d2 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_tooltip.qml @@ -0,0 +1,477 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 400 + height: 400 + visible: true + when: windowShown + name: "ToolTip" + + Component { + id: toolTip + ToolTip { } + } + + Component { + id: mouseArea + MouseArea { } + } + + Component { + id: signalSpy + SignalSpy { } + } + + Component { + id: itemComponent + Item {} + } + + QtObject { + id: object + } + + SignalSpy { + id: sharedSpy + target: ToolTip.toolTip + } + + function test_properties_data() { + return [ + {tag: "text", property: "text", defaultValue: "", setValue: "Hello", signalName: "textChanged"}, + {tag: "delay", property: "delay", defaultValue: 0, setValue: 1000, signalName: "delayChanged"}, + {tag: "timeout", property: "timeout", defaultValue: -1, setValue: 2000, signalName: "timeoutChanged"} + ] + } + + function test_properties(data) { + var control = createTemporaryObject(toolTip, testCase) + verify(control) + + compare(control[data.property], data.defaultValue) + + var spy = createTemporaryObject(signalSpy, testCase, {target: control, signalName: data.signalName}) + verify(spy.valid) + + control[data.property] = data.setValue + compare(control[data.property], data.setValue) + compare(spy.count, 1) + } + + function test_attached_data() { + return [ + {tag: "text", property: "text", defaultValue: "", setValue: "Hello", signalName: "textChanged"}, + {tag: "delay", property: "delay", defaultValue: 0, setValue: 1000, signalName: "delayChanged"}, + {tag: "timeout", property: "timeout", defaultValue: -1, setValue: 2000, signalName: "timeoutChanged"} + ] + } + + function test_attached(data) { + var item1 = createTemporaryObject(mouseArea, testCase) + verify(item1) + + var item2 = createTemporaryObject(mouseArea, testCase) + verify(item2) + + // Reset the properties to the expected default values, in case + // we're not the first test that uses attached properties to be run. + var sharedTip = ToolTip.toolTip + sharedTip[data.property] = data.defaultValue + + compare(item1.ToolTip[data.property], data.defaultValue) + compare(item2.ToolTip[data.property], data.defaultValue) + + var spy1 = signalSpy.createObject(item1, {target: item1.ToolTip, signalName: data.signalName}) + verify(spy1.valid) + + var spy2 = signalSpy.createObject(item2, {target: item2.ToolTip, signalName: data.signalName}) + verify(spy2.valid) + + sharedSpy.signalName = data.signalName + verify(sharedSpy.valid) + sharedSpy.clear() + + // change attached properties while the shared tooltip is not visible + item1.ToolTip[data.property] = data.setValue + compare(item1.ToolTip[data.property], data.setValue) + compare(spy1.count, 1) + + compare(spy2.count, 0) + compare(item2.ToolTip[data.property], data.defaultValue) + + // the shared tooltip is not visible for item1, so the attached + // property change should therefore not apply to the shared instance + compare(sharedSpy.count, 0) + compare(sharedTip[data.property], data.defaultValue) + + // show the shared tooltip for item2 + item2.ToolTip.visible = true + verify(item2.ToolTip.visible) + verify(sharedTip.visible) + + // change attached properties while the shared tooltip is visible + item2.ToolTip[data.property] = data.setValue + compare(item2.ToolTip[data.property], data.setValue) + compare(spy2.count, 1) + + // the shared tooltip is visible for item2, so the attached + // property change should apply to the shared instance + compare(sharedSpy.count, 1) + compare(sharedTip[data.property], data.setValue) + } + + function test_delay_data() { + return [ + {tag: "imperative:0", delay: 0, imperative: true}, + {tag: "imperative:100", delay: 100, imperative: true}, + {tag: "declarative:0", delay: 0, imperative: false}, + {tag: "declarative:100", delay: 100, imperative: false} + ] + } + + function test_delay(data) { + var control = createTemporaryObject(toolTip, testCase, {delay: data.delay}) + + compare(control.visible, false) + if (data.imperative) + control.open() + else + control.visible = true + compare(control.visible, data.delay <= 0) + tryCompare(control, "visible", true) + } + + function test_timeout_data() { + return [ + {tag: "imperative", imperative: true}, + {tag: "declarative", imperative: false} + ] + } + + function test_timeout(data) { + var control = createTemporaryObject(toolTip, testCase, {timeout: 100}) + + compare(control.visible, false) + if (data.imperative) + control.open() + else + control.visible = true + compare(control.visible, true) + // wait a bit to make sure that it's still visible + wait(50) + compare(control.visible, true) + // re-arm for another 200 ms + control.timeout = 200 + compare(control.visible, true) + // ensure that it's still visible after 150 ms (where old timeout < 150 < new timeout) + wait(150) + compare(control.visible, true) + tryCompare(control, "visible", false) + } + + function test_warning() { + ignoreWarning(new RegExp(".*QML QtObject: ToolTip must be attached to an Item")) + ignoreWarning(new RegExp(".*: QML ToolTip: cannot find any window to open popup in.")) + object.ToolTip.show("") // don't crash (QTBUG-56243) + } + + Component { + id: toolTipWithExitTransition + + ToolTip { + Component.onCompleted: contentItem.objectName = "contentItem" + + enter: Transition { + NumberAnimation { property: "opacity"; from: 0.0; to: 1.0; duration: 100 } + } + exit: Transition { + NumberAnimation { property: "opacity"; from: 1.0; to: 0.0; duration: 500 } + } + } + } + + function test_makeVisibleWhileExitTransitionRunning_data() { + return [ + { tag: "imperative", imperative: true }, + { tag: "declarative", imperative: false } + ] + } + + function test_makeVisibleWhileExitTransitionRunning(data) { + var control = createTemporaryObject(toolTipWithExitTransition, testCase) + + // Show, hide, and show the tooltip again. Its exit transition should + // start and get cancelled, and then its enter transition should run. + if (data.imperative) + control.open() + else + control.visible = true + tryCompare(control, "opacity", 1) + + if (data.imperative) + control.close() + else + control.visible = false + verify(control.exit.running) + tryVerify(function() { return control.opacity < 1; }) + + if (data.imperative) + control.open() + else + control.visible = true + tryCompare(control, "opacity", 1) + } + + Component { + id: buttonAndShortcutComponent + + Item { + property alias shortcut: shortcut + property alias button: button + + Shortcut { + id: shortcut + sequence: "A" + } + + Button { + id: button + text: "Just a button" + focusPolicy: Qt.NoFocus + + ToolTip.visible: button.hovered + ToolTip.text: qsTr("Some helpful text") + } + } + } + + function test_activateShortcutWhileToolTipVisible() { + if ((Qt.platform.pluginName === "offscreen") + || (Qt.platform.pluginName === "minimal")) + skip("Mouse hoovering not functional on offscreen/minimal platforms") + + var root = createTemporaryObject(buttonAndShortcutComponent, testCase) + verify(root) + + mouseMove(root.button, root.button.width / 2, root.button.height / 2) + tryCompare(root.button.ToolTip.toolTip, "visible", true) + + var shortcutActivatedSpy = signalSpy.createObject(root, { target: root.shortcut, signalName: "activated" }) + verify(shortcutActivatedSpy.valid) + keyPress(Qt.Key_A) + compare(shortcutActivatedSpy.count, 1) + } + + Component { + id: hoverComponent + MouseArea { + id: hoverArea + property alias tooltip: tooltip + hoverEnabled: true + width: testCase.width + height: testCase.height + ToolTip { + id: tooltip + x: 10; y: 10 + width: 10; height: 10 + visible: hoverArea.containsMouse + } + } + } + + // QTBUG-63644 + function test_hover() { + var root = createTemporaryObject(hoverComponent, testCase) + verify(root) + + var tooltip = root.tooltip + verify(tooltip) + + for (var pos = 0; pos <= 25; pos += 5) { + mouseMove(root, pos, pos) + verify(tooltip.visible) + } + } + + Component { + id: nonAttachedToolTipComponent + ToolTip { } + } + + function test_nonAttachedToolTipShowAndHide() { + var tip = createTemporaryObject(nonAttachedToolTipComponent, testCase) + verify(tip) + tip.show("hello"); + verify(tip.visible) + verify(tip.text === "hello") + tip.hide() + tryCompare(tip, "visible", false) + tip.show("delay", 200) + verify(tip.visible) + tryCompare(tip, "visible", false) + } + + Component { + id: timeoutButtonRowComponent + + Row { + Button { + text: "Timeout: 1" + ToolTip.text: text + ToolTip.visible: down + ToolTip.timeout: 1 + } + + Button { + text: "Timeout: -1" + ToolTip.text: text + ToolTip.visible: down + } + } + } + + // QTBUG-74226 + function test_attachedTimeout() { + var row = createTemporaryObject(timeoutButtonRowComponent, testCase) + verify(row) + + // Press the button that has no timeout; it should stay visible. + var button2 = row.children[1] + mousePress(button2) + compare(button2.down, true) + tryCompare(button2.ToolTip.toolTip, "opened", true) + + // Wait a bit to make sure that it's still visible. + wait(50) + compare(button2.ToolTip.toolTip.opened, true) + + // Release and should close. + mouseRelease(button2) + compare(button2.down, false) + tryCompare(button2.ToolTip, "visible", false) + + // Now, press the first button that does have a timeout; it should close on its own eventually. + var button1 = row.children[0] + mousePress(button1) + compare(button1.down, true) + // We use a short timeout to speed up the test, but tryCompare(...opened, true) then + // fails because the dialog has already been hidden by that point, so just check that it's + // immediately visible, which is more or less the same thing. + compare(button1.ToolTip.visible, true) + tryCompare(button1.ToolTip, "visible", false) + mouseRelease(button2) + + // Now, hover over the second button again. It should still stay visible until the mouse is released. + mousePress(button2) + compare(button2.down, true) + tryCompare(button2.ToolTip.toolTip, "opened", true) + + // Wait a bit to make sure that it's still visible. + wait(50) + compare(button2.ToolTip.toolTip.opened, true) + + // Release and should close. + mouseRelease(button2) + compare(button2.down, false) + tryCompare(button2.ToolTip, "visible", false) + } + + Component { + id: wrapComponent + + Item { + ToolTip.text: "This is some very very very very very very very very very very very very" + + " very very very very very very very very very very very very very very" + + " very very very very very very very very very very very very long text" + } + } + + // QTBUG-62350 + function test_wrap() { + var item = createTemporaryObject(wrapComponent, testCase) + verify(item) + + // Avoid "cannot find window to popup in" warning that can occur if it's made visible too early. + item.ToolTip.visible = true + tryCompare(item.ToolTip.toolTip, "opened", true) + compare(item.ToolTip.toolTip.contentItem.wrapMode, Text.Wrap) + verify(item.ToolTip.toolTip.contentItem.width < item.ToolTip.toolTip.contentItem.implicitWidth) + } + + // QTBUG-83630: Test that newlines are accounted for in the implicit contentWidth. + function test_newLines() { + var item = createTemporaryObject(itemComponent, testCase) + verify(item) + + item.ToolTip.show("This is one line of text\nThis is another line of text") + + // The implicitWidth of the Text item for the text above will be larger than + // its contentWidth. ToolTip's implicitWidth uses contentWidth in its calculation, + // so we check that it's less than the Text's implicitWidth. + verify(item.ToolTip.toolTip.implicitWidth < item.ToolTip.toolTip.contentItem.implicitWidth) + } + + function test_timeoutAfterOpened() { + let control = createTemporaryObject(toolTipWithExitTransition, testCase, { timeout: 1, exit: null }) + verify(control) + + let openedSpy = createTemporaryObject(signalSpy, testCase, { target: control, signalName: "opened" }) + verify(openedSpy.valid) + + control.show("Test") + tryCompare(openedSpy, "count", 1) + } +} diff --git a/tests/auto/quickcontrols2/controls/data/tst_tumbler.qml b/tests/auto/quickcontrols2/controls/data/tst_tumbler.qml new file mode 100644 index 0000000000..3a09bd9fe9 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/data/tst_tumbler.qml @@ -0,0 +1,1281 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 QtTest +import QtQuick.Controls + +TestCase { + id: testCase + width: 300 + height: 300 + visible: true + when: windowShown + name: "Tumbler" + + property var tumbler: null + readonly property real implicitTumblerWidth: 60 + readonly property real implicitTumblerHeight: 200 + readonly property real defaultImplicitDelegateHeight: implicitTumblerHeight / 3 + readonly property real defaultListViewTumblerOffset: -defaultImplicitDelegateHeight + readonly property real tumblerDelegateHeight: tumbler ? tumbler.availableHeight / tumbler.visibleItemCount : 0 + property Item tumblerView: null + + Component { + id: tumblerComponent + + Tumbler { + visibleItemCount: 3 + } + } + + Component { + id: itemComponent + + Item { + anchors.fill: parent + } + } + + function createTumbler(args) { + tumbler = createTemporaryObject(tumblerComponent, testCase, args); + verify(tumbler, "Tumbler: failed to create an instance"); + tumblerView = findView(tumbler); + verify(tumblerView); + } + + function tumblerXCenter() { + return tumbler.leftPadding + tumbler.width / 2; + } + + function tumblerYCenter() { + return tumbler.topPadding + tumbler.height / 2; + } + + // visualItemIndex is from 0 to the amount of visible items. + function itemCenterPos(visualItemIndex) { + var halfDelegateHeight = tumblerDelegateHeight / 2; + var yCenter = tumbler.y + tumbler.topPadding + halfDelegateHeight + + (tumblerDelegateHeight * visualItemIndex); + return Qt.point(tumblerXCenter(), yCenter); + } + + function itemTopLeftPos(visualItemIndex) { + return Qt.point(tumbler.leftPadding, tumbler.topPadding + (tumblerDelegateHeight * visualItemIndex)); + } + + function checkItemSizes() { + var contentChildren = tumbler.wrap ? tumblerView.children : tumblerView.contentItem.children; + verify(contentChildren.length >= tumbler.count); + for (var i = 0; i < contentChildren.length; ++i) { + compare(contentChildren[i].width, tumbler.availableWidth); + compare(contentChildren[i].height, tumblerDelegateHeight); + } + } + + function findView(parent) { + for (var i = 0; i < parent.children.length; ++i) { + var child = parent.children[i]; + if (child.hasOwnProperty("currentIndex")) { + return child; + } + + var grandChild = findView(child); + if (grandChild) + return grandChild; + } + + return null; + } + + function findDelegateWithText(parent, text) { + for (var i = 0; i < parent.children.length; ++i) { + var child = parent.children[i]; + if (child.hasOwnProperty("text") && child.text === text) { + return child; + } + + var grandChild = findDelegateWithText(child, text); + if (grandChild) + return grandChild; + } + + return null; + } + + property Component noAttachedPropertiesDelegate: Text { + text: modelData + } + + function test_wrapWithoutAttachedProperties() { + createTumbler(); + verify(tumbler.wrap); + + tumbler.delegate = noAttachedPropertiesDelegate; + // Shouldn't assert. + tumbler.wrap = false; + verify(findView(tumbler)); + } + + // TODO: test that currentIndex is maintained between contentItem changes... +// function tst_dynamicContentItemChange() { +// } + + function test_currentIndex() { + createTumbler(); + compare(tumbler.contentItem.parent, tumbler); + + tumbler.model = 5; + + compare(tumbler.currentIndex, 0); + waitForRendering(tumbler); + + // Set it through user interaction. + var pos = Qt.point(tumblerXCenter(), tumbler.height / 2); + mouseDrag(tumbler, pos.x, pos.y, 0, tumbler.height / 3, Qt.LeftButton, Qt.NoModifier, 200); + tryCompare(tumblerView, "offset", 1); + compare(tumbler.currentIndex, 4); + compare(tumblerView.currentIndex, 4); + + // Set it manually. + tumbler.currentIndex = 2; + tryCompare(tumbler, "currentIndex", 2); + compare(tumblerView.currentIndex, 2); + + tumbler.model = null; + tryCompare(tumbler, "currentIndex", -1); + // PathView will only use 0 as the currentIndex when there are no items. + compare(tumblerView.currentIndex, 0); + + tumbler.model = ["A", "B", "C"]; + tryCompare(tumbler, "currentIndex", 0); + + // Setting a negative current index should have no effect, because the model isn't empty. + tumbler.currentIndex = -1; + compare(tumbler.currentIndex, 0); + + tumbler.model = 1; + compare(tumbler.currentIndex, 0); + + tumbler.model = 5; + compare(tumbler.count, 5); + tumblerView = findView(tumbler); + tryCompare(tumblerView, "count", 5); + tumbler.currentIndex = 4; + compare(tumbler.currentIndex, 4); + compare(tumblerView.currentIndex, 4); + + --tumbler.model; + compare(tumbler.count, 4); + compare(tumblerView.count, 4); + // Removing an item from an integer-based model will cause views to reset their currentIndex to 0. + compare(tumbler.currentIndex, 0); + compare(tumblerView.currentIndex, 0); + + tumbler.model = 0; + compare(tumbler.currentIndex, -1); + } + + Component { + id: currentIndexTumbler + + Tumbler { + model: 5 + currentIndex: 2 + visibleItemCount: 3 + } + } + + Component { + id: currentIndexTumblerNoWrap + + Tumbler { + model: 5 + currentIndex: 2 + wrap: false + visibleItemCount: 3 + } + } + + Component { + id: currentIndexTumblerNoWrapReversedOrder + + Tumbler { + model: 5 + wrap: false + currentIndex: 2 + visibleItemCount: 3 + } + } + + Component { + id: negativeCurrentIndexTumblerNoWrap + + Tumbler { + model: 5 + wrap: false + currentIndex: -1 + visibleItemCount: 3 + } + } + + Component { + id: currentIndexTooLargeTumbler + + Tumbler { + objectName: "currentIndexTooLargeTumbler" + model: 10 + currentIndex: 10 + } + } + + + function test_currentIndexAtCreation_data() { + return [ + { tag: "wrap: implicit, expected currentIndex: 2", currentIndex: 2, wrap: true, component: currentIndexTumbler }, + { tag: "wrap: false, expected currentIndex: 2", currentIndex: 2, wrap: false, component: currentIndexTumblerNoWrap }, + // Order of property assignments shouldn't matter + { tag: "wrap: false, expected currentIndex: 2, reversed property assignment order", + currentIndex: 2, wrap: false, component: currentIndexTumblerNoWrapReversedOrder }, + { tag: "wrap: false, expected currentIndex: 0", currentIndex: 0, wrap: false, component: negativeCurrentIndexTumblerNoWrap }, + { tag: "wrap: implicit, expected currentIndex: 0", currentIndex: 0, wrap: true, component: currentIndexTooLargeTumbler } + ] + } + + function test_currentIndexAtCreation(data) { + // Test setting currentIndex at creation time + tumbler = createTemporaryObject(data.component, testCase); + verify(tumbler); + // A "statically declared" currentIndex will be pending until the count has changed, + // which happens when the model is set, which happens on the TumblerView's next polish. + tryCompare(tumbler, "currentIndex", data.currentIndex); + + tumblerView = findView(tumbler); + tryVerify(function() { return tumblerView.currentItem }); + compare(tumblerView.currentIndex, data.currentIndex); + compare(tumblerView.currentItem.text, data.currentIndex.toString()); + + if (data.wrap) { + tryCompare(tumblerView, "offset", data.currentIndex > 0 ? tumblerView.count - data.currentIndex : 0); + } else { + tryCompare(tumblerView, "contentY", tumblerDelegateHeight * data.currentIndex - tumblerView.preferredHighlightBegin); + } + } + + function test_keyboardNavigation() { + createTumbler(); + + tumbler.model = 5; + tumbler.forceActiveFocus(); + tumblerView.highlightMoveDuration = 0; + + // Navigate upwards through entire wheel. + for (var j = 0; j < tumbler.count - 1; ++j) { + keyClick(Qt.Key_Up, Qt.NoModifier); + tryCompare(tumblerView, "offset", j + 1); + compare(tumbler.currentIndex, tumbler.count - 1 - j); + } + + keyClick(Qt.Key_Up, Qt.NoModifier); + tryCompare(tumblerView, "offset", 0); + compare(tumbler.currentIndex, 0); + + // Navigate downwards through entire wheel. + for (j = 0; j < tumbler.count - 1; ++j) { + keyClick(Qt.Key_Down, Qt.NoModifier); + tryCompare(tumblerView, "offset", tumbler.count - 1 - j); + compare(tumbler.currentIndex, j + 1); + } + + keyClick(Qt.Key_Down, Qt.NoModifier); + tryCompare(tumblerView, "offset", 0); + compare(tumbler.currentIndex, 0); + } + + function test_itemsCorrectlyPositioned() { + createTumbler(); + + tumbler.model = 4; + tumbler.height = 120; + compare(tumblerDelegateHeight, 40); + checkItemSizes(); + + wait(tumblerView.highlightMoveDuration); + var firstItemCenterPos = itemCenterPos(1); + var firstItem = tumblerView.itemAt(firstItemCenterPos.x, firstItemCenterPos.y); + var actualPos = testCase.mapFromItem(firstItem, 0, 0); + compare(actualPos.x, tumbler.leftPadding); + compare(actualPos.y, tumbler.topPadding + 40); + + tumbler.forceActiveFocus(); + keyClick(Qt.Key_Down); + tryCompare(tumblerView, "offset", 3.0); + tryCompare(tumbler, "moving", false); + firstItemCenterPos = itemCenterPos(0); + firstItem = tumblerView.itemAt(firstItemCenterPos.x, firstItemCenterPos.y); + verify(firstItem); + // Test QTBUG-40298. + actualPos = testCase.mapFromItem(firstItem, 0, 0); + fuzzyCompare(actualPos.x, tumbler.leftPadding, 0.0001); + fuzzyCompare(actualPos.y, tumbler.topPadding, 0.0001); + + var secondItemCenterPos = itemCenterPos(1); + var secondItem = tumblerView.itemAt(secondItemCenterPos.x, secondItemCenterPos.y); + verify(secondItem); + verify(firstItem.y < secondItem.y); + + var thirdItemCenterPos = itemCenterPos(2); + var thirdItem = tumblerView.itemAt(thirdItemCenterPos.x, thirdItemCenterPos.y); + verify(thirdItem); + verify(firstItem.y < thirdItem.y); + verify(secondItem.y < thirdItem.y); + } + + function test_focusPastTumbler() { + tumbler = createTemporaryObject(tumblerComponent, testCase); + verify(tumbler); + + var mouseArea = createTemporaryQmlObject( + "import QtQuick; TextInput { activeFocusOnTab: true; width: 50; height: 50 }", testCase, ""); + + tumbler.forceActiveFocus(); + verify(tumbler.activeFocus); + + keyClick(Qt.Key_Tab); + verify(!tumbler.activeFocus); + verify(mouseArea.activeFocus); + } + + function test_datePicker() { + var component = Qt.createComponent("TumblerDatePicker.qml"); + compare(component.status, Component.Ready, component.errorString()); + tumbler = createTemporaryObject(component, testCase); + // Should not be any warnings. + + tryCompare(tumbler.dayTumbler, "currentIndex", 0); + compare(tumbler.dayTumbler.count, 31); + compare(tumbler.monthTumbler.currentIndex, 0); + compare(tumbler.monthTumbler.count, 12); + compare(tumbler.yearTumbler.currentIndex, 0); + tryCompare(tumbler.yearTumbler, "count", 100); + + verify(findView(tumbler.dayTumbler).children.length >= tumbler.dayTumbler.visibleItemCount); + verify(findView(tumbler.monthTumbler).children.length >= tumbler.monthTumbler.visibleItemCount); + // TODO: do this properly somehow + wait(100); + verify(findView(tumbler.yearTumbler).children.length >= tumbler.yearTumbler.visibleItemCount); + + // March. + tumbler.monthTumbler.currentIndex = 2; + tryCompare(tumbler.monthTumbler, "currentIndex", 2); + + // 30th of March. + tumbler.dayTumbler.currentIndex = 29; + tryCompare(tumbler.dayTumbler, "currentIndex", 29); + + // February. + tumbler.monthTumbler.currentIndex = 1; + tryCompare(tumbler.monthTumbler, "currentIndex", 1); + tryCompare(tumbler.dayTumbler, "currentIndex", 27); + } + + Component { + id: timePickerComponent + + Row { + property alias minuteTumbler: minuteTumbler + property alias amPmTumbler: amPmTumbler + + Tumbler { + id: minuteTumbler + currentIndex: 6 + model: 60 + width: 50 + height: 150 + } + + Tumbler { + id: amPmTumbler + model: ["AM", "PM"] + width: 50 + height: 150 + contentItem: ListView { + anchors.fill: parent + model: amPmTumbler.model + delegate: amPmTumbler.delegate + } + } + } + } + + function test_listViewTimePicker() { + var root = createTemporaryObject(timePickerComponent, testCase); + verify(root); + + mouseDrag(root.minuteTumbler, root.minuteTumbler.width / 2, root.minuteTumbler.height / 2, 0, 50); + // Shouldn't crash. + mouseDrag(root.amPmTumbler, root.amPmTumbler.width / 2, root.amPmTumbler.height / 2, 0, 50); + } + + function test_displacement_data() { + var data = [ + // At 0 offset, the first item is current. + { count: 6, index: 0, offset: 0, expectedDisplacement: 0 }, + { count: 6, index: 1, offset: 0, expectedDisplacement: -1 }, + { count: 6, index: 5, offset: 0, expectedDisplacement: 1 }, + // When we start to move the first item down, the second item above it starts to become current. + { count: 6, index: 0, offset: 0.25, expectedDisplacement: -0.25 }, + { count: 6, index: 1, offset: 0.25, expectedDisplacement: -1.25 }, + { count: 6, index: 5, offset: 0.25, expectedDisplacement: 0.75 }, + { count: 6, index: 0, offset: 0.5, expectedDisplacement: -0.5 }, + { count: 6, index: 1, offset: 0.5, expectedDisplacement: -1.5 }, + { count: 6, index: 5, offset: 0.5, expectedDisplacement: 0.5 }, + // By this stage, the delegate at index 1 is destroyed, so we can't test its displacement. + { count: 6, index: 0, offset: 0.75, expectedDisplacement: -0.75 }, + { count: 6, index: 5, offset: 0.75, expectedDisplacement: 0.25 }, + { count: 6, index: 0, offset: 4.75, expectedDisplacement: 1.25 }, + { count: 6, index: 1, offset: 4.75, expectedDisplacement: 0.25 }, + { count: 6, index: 0, offset: 4.5, expectedDisplacement: 1.5 }, + { count: 6, index: 1, offset: 4.5, expectedDisplacement: 0.5 }, + { count: 6, index: 0, offset: 4.25, expectedDisplacement: 1.75 }, + { count: 6, index: 1, offset: 4.25, expectedDisplacement: 0.75 }, + // count == visibleItemCount + { count: 3, index: 0, offset: 0, expectedDisplacement: 0 }, + { count: 3, index: 1, offset: 0, expectedDisplacement: -1 }, + { count: 3, index: 2, offset: 0, expectedDisplacement: 1 }, + // count < visibleItemCount + { count: 2, index: 0, offset: 0, expectedDisplacement: 0 }, + { count: 2, index: 1, offset: 0, expectedDisplacement: 1 }, + // count == 1 + { count: 1, index: 0, offset: 0, expectedDisplacement: 0 } + ]; + for (var i = 0; i < data.length; ++i) { + var row = data[i]; + row.tag = "delegate" + row.index + " offset=" + row.offset + " expectedDisplacement=" + row.expectedDisplacement; + } + return data; + } + + property Component displacementDelegate: Text { + objectName: "delegate" + index + text: modelData + opacity: 0.2 + Math.max(0, 1 - Math.abs(Tumbler.displacement)) * 0.8 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + + Text { + text: parent.displacement.toFixed(2) + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + } + + property real displacement: Tumbler.displacement + } + + function test_displacement(data) { + createTumbler(); + + // TODO: test setting these in the opposite order (delegate after model + // doesn't seem to cause a change in delegates in PathView) + tumbler.wrap = true; + tumbler.delegate = displacementDelegate; + tumbler.model = data.count; + compare(tumbler.count, data.count); + + var delegate = findChild(tumblerView, "delegate" + data.index); + verify(delegate); + + tumblerView.offset = data.offset; + compare(delegate.displacement, data.expectedDisplacement); + + // test displacement after adding and removing items + } + + function test_wrap() { + createTumbler(); + + tumbler.model = 5; + compare(tumbler.count, 5); + + tumbler.currentIndex = 2; + compare(tumblerView.currentIndex, 2); + + tumbler.wrap = false; + tumblerView = findView(tumbler); + compare(tumbler.count, 5); + compare(tumbler.currentIndex, 2); + // Tumbler's count hasn't changed (the model hasn't changed), + // but the new view needs time to instantiate its items. + tryCompare(tumblerView, "count", 5); + compare(tumblerView.currentIndex, 2); + } + + Component { + id: twoItemTumbler + + Tumbler { + model: 2 + } + } + + Component { + id: tenItemTumbler + + Tumbler { + model: 10 + } + } + + function test_countWrap() { + tumbler = createTemporaryObject(tumblerComponent, testCase); + verify(tumbler); + + // Check that a count that is less than visibleItemCount results in wrap being set to false. + verify(2 < tumbler.visibleItemCount); + tumbler.model = 2; + compare(tumbler.count, 2); + compare(tumbler.wrap, false); + } + + function test_explicitlyNonwrapping() { + // Check that explicitly setting wrap to false works even when it was implicitly false. + var explicitlyNonWrapping = createTemporaryObject(twoItemTumbler, testCase); + verify(explicitlyNonWrapping); + tryCompare(explicitlyNonWrapping, "wrap", false); + + explicitlyNonWrapping.wrap = false; + // wrap shouldn't be set to true now that there are more items than there are visible ones. + verify(10 > explicitlyNonWrapping.visibleItemCount); + explicitlyNonWrapping.model = 10; + compare(explicitlyNonWrapping.wrap, false); + + // Test resetting wrap back to the default behavior. + explicitlyNonWrapping.wrap = undefined; + compare(explicitlyNonWrapping.wrap, true); + } + + function test_explicitlyWrapping() { + // Check that explicitly setting wrap to true works even when it was implicitly true. + var explicitlyWrapping = createTemporaryObject(tenItemTumbler, testCase); + verify(explicitlyWrapping); + compare(explicitlyWrapping.wrap, true); + + explicitlyWrapping.wrap = true; + // wrap shouldn't be set to false now that there are more items than there are visible ones. + explicitlyWrapping.model = 2; + compare(explicitlyWrapping.wrap, true); + + // Test resetting wrap back to the default behavior. + explicitlyWrapping.wrap = undefined; + compare(explicitlyWrapping.wrap, false); + } + + Component { + id: customListViewTumblerComponent + + Tumbler { + id: listViewTumbler + + contentItem: ListView { + anchors.fill: parent + model: listViewTumbler.model + delegate: listViewTumbler.delegate + + snapMode: ListView.SnapToItem + highlightRangeMode: ListView.StrictlyEnforceRange + preferredHighlightBegin: height / 2 - (height / listViewTumbler.visibleItemCount / 2) + preferredHighlightEnd: height / 2 + (height / listViewTumbler.visibleItemCount / 2) + clip: true + } + } + } + + Component { + id: customPathViewTumblerComponent + + Tumbler { + id: pathViewTumbler + + contentItem: PathView { + id: pathView + model: pathViewTumbler.model + delegate: pathViewTumbler.delegate + clip: true + pathItemCount: pathViewTumbler.visibleItemCount + 1 + preferredHighlightBegin: 0.5 + preferredHighlightEnd: 0.5 + dragMargin: width / 2 + + path: Path { + startX: pathView.width / 2 + startY: -pathView.delegateHeight / 2 + PathLine { + x: pathView.width / 2 + y: pathView.pathItemCount * pathView.delegateHeight - pathView.delegateHeight / 2 + } + } + + property real delegateHeight: pathViewTumbler.availableHeight / pathViewTumbler.visibleItemCount + } + } + } + + function test_customContentItemAtConstruction_data() { + return [ + { tag: "ListView", component: customListViewTumblerComponent }, + { tag: "PathView", component: customPathViewTumblerComponent } + ]; + } + + function test_customContentItemAtConstruction(data) { + var tumbler = createTemporaryObject(data.component, testCase); + // Shouldn't assert. + + tumbler.model = 5; + compare(tumbler.count, 5); + + tumbler.currentIndex = 2; + var tumblerView = findView(tumbler); + compare(tumblerView.currentIndex, 2); + + tumblerView.incrementCurrentIndex(); + compare(tumblerView.currentIndex, 3); + compare(tumbler.currentIndex, 3); + + // Shouldn't have any affect. + tumbler.wrap = false; + compare(tumbler.count, 5); + compare(tumblerView.currentIndex, 3); + compare(tumbler.currentIndex, 3); + } + + function findFirstDelegateWithText(view, text) { + var delegate = null; + var contentItem = view.hasOwnProperty("contentItem") ? view.contentItem : view; + for (var i = 0; i < contentItem.children.length && !delegate; ++i) { + var child = contentItem.children[i]; + if (child.hasOwnProperty("text") && child.text === text) + delegate = child; + } + return delegate; + } + + function test_customContentItemAfterConstruction_data() { + return [ + { tag: "ListView", componentPath: "TumblerListView.qml" }, + { tag: "PathView", componentPath: "TumblerPathView.qml" } + ]; + } + + function test_customContentItemAfterConstruction(data) { + createTumbler(); + + tumbler.model = 5; + compare(tumbler.count, 5); + + tumbler.currentIndex = 2; + compare(tumblerView.currentIndex, 2); + + var contentItemComponent = Qt.createComponent(data.componentPath); + compare(contentItemComponent.status, Component.Ready); + + var customContentItem = createTemporaryObject(contentItemComponent, tumbler); + tumbler.contentItem = customContentItem; + compare(tumbler.count, 5); + tumblerView = findView(tumbler); + compare(tumblerView.currentIndex, 2); + + var delegate = findFirstDelegateWithText(tumblerView, "Custom2"); + verify(delegate); + compare(delegate.height, defaultImplicitDelegateHeight); + tryCompare(delegate.Tumbler, "displacement", 0); + + tumblerView.incrementCurrentIndex(); + compare(tumblerView.currentIndex, 3); + compare(tumbler.currentIndex, 3); + } + + function test_displacementListView_data() { + var offset = defaultListViewTumblerOffset; + + var data = [ + // At 0 contentY, the first item is current. + { contentY: offset, expectedDisplacements: [ + { index: 0, displacement: 0 }, + { index: 1, displacement: -1 }, + { index: 2, displacement: -2 } ] + }, + // When we start to move the first item down, the second item above it starts to become current. + { contentY: offset + defaultImplicitDelegateHeight * 0.25, expectedDisplacements: [ + { index: 0, displacement: 0.25 }, + { index: 1, displacement: -0.75 }, + { index: 2, displacement: -1.75 } ] + }, + { contentY: offset + defaultImplicitDelegateHeight * 0.5, expectedDisplacements: [ + { index: 0, displacement: 0.5 }, + { index: 1, displacement: -0.5 }, + { index: 2, displacement: -1.5 } ] + }, + { contentY: offset + defaultImplicitDelegateHeight * 0.75, expectedDisplacements: [ + { index: 0, displacement: 0.75 }, + { index: 1, displacement: -0.25 } ] + }, + { contentY: offset + defaultImplicitDelegateHeight * 3.5, expectedDisplacements: [ + { index: 3, displacement: 0.5 }, + { index: 4, displacement: -0.5 } ] + } + ]; + for (var i = 0; i < data.length; ++i) { + var row = data[i]; + row.tag = "contentY=" + row.contentY; + } + return data; + } + + function test_displacementListView(data) { + createTumbler(); + + tumbler.wrap = false; + tumbler.delegate = displacementDelegate; + tumbler.model = 5; + compare(tumbler.count, 5); + // Ensure assumptions about the tumbler used in our data() function are correct. + tumblerView = findView(tumbler); + compare(tumblerView.contentY, -defaultImplicitDelegateHeight); + var delegateCount = 0; + var listView = tumblerView; + var listViewContentItem = tumblerView.contentItem; + + // We use the mouse instead of setting contentY directly, otherwise the + // items snap back into place. This doesn't seem to be an issue for + // PathView for some reason. + // + // I tried lots of things to get this test to work with small changes + // in ListView's contentY (to match the tests for a PathView-based Tumbler), but they didn't work: + // + // - Pressing once and then directly moving the mouse to the correct location + // - Pressing once and interpolating the mouse position to the correct location + // - Pressing once and doing some dragging up and down to trigger the + // overThreshold of QQuickFlickable + // + // Even after the last item above, QQuickFlickable wouldn't consider it a drag. + // It seems that overThreshold is set too late, and because the drag distance is quite small + // to begin with, nothing changes (the displacement was always very close to 0 in the end). + + // Ensure that we at least cover the distance required to reach the desired contentY. + var distanceToReachContentY = data.contentY - defaultListViewTumblerOffset; + var distance = Math.abs(distanceToReachContentY) + tumbler.height / 2; + // If distanceToReachContentY is 0, we're testing 0 displacement, so we don't need to do anything. + if (distanceToReachContentY != 0) { + mousePress(tumbler, tumblerXCenter(), tumblerYCenter()); + + var dragDirection = distanceToReachContentY > 0 ? -1 : 1; + for (var i = 0; i < distance && Math.floor(listView.contentY) !== Math.floor(data.contentY); ++i) { + mouseMove(tumbler, tumblerXCenter(), tumblerYCenter() + i * dragDirection); + wait(1); // because Flickable pays attention to velocity, we need some time between movements (qtdeclarative ebf07c3) + } + } + + for (var i = 0; i < data.expectedDisplacements.length; ++i) { + var delegate = findChild(listViewContentItem, "delegate" + data.expectedDisplacements[i].index); + verify(delegate); + compare(delegate.height, defaultImplicitDelegateHeight); + // Due to the way we must perform this test, we can't expect high precision. + var expectedDisplacement = data.expectedDisplacements[i].displacement; + fuzzyCompare(delegate.displacement, expectedDisplacement, 0.1, + "Delegate of ListView-based Tumbler at index " + data.expectedDisplacements[i].index + + " has displacement of " + delegate.displacement + " when it should be " + expectedDisplacement); + } + + if (distanceToReachContentY != 0) + mouseRelease(tumbler, tumblerXCenter(), itemCenterPos(1) + (data.contentY - defaultListViewTumblerOffset), Qt.LeftButton); + } + + function test_listViewFlickAboveBounds_data() { + // Tests that flicking above the bounds when already at the top of the + // tumbler doesn't result in an incorrect displacement. + var data = []; + // Less than two items doesn't make sense. The default visibleItemCount + // is 3, so we test a bit more than double that. + for (var i = 2; i <= 7; ++i) { + data.push({ tag: i + " items", model: i }); + } + return data; + } + + function test_listViewFlickAboveBounds(data) { + createTumbler(); + + tumbler.wrap = false; + tumbler.delegate = displacementDelegate; + tumbler.model = data.model; + tumblerView = findView(tumbler); + + mousePress(tumbler, tumblerXCenter(), tumblerYCenter()); + + // Ensure it's stationary. + var listView = tumblerView; + compare(listView.contentY, defaultListViewTumblerOffset); + + // We could just move up until the contentY changed, but this is safer. + var distance = tumbler.height; + var changed = false; + + for (var i = 0; i < distance && !changed; ++i) { + mouseMove(tumbler, tumblerXCenter(), tumblerYCenter() + i, 10); + + // Don't test until the contentY has actually changed. + if (Math.abs(listView.contentY) - listView.preferredHighlightBegin > 0.01) { + + for (var delegateIndex = 0; delegateIndex < Math.min(tumbler.count, tumbler.visibleItemCount); ++delegateIndex) { + var delegate = findChild(listView.contentItem, "delegate" + delegateIndex); + verify(delegate); + + verify(delegate.displacement <= -delegateIndex, "Delegate at index " + delegateIndex + " has a displacement of " + + delegate.displacement + " when it should be less than or equal to " + -delegateIndex); + verify(delegate.displacement > -delegateIndex - 0.1, "Delegate at index 0 has a displacement of " + + delegate.displacement + " when it should be greater than ~ " + -delegateIndex - 0.1); + } + + changed = true; + } + } + + // Sanity check that something was actually tested. + verify(changed); + + mouseRelease(tumbler, tumblerXCenter(), tumbler.topPadding); + } + + property Component objectNameDelegate: Text { + objectName: "delegate" + index + text: modelData + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + function test_visibleItemCount_data() { + var data = [ + // e.g. {0: 2} = {delegate index: y pos / delegate height} + // Skip item at index 3, because it's out of view. + { model: 6, visibleItemCount: 5, expectedYPositions: {0: 2, 1: 3, 2: 4, 4: 0} }, + { model: 5, visibleItemCount: 3, expectedYPositions: {0: 1, 1: 2, 4: 0} }, + // Takes up the whole view. + { model: 2, visibleItemCount: 1, expectedYPositions: {0: 0} }, + ]; + + for (var i = 0; i < data.length; ++i) { + data[i].tag = "items=" + data[i].model + ", visibleItemCount=" + data[i].visibleItemCount; + } + return data; + } + + function test_visibleItemCount(data) { + createTumbler(); + + tumbler.delegate = objectNameDelegate; + tumbler.visibleItemCount = data.visibleItemCount; + + tumbler.model = data.model; + compare(tumbler.count, data.model); + + for (var delegateIndex = 0; delegateIndex < data.visibleItemCount; ++delegateIndex) { + if (data.expectedYPositions.hasOwnProperty(delegateIndex)) { + var delegate = findChild(tumblerView, "delegate" + delegateIndex); + verify(delegate, "Delegate found at index " + delegateIndex); + var expectedYPos = data.expectedYPositions[delegateIndex] * tumblerDelegateHeight; + compare(delegate.mapToItem(tumbler.contentItem, 0, 0).y, expectedYPos); + } + } + } + + property Component wrongDelegateTypeComponent: QtObject { + property real displacement: Tumbler.displacement + } + + property Component noParentDelegateComponent: Item { + property real displacement: Tumbler.displacement + } + + function test_attachedProperties() { + tumbler = createTemporaryObject(tumblerComponent, testCase); + verify(tumbler); + + // TODO: crashes somewhere in QML's guts +// tumbler.model = 5; +// tumbler.delegate = wrongDelegateTypeComponent; +// ignoreWarning("Attached properties of Tumbler must be accessed from within a delegate item"); +// // Cause displacement to be changed. The warning isn't triggered if we don't do this. +// tumbler.contentItem.offset += 1; + + ignoreWarning("Tumbler: attached properties must be accessed through a delegate item that has a parent"); + createTemporaryObject(noParentDelegateComponent, null); + + ignoreWarning("Tumbler: attempting to access attached property on item without an \"index\" property"); + var object = createTemporaryObject(noParentDelegateComponent, testCase); + verify(object); + } + + property Component paddingDelegate: Text { + objectName: "delegate" + index + text: modelData + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + + Rectangle { + anchors.fill: parent + color: "transparent" + border.color: "red" + border.width: 1 + } + } + + function test_padding_data() { + var data = []; + + data.push({ padding: 0 }); + data.push({ padding: 10 }); + data.push({ left: 10, top: 10 }); + data.push({ right: 10, bottom: 10 }); + + for (var i = 0; i < data.length; ++i) { + var tag = ""; + + if (data[i].padding !== undefined) + tag += "padding: " + data[i].padding + " "; + if (data[i].left !== undefined) + tag += "left: " + data[i].left + " "; + if (data[i].right !== undefined) + tag += "right: " + data[i].right + " "; + if (data[i].top !== undefined) + tag += "top: " + data[i].top + " "; + if (data[i].bottom !== undefined) + tag += "bottom: " + data[i].bottom + " "; + tag = tag.slice(0, -1); + + data[i].tag = tag; + } + + return data; + } + + function test_padding(data) { + createTumbler(); + + tumbler.delegate = paddingDelegate; + tumbler.model = 5; + compare(tumbler.padding, 0); + compare(tumbler.leftPadding, 0); + compare(tumbler.rightPadding, 0); + compare(tumbler.topPadding, 0); + compare(tumbler.bottomPadding, 0); + compare(tumbler.contentItem.x, 0); + compare(tumbler.contentItem.y, 0); + + if (data.padding !== undefined) + tumbler.padding = data.padding; + if (data.left !== undefined) + tumbler.leftPadding = data.left; + if (data.right !== undefined) + tumbler.rightPadding = data.right; + if (data.top !== undefined) + tumbler.topPadding = data.top; + if (data.bottom !== undefined) + tumbler.bottomPadding = data.bottom; + + compare(tumbler.availableWidth, tumbler.implicitWidth - tumbler.leftPadding - tumbler.rightPadding); + compare(tumbler.availableHeight, tumbler.implicitHeight - tumbler.topPadding - tumbler.bottomPadding); + compare(tumbler.contentItem.x, tumbler.leftPadding); + compare(tumbler.contentItem.y, tumbler.topPadding); + + var pathView = tumbler.contentItem; + var expectedDelegateHeight = tumbler.availableHeight / tumbler.visibleItemCount; + var itemIndicesInVisualOrder = [4, 0, 1]; + for (var i = 0; i < itemIndicesInVisualOrder.length; ++i) { + var delegate = findChild(pathView, "delegate" + itemIndicesInVisualOrder[i]); + verify(delegate, "Couldn't find delegate at index " + itemIndicesInVisualOrder[i] + + " (iteration " + i + " out of " + (pathView.children.length - 1) + ")"); + + compare(delegate.width, tumbler.availableWidth); + compare(delegate.height, expectedDelegateHeight); + + var expectedY = tumbler.topPadding + i * expectedDelegateHeight; + var mappedPos = delegate.mapToItem(null, delegate.width / 2, 0); + fuzzyCompare(mappedPos.y, expectedY, 0.5, + "Tumbler's PathView delegate at index " + itemIndicesInVisualOrder[i] + + " should have a y pos of " + expectedY + ", but it's actually " + mappedPos.y.toFixed(20)); + + var expectedX = tumbler.leftPadding; + compare(delegate.mapToItem(null, 0, 0).x, expectedX, + "Tumbler's PathView delegate at index " + itemIndicesInVisualOrder[i] + + " should have a x pos of " + expectedX + ", but it's actually " + mappedPos.x.toFixed(20)); + } + + // Force new items to be created, as there was a bug where the path was correct until this happened. + compare(tumblerView.offset, 0); + ++tumbler.currentIndex; + tryCompare(tumblerView, "offset", 4, tumblerView.highlightMoveDuration * 2); + } + + function test_moving_data() { + return [ + { tag: "wrap:true", wrap: true }, + { tag: "wrap:false", wrap: false } + ] + } + + function test_moving(data) { + createTumbler({wrap: data.wrap, model: 5}) + compare(tumbler.wrap, data.wrap) + compare(tumbler.moving, false) + + waitForRendering(tumbler) + + mousePress(tumbler, tumbler.width / 2, tumbler.height / 2, Qt.LeftButton) + compare(tumbler.moving, false) + + for (var y = tumbler.height / 2; y >= tumbler.height / 4; y -= 10) + mouseMove(tumbler, tumbler.width / 2, y, 1) + compare(tumbler.moving, true) + + mouseRelease(tumbler, tumbler.width / 2, tumbler.height / 4, Qt.LeftButton) + compare(tumbler.moving, true) + tryCompare(tumbler, "moving", false) + } + + Component { + id: qtbug61374Component + + Row { + property alias tumbler: tumbler + property alias label: label + + Component.onCompleted: { + tumbler.currentIndex = 2 + } + + Tumbler { + id: tumbler + model: 5 + // ... + } + + Label { + id: label + text: tumbler.currentItem.text + } + } + } + + function test_qtbug61374() { + var row = createTemporaryObject(qtbug61374Component, testCase); + verify(row); + + var tumbler = row.tumbler; + tryCompare(tumbler, "currentIndex", 2); + + tumblerView = findView(tumbler); + + var label = row.label; + compare(label.text, "2"); + } + + function test_positionViewAtIndex_data() { + return [ + // Should be 20, 21, ... but there is a documented limitation for this in positionViewAtIndex()'s docs. + { tag: "wrap=true, mode=Beginning", wrap: true, mode: Tumbler.Beginning, expectedVisibleIndices: [21, 22, 23, 24, 25] }, + { tag: "wrap=true, mode=Center", wrap: true, mode: Tumbler.Center, expectedVisibleIndices: [18, 19, 20, 21, 22] }, + { tag: "wrap=true, mode=End", wrap: true, mode: Tumbler.End, expectedVisibleIndices: [16, 17, 18, 19, 20] }, + // Same as Beginning; should start at 20. + { tag: "wrap=true, mode=Contain", wrap: true, mode: Tumbler.Contain, expectedVisibleIndices: [21, 22, 23, 24, 25] }, + { tag: "wrap=true, mode=SnapPosition", wrap: true, mode: Tumbler.SnapPosition, expectedVisibleIndices: [18, 19, 20, 21, 22] }, + { tag: "wrap=false, mode=Beginning", wrap: false, mode: Tumbler.Beginning, expectedVisibleIndices: [20, 21, 22, 23, 24] }, + { tag: "wrap=false, mode=Center", wrap: false, mode: Tumbler.Center, expectedVisibleIndices: [18, 19, 20, 21, 22] }, + { tag: "wrap=false, mode=End", wrap: false, mode: Tumbler.End, expectedVisibleIndices: [16, 17, 18, 19, 20] }, + { tag: "wrap=false, mode=Visible", wrap: false, mode: Tumbler.Visible, expectedVisibleIndices: [16, 17, 18, 19, 20] }, + { tag: "wrap=false, mode=Contain", wrap: false, mode: Tumbler.Contain, expectedVisibleIndices: [16, 17, 18, 19, 20] }, + { tag: "wrap=false, mode=SnapPosition", wrap: false, mode: Tumbler.SnapPosition, expectedVisibleIndices: [18, 19, 20, 21, 22] } + ] + } + + function test_positionViewAtIndex(data) { + createTumbler({ wrap: data.wrap, model: 40, visibleItemCount: 5 }) + compare(tumbler.wrap, data.wrap) + + waitForRendering(tumbler) + + tumbler.positionViewAtIndex(20, data.mode) + tryCompare(tumbler, "moving", false) + + compare(tumbler.visibleItemCount, 5) + for (var i = 0; i < 5; ++i) { + // Find the item through its text, as that's easier than child/itemAt(). + var text = data.expectedVisibleIndices[i].toString() + var item = findDelegateWithText(tumblerView, text) + verify(item, "found no item with text \"" + text + "\"") + compare(item.text, data.expectedVisibleIndices[i].toString()) + + // Ensure that it's at the position we expect. + var expectedPos = itemTopLeftPos(i) + var actualPos = testCase.mapFromItem(item, 0, 0) + compare(actualPos.x, expectedPos.x, "expected delegate with text " + item.text + + " to have an x pos of " + expectedPos.x + " but it was " + actualPos.x) + compare(actualPos.y, expectedPos.y, "expected delegate with text " + item.text + + " to have an y pos of " + expectedPos.y + " but it was " + actualPos.y) + } + } + + Component { + id: setCurrentIndexOnImperativeModelChangeComponent + + Tumbler { + onModelChanged: currentIndex = model - 2 + } + } + + function test_setCurrentIndexOnImperativeModelChange() { + var tumbler = createTemporaryObject(setCurrentIndexOnImperativeModelChangeComponent, testCase); + verify(tumbler); + + tumbler.model = 4 + compare(tumbler.count, 4); + tumblerView = findView(tumbler); + tryCompare(tumblerView, "count", 4); + + // 4 - 2 = 2 + compare(tumbler.currentIndex, 2); + + ++tumbler.model; + compare(tumbler.count, 5); + compare(tumbler.wrap, true); + tumblerView = findView(tumbler); + tryCompare(tumblerView, "count", 5); + // 5 - 2 = 3 + compare(tumbler.currentIndex, 3); + } + + Component { + id: setCurrentIndexOnDeclarativeModelChangeComponent + + Item { + property alias tumbler: tumbler + + property int setting: 4 + + Tumbler { + id: tumbler + model: setting + onModelChanged: currentIndex = model - 2 + } + } + } + + function test_setCurrentIndexOnDeclarativeModelChange() { + var root = createTemporaryObject(setCurrentIndexOnDeclarativeModelChangeComponent, testCase); + verify(root); + + var tumbler = root.tumbler; + compare(tumbler.count, 4); + compare(tumbler.wrap, false); + tumblerView = findView(tumbler); + tryCompare(tumblerView, "count", 4); + // 4 - 2 = 2 + compare(tumbler.currentIndex, 2); + + ++root.setting; + compare(tumbler.count, 5); + compare(tumbler.wrap, true); + tumblerView = findView(tumbler); + tryCompare(tumblerView, "count", 5); + // 5 - 2 = 3 + compare(tumbler.currentIndex, 3); + } + + function test_displacementAfterResizing() { + createTumbler({ + width: 200, + wrap: false, + delegate: displacementDelegate, + model: 30, + visibleItemCount: 7, + currentIndex: 15 + }) + + var delegate = findChild(tumblerView, "delegate15") + verify(delegate) + + tryCompare(delegate, "displacement", 0) + + // Resizing the Tumbler shouldn't affect the displacement. + tumbler.height *= 1.4 + tryCompare(delegate, "displacement", 0) + } + + //QTBUG-84426 + Component { + id: initialCurrentIndexTumbler + + Tumbler { + anchors.centerIn: parent + width: 60 + height: 200 + delegate: Text {text: modelData} + model: 10 + currentIndex: 4 + } + } + + function test_initialCurrentIndex() { + var tumbler = createTemporaryObject(initialCurrentIndexTumbler, testCase, {wrap: true}); + compare(tumbler.currentIndex, 4); + tumbler = createTemporaryObject(initialCurrentIndexTumbler, testCase, {wrap: false}); + compare(tumbler.currentIndex, 4); + } +} diff --git a/tests/auto/quickcontrols2/controls/fusion/BLACKLIST b/tests/auto/quickcontrols2/controls/fusion/BLACKLIST new file mode 100644 index 0000000000..476340700a --- /dev/null +++ b/tests/auto/quickcontrols2/controls/fusion/BLACKLIST @@ -0,0 +1,5 @@ +# See qtbase/src/testlib/qtestblacklist.cpp for format + +# QTBUG-70597 +[Tumbler::test_itemsCorrectlyPositioned] +* diff --git a/tests/auto/quickcontrols2/controls/fusion/CMakeLists.txt b/tests/auto/quickcontrols2/controls/fusion/CMakeLists.txt new file mode 100644 index 0000000000..96d4fa5a85 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/fusion/CMakeLists.txt @@ -0,0 +1,28 @@ +# Generated from fusion.pro. + +##################################################################### +## tst_fusion Test: +##################################################################### + +# Collect test data +file(GLOB_RECURSE test_data_glob + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../data/tst_*) +list(APPEND test_data ${test_data_glob}) + +qt_internal_add_test(tst_fusion + GUI + QMLTEST + SOURCES + tst_fusion.cpp + DEFINES + TST_CONTROLS_DATA=\\\"${CMAKE_CURRENT_SOURCE_DIR}/../data\\\" + PUBLIC_LIBRARIES + Qt::Gui + Qt::QuickControls2 + TESTDATA ${test_data} +) + +#### Keys ignored in scope 1:.:.:fusion.pro:<TRUE>: +# OTHER_FILES = "$$PWD/../data/*.qml" +# TEMPLATE = "app" diff --git a/tests/auto/quickcontrols2/controls/fusion/dependencies.qml b/tests/auto/quickcontrols2/controls/fusion/dependencies.qml new file mode 100644 index 0000000000..f2e40e6059 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/fusion/dependencies.qml @@ -0,0 +1,6 @@ +import QtTest +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.Fusion + +TestCase { } diff --git a/tests/auto/quickcontrols2/controls/fusion/tst_fusion.cpp b/tests/auto/quickcontrols2/controls/fusion/tst_fusion.cpp new file mode 100644 index 0000000000..7528ccea2b --- /dev/null +++ b/tests/auto/quickcontrols2/controls/fusion/tst_fusion.cpp @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtQuickTest/quicktest.h> +#include <QtQuickControls2/qquickstyle.h> + +int main(int argc, char *argv[]) +{ + QTEST_SET_MAIN_SOURCE_PATH + qputenv("QML_NO_TOUCH_COMPRESSION", "1"); + QQuickStyle::setStyle("Fusion"); + return quick_test_main(argc, argv, "tst_controls::Fusion", TST_CONTROLS_DATA); +} diff --git a/tests/auto/quickcontrols2/controls/imagine/BLACKLIST b/tests/auto/quickcontrols2/controls/imagine/BLACKLIST new file mode 100644 index 0000000000..476340700a --- /dev/null +++ b/tests/auto/quickcontrols2/controls/imagine/BLACKLIST @@ -0,0 +1,5 @@ +# See qtbase/src/testlib/qtestblacklist.cpp for format + +# QTBUG-70597 +[Tumbler::test_itemsCorrectlyPositioned] +* diff --git a/tests/auto/quickcontrols2/controls/imagine/CMakeLists.txt b/tests/auto/quickcontrols2/controls/imagine/CMakeLists.txt new file mode 100644 index 0000000000..be47d59d0b --- /dev/null +++ b/tests/auto/quickcontrols2/controls/imagine/CMakeLists.txt @@ -0,0 +1,28 @@ +# Generated from imagine.pro. + +##################################################################### +## tst_imagine Test: +##################################################################### + +# Collect test data +file(GLOB_RECURSE test_data_glob + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../data/tst_*) +list(APPEND test_data ${test_data_glob}) + +qt_internal_add_test(tst_imagine + GUI + QMLTEST + SOURCES + tst_imagine.cpp + DEFINES + TST_CONTROLS_DATA=\\\"${CMAKE_CURRENT_SOURCE_DIR}/../data\\\" + PUBLIC_LIBRARIES + Qt::Gui + Qt::QuickControls2 + TESTDATA ${test_data} +) + +#### Keys ignored in scope 1:.:.:imagine.pro:<TRUE>: +# OTHER_FILES = "$$PWD/../data/*.qml" +# TEMPLATE = "app" diff --git a/tests/auto/quickcontrols2/controls/imagine/dependencies.qml b/tests/auto/quickcontrols2/controls/imagine/dependencies.qml new file mode 100644 index 0000000000..9a31141bff --- /dev/null +++ b/tests/auto/quickcontrols2/controls/imagine/dependencies.qml @@ -0,0 +1,6 @@ +import QtTest +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.Imagine + +TestCase { } diff --git a/tests/auto/quickcontrols2/controls/imagine/tst_imagine.cpp b/tests/auto/quickcontrols2/controls/imagine/tst_imagine.cpp new file mode 100644 index 0000000000..9db7ed4dc6 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/imagine/tst_imagine.cpp @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtQuickTest/quicktest.h> +#include <QtQuickControls2/qquickstyle.h> + +int main(int argc, char *argv[]) +{ + QTEST_SET_MAIN_SOURCE_PATH + qputenv("QML_NO_TOUCH_COMPRESSION", "1"); + QQuickStyle::setStyle("Imagine"); + return quick_test_main(argc, argv, "tst_controls::Imagine", TST_CONTROLS_DATA); +} diff --git a/tests/auto/quickcontrols2/controls/material/BLACKLIST b/tests/auto/quickcontrols2/controls/material/BLACKLIST new file mode 100644 index 0000000000..476340700a --- /dev/null +++ b/tests/auto/quickcontrols2/controls/material/BLACKLIST @@ -0,0 +1,5 @@ +# See qtbase/src/testlib/qtestblacklist.cpp for format + +# QTBUG-70597 +[Tumbler::test_itemsCorrectlyPositioned] +* diff --git a/tests/auto/quickcontrols2/controls/material/CMakeLists.txt b/tests/auto/quickcontrols2/controls/material/CMakeLists.txt new file mode 100644 index 0000000000..7aa6c25dfc --- /dev/null +++ b/tests/auto/quickcontrols2/controls/material/CMakeLists.txt @@ -0,0 +1,28 @@ +# Generated from material.pro. + +##################################################################### +## tst_material Test: +##################################################################### + +# Collect test data +file(GLOB_RECURSE test_data_glob + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../data/tst_*) +list(APPEND test_data ${test_data_glob}) + +qt_internal_add_test(tst_material + GUI + QMLTEST + SOURCES + tst_material.cpp + DEFINES + TST_CONTROLS_DATA=\\\"${CMAKE_CURRENT_SOURCE_DIR}/../data\\\" + PUBLIC_LIBRARIES + Qt::Gui + Qt::QuickControls2 + TESTDATA ${test_data} +) + +#### Keys ignored in scope 1:.:.:material.pro:<TRUE>: +# OTHER_FILES = "$$PWD/../data/*.qml" +# TEMPLATE = "app" diff --git a/tests/auto/quickcontrols2/controls/material/dependencies.qml b/tests/auto/quickcontrols2/controls/material/dependencies.qml new file mode 100644 index 0000000000..8bcf03013c --- /dev/null +++ b/tests/auto/quickcontrols2/controls/material/dependencies.qml @@ -0,0 +1,6 @@ +import QtTest +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.Material + +TestCase { } diff --git a/tests/auto/quickcontrols2/controls/material/tst_material.cpp b/tests/auto/quickcontrols2/controls/material/tst_material.cpp new file mode 100644 index 0000000000..ae8bb3a11e --- /dev/null +++ b/tests/auto/quickcontrols2/controls/material/tst_material.cpp @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtQuickTest/quicktest.h> +#include <QtQuickControls2/qquickstyle.h> + +int main(int argc, char *argv[]) +{ + QTEST_SET_MAIN_SOURCE_PATH + qputenv("QML_NO_TOUCH_COMPRESSION", "1"); + QQuickStyle::setStyle("Material"); + return quick_test_main(argc, argv, "tst_controls::Material", TST_CONTROLS_DATA); +} diff --git a/tests/auto/quickcontrols2/controls/universal/BLACKLIST b/tests/auto/quickcontrols2/controls/universal/BLACKLIST new file mode 100644 index 0000000000..476340700a --- /dev/null +++ b/tests/auto/quickcontrols2/controls/universal/BLACKLIST @@ -0,0 +1,5 @@ +# See qtbase/src/testlib/qtestblacklist.cpp for format + +# QTBUG-70597 +[Tumbler::test_itemsCorrectlyPositioned] +* diff --git a/tests/auto/quickcontrols2/controls/universal/CMakeLists.txt b/tests/auto/quickcontrols2/controls/universal/CMakeLists.txt new file mode 100644 index 0000000000..c009750e90 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/universal/CMakeLists.txt @@ -0,0 +1,28 @@ +# Generated from universal.pro. + +##################################################################### +## tst_universal Test: +##################################################################### + +# Collect test data +file(GLOB_RECURSE test_data_glob + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/../data/tst_*) +list(APPEND test_data ${test_data_glob}) + +qt_internal_add_test(tst_universal + GUI + QMLTEST + SOURCES + tst_universal.cpp + DEFINES + TST_CONTROLS_DATA=\\\"${CMAKE_CURRENT_SOURCE_DIR}/../data\\\" + PUBLIC_LIBRARIES + Qt::Gui + Qt::QuickControls2 + TESTDATA ${test_data} +) + +#### Keys ignored in scope 1:.:.:universal.pro:<TRUE>: +# OTHER_FILES = "$$PWD/../data/*.qml" +# TEMPLATE = "app" diff --git a/tests/auto/quickcontrols2/controls/universal/dependencies.qml b/tests/auto/quickcontrols2/controls/universal/dependencies.qml new file mode 100644 index 0000000000..8ba71c8302 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/universal/dependencies.qml @@ -0,0 +1,6 @@ +import QtTest +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.Universal + +TestCase { } diff --git a/tests/auto/quickcontrols2/controls/universal/tst_universal.cpp b/tests/auto/quickcontrols2/controls/universal/tst_universal.cpp new file mode 100644 index 0000000000..db5b560e01 --- /dev/null +++ b/tests/auto/quickcontrols2/controls/universal/tst_universal.cpp @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtQuickTest/quicktest.h> +#include <QtQuickControls2/qquickstyle.h> + +int main(int argc, char *argv[]) +{ + QTEST_SET_MAIN_SOURCE_PATH + qputenv("QML_NO_TOUCH_COMPRESSION", "1"); + QQuickStyle::setStyle("Universal"); + return quick_test_main(argc, argv, "tst_controls::Universal", TST_CONTROLS_DATA); +} |