/**************************************************************************** ** ** Copyright (C) 2016 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 QtTest 1.0 import QtQuick 2.1 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Private 1.0 import QtQuick.Extras 1.4 import "TestUtils.js" as TestUtils Item { id: container width: 400 height: 400 TestCase { id: testCase name: "Tests_Tumbler" when: windowShown anchors.fill: parent Component { id: tumblerComponent Tumbler {} } property Component simpleColumn: TumblerColumn { model: ListModel { Component.onCompleted: { for (var i = 0; i < 5; ++i) { append({value: i.toString()}); } } } } property Component simpleColumn6Items: TumblerColumn { model: ListModel { Component.onCompleted: { for (var i = 0; i < 6; ++i) { append({value: i.toString()}); } } } } function test_instance() { var tumbler = createTemporaryObject(tumblerComponent, container); verify(tumbler); } function columnXCenter(tumbler, columnIndex) { var columnWidth = tumbler.width / tumbler.columnCount; var halfColumnWidth = (columnWidth) / 2; return tumbler.__style.padding.left + halfColumnWidth + (columnWidth * columnIndex); } // visualItemIndex is from 0 to the amount of visible items. function itemCenterPos(tumbler, columnIndex, visualItemIndex) { var halfDelegateHeight = tumbler.__style.__delegateHeight / 2; var yCenter = tumbler.y + tumbler.__style.padding.top + halfDelegateHeight + (tumbler.__style.__delegateHeight * visualItemIndex); return Qt.point(columnXCenter(tumbler, columnIndex), yCenter); } function test_currentIndex() { var tumbler = createTemporaryObject(tumblerComponent, container); verify(tumbler); var column = simpleColumn.createObject(tumbler); compare(tumbler.addColumn(column), column); compare(tumbler.currentIndexAt(0), 0); compare(column.currentIndex, 0); waitForRendering(tumbler); var pos = Qt.point(columnXCenter(tumbler, 0), tumbler.height / 2); mouseDrag(tumbler, pos.x, pos.y, 0, -tumbler.__style.__delegateHeight, Qt.LeftButton, Qt.NoModifier, 200); compare(tumbler.currentIndexAt(0), 1); compare(column.currentIndex, 1); } function test_setCurrentIndexAt() { var tumbler = createTemporaryObject(tumblerComponent, container); verify(tumbler); var column = simpleColumn.createObject(tumbler); compare(tumbler.addColumn(column), column); compare(tumbler.currentIndexAt(0), 0); waitForRendering(tumbler); tumbler.setCurrentIndexAt(0, -1); compare(tumbler.currentIndexAt(0), 0); tumbler.setCurrentIndexAt(0, -2); compare(tumbler.currentIndexAt(0), 0); tumbler.setCurrentIndexAt(0, tumbler.getColumn(0).model.count); compare(tumbler.currentIndexAt(0), 0); tumbler.setCurrentIndexAt(0, tumbler.getColumn(0).model.count + 1); compare(tumbler.currentIndexAt(0), 0); tumbler.setCurrentIndexAt(-1, 0); for (var i = 0; i < tumbler.columnCount; ++i) { compare(tumbler.currentIndexAt(i), 0); } tumbler.setCurrentIndexAt(-1, 1); for (i = 0; i < tumbler.columnCount; ++i) { compare(tumbler.currentIndexAt(i), 0); } tumbler.setCurrentIndexAt(0, 1); tryCompare(tumbler.__viewAt(0), "offset", 4); compare(tumbler.currentIndexAt(0), 1); tumbler.setCurrentIndexAt(0, 0); waitForRendering(tumbler); tumbler.setCurrentIndexAt(0, tumbler.getColumn(0).model.count-1, 1000); tryCompare(tumbler.__viewAt(0), "offset", 1); compare(tumbler.currentIndexAt(0), tumbler.getColumn(0).model.count-1); } function test_visible() { var tumbler = createTemporaryObject(tumblerComponent, container); verify(tumbler); var column = simpleColumn.createObject(tumbler); compare(tumbler.addColumn(column), column); column = simpleColumn.createObject(tumbler); compare(tumbler.addColumn(column), column); compare(tumbler.currentIndexAt(0), 0); waitForRendering(tumbler); tumbler.getColumn(1).visible = false; verify(!tumbler.__viewAt(1).visible); // Right-most column never has a separator. compare(tumbler.__viewAt(1).parent.separator, null); tumbler.getColumn(1).visible = true; verify(tumbler.__viewAt(1).visible); tumbler.getColumn(0).visible = false; verify(!tumbler.__viewAt(0).visible); if (Settings.styleName === "Base") verify(!tumbler.__viewAt(0).parent.separator.visible); } function test_keyboardNavigation() { if (Qt.platform.os === "osx") skip("OS X doesn't allow tab focus for certain controls by default"); var tumbler = createTemporaryObject(tumblerComponent, container); verify(tumbler); var column = simpleColumn.createObject(tumbler); compare(tumbler.addColumn(column), column); column = simpleColumn.createObject(tumbler); compare(tumbler.addColumn(column), column); compare(tumbler.currentIndexAt(0), 0); waitForRendering(tumbler); // Tab through each column twice. for (var i = 0; i < 4; ++i) { var columnIndex = i % 2; // Speed it up. tumbler.__highlightMoveDuration = 50; keyClick(Qt.Key_Tab); verify(tumbler.__viewAt(columnIndex).activeFocus); // Navigate upwards through entire column. for (var j = 0; j < column.model.count - 1; ++j) { tryCompare(tumbler.__movementDelayTimer, "running", false); keyClick(Qt.Key_Up); tryCompare(tumbler.__viewAt(columnIndex), "offset", j + 1); compare(tumbler.currentIndexAt(columnIndex), column.model.count - 1 - j); } tryCompare(tumbler.__movementDelayTimer, "running", false); keyClick(Qt.Key_Up); tryCompare(tumbler.__viewAt(columnIndex), "offset", 0); compare(tumbler.currentIndexAt(columnIndex), 0); // Navigate downwards through entire column. for (j = 0; j < column.model.count - 1; ++j) { tryCompare(tumbler.__movementDelayTimer, "running", false); keyClick(Qt.Key_Down); tryCompare(tumbler.__viewAt(columnIndex), "offset", column.model.count - 1 - j); compare(tumbler.currentIndexAt(columnIndex), j + 1); } tryCompare(tumbler.__movementDelayTimer, "running", false); keyClick(Qt.Key_Down); tryCompare(tumbler.__viewAt(columnIndex), "offset", 0); compare(tumbler.currentIndexAt(columnIndex), 0); } // Shift-tab through columns. Focus is on the last column. for (i = 0; i < 4; ++i) { keyClick(Qt.Key_Tab, Qt.ShiftModifier); verify(tumbler.__viewAt(i % 2).activeFocus); } // Go back to the first column. keyClick(Qt.Key_Tab, Qt.ShiftModifier); verify(tumbler.__viewAt(0).activeFocus); compare(tumbler.__viewAt(0).offset, 0); } property Component fourItemColumn: TumblerColumn { model: 4 } property Component fourItemDelegate: Item { implicitHeight: 40 Text { text: styleData.value anchors.centerIn: parent } } function test_itemsCorrectlyPositioned() { if (Qt.platform.os === "osx") skip("OS X doesn't allow tab focus for certain controls by default"); // TODO: rewrite this test so that it tests supported usecases. // Somehow it works with the Base style. It could be rewritten to use an // equal amount of items for the model and visibleItemCount, judging from // the snippet in QTBUG-40298. if (Settings.styleName === "Flat") skip("Not a valid test case as the model count is less than the visibleItemCount"); var tumbler = createTemporaryObject(tumblerComponent, container); verify(tumbler); tumbler.height = 120; // By default, the delegate height is based on the height of the tumbler, // but it starts off at 0. compare(tumbler.__style.__delegateHeight, 0); var column = fourItemColumn.createObject(tumbler); column.delegate = fourItemDelegate; compare(tumbler.addColumn(column), column); // Now that the delegate has changed, the binding is reevaluated and we get 120 / 3. compare(tumbler.__style.__delegateHeight, 40); waitForRendering(tumbler); keyClick(Qt.Key_Tab) verify(tumbler.__viewAt(0).activeFocus); var firstItemCenterPos = itemCenterPos(tumbler, 0, 1); var firstItem = tumbler.__viewAt(0).itemAt(firstItemCenterPos.x, firstItemCenterPos.y); var actualPos = container.mapFromItem(firstItem, 0, 0); compare(actualPos.x, tumbler.__style.padding.left); compare(actualPos.y, tumbler.__style.padding.top + 40); keyClick(Qt.Key_Down); tryCompare(tumbler.__viewAt(0), "offset", 3.0); firstItemCenterPos = itemCenterPos(tumbler, 0, 0); firstItem = tumbler.__viewAt(0).itemAt(firstItemCenterPos.x, firstItemCenterPos.y); verify(firstItem); // Test QTBUG-40298. actualPos = container.mapFromItem(firstItem, 0, 0); compare(actualPos.x, tumbler.__style.padding.left); compare(actualPos.y, tumbler.__style.padding.top); var secondItemCenterPos = itemCenterPos(tumbler, 0, 1); var secondItem = tumbler.__viewAt(0).itemAt(secondItemCenterPos.x, secondItemCenterPos.y); verify(secondItem); verify(firstItem.y < secondItem.y); var thirdItemCenterPos = itemCenterPos(tumbler, 0, 2); var thirdItem = tumbler.__viewAt(0).itemAt(thirdItemCenterPos.x, thirdItemCenterPos.y); verify(thirdItem); verify(firstItem.y < thirdItem.y); verify(secondItem.y < thirdItem.y); } property Component dayOfMonthColumn: TumblerColumn { model: ListModel { Component.onCompleted: { for (var i = 0; i < 31; ++i) { append({value: i.toString()}); } } } } property Component yearColumn: TumblerColumn { model: ListModel { Component.onCompleted: { for (var i = 2000; i < 2100; ++i) { append({value: i.toString()}); } } } } function test_focusPastLastColumn() { if (Qt.platform.os === "osx") skip("OS X doesn't allow tab focus for certain controls by default"); var tumbler = createTemporaryObject(tumblerComponent, container); verify(tumbler); var column = dayOfMonthColumn.createObject(tumbler); compare(tumbler.addColumn(column), column); column = yearColumn.createObject(tumbler); compare(tumbler.addColumn(column), column); var mouseArea = createTemporaryQmlObject( "import QtQuick 2.2; MouseArea { activeFocusOnTab: true; width: 50; height: 50 }", container, ""); verify(mouseArea); keyClick(Qt.Key_Tab); verify(tumbler.__viewAt(0).activeFocus); verify(tumbler.getColumn(0).activeFocus); verify(!tumbler.__viewAt(1).activeFocus); verify(!tumbler.getColumn(1).activeFocus); keyClick(Qt.Key_Tab); verify(!tumbler.__viewAt(0).activeFocus); verify(!tumbler.getColumn(0).activeFocus); verify(tumbler.__viewAt(1).activeFocus); verify(tumbler.getColumn(1).activeFocus); keyClick(Qt.Key_Tab); verify(!tumbler.__viewAt(0).activeFocus); verify(!tumbler.getColumn(0).activeFocus); verify(!tumbler.__viewAt(1).activeFocus); verify(!tumbler.getColumn(1).activeFocus); verify(mouseArea.activeFocus); } function test_datePicker() { var component = Qt.createComponent("TumblerDatePicker.qml"); compare(component.status, Component.Ready); var tumbler = createTemporaryObject(component, container); verify(tumbler); // Should not be any warnings. // March. tumbler.setCurrentIndexAt(1, 2); compare(tumbler.currentIndexAt(1), 2); compare(tumbler.getColumn(1).currentIndex, 2); // 30th of March. tumbler.setCurrentIndexAt(0, 29); compare(tumbler.currentIndexAt(0), 29); compare(tumbler.getColumn(0).currentIndex, 29); // February. tumbler.setCurrentIndexAt(1, 1); compare(tumbler.currentIndexAt(1), 1); compare(tumbler.getColumn(1).currentIndex, 1); compare(tumbler.getColumn(0).currentIndex, 27); } property Component displacementStyle: TumblerStyle { visibleItemCount: 5 delegate: Item { objectName: "delegate" + styleData.index implicitHeight: (control.height - padding.top - padding.bottom) / visibleItemCount property real displacement: styleData.displacement Text { text: styleData.value anchors.centerIn: parent } Text { anchors.right: parent.right text: styleData.displacement.toFixed(1) } } } function test_displacement_data() { var data = [ // At 0 offset, the first item is current. { index: 0, offset: 0, expectedDisplacement: 0 }, { index: 1, offset: 0, expectedDisplacement: -1 }, { index: 5, offset: 0, expectedDisplacement: 1 }, // When we start to move the first item down, the second item above it starts to become current. { index: 0, offset: 0.25, expectedDisplacement: -0.25 }, { index: 1, offset: 0.25, expectedDisplacement: -1.25 }, { index: 5, offset: 0.25, expectedDisplacement: 0.75 }, { index: 0, offset: 0.5, expectedDisplacement: -0.5 }, { index: 1, offset: 0.5, expectedDisplacement: -1.5 }, { 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. { index: 0, offset: 0.75, expectedDisplacement: -0.75 }, { index: 5, offset: 0.75, expectedDisplacement: 0.25 }, { index: 0, offset: 4.75, expectedDisplacement: 1.25 }, { index: 1, offset: 4.75, expectedDisplacement: 0.25 }, { index: 0, offset: 4.5, expectedDisplacement: 1.5 }, { index: 1, offset: 4.5, expectedDisplacement: 0.5 }, { index: 0, offset: 4.25, expectedDisplacement: 1.75 }, { index: 1, offset: 4.25, expectedDisplacement: 0.75 } ]; 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; } function test_displacement(data) { var tumbler = createTemporaryObject(tumblerComponent, container, { style: displacementStyle }); verify(tumbler); var column = simpleColumn6Items.createObject(tumbler); compare(tumbler.addColumn(column), column); waitForRendering(tumbler); compare(tumbler.columnCount, 1); compare(tumbler.__viewAt(0).count, 6); var delegate = TestUtils.findChild(tumbler, "delegate" + data.index); verify(delegate); tumbler.__viewAt(0).offset = data.offset; compare(delegate.displacement, data.expectedDisplacement); } 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) { var tumbler = createTemporaryObject(tumblerComponent, container, { style: displacementStyle }); verify(tumbler); tumbler.__style.visibleItemCount = data.visibleItemCount; var column = simpleColumn.createObject(tumbler); column.model = data.model; compare(tumbler.addColumn(column), column); waitForRendering(tumbler); compare(tumbler.columnCount, 1); compare(tumbler.__viewAt(0).count, data.model); for (var delegateIndex = 0; delegateIndex < data.visibleItemCount; ++delegateIndex) { if (data.expectedYPositions.hasOwnProperty(delegateIndex)) { var delegate = TestUtils.findChild(tumbler, "delegate" + delegateIndex); verify(delegate, "Delegate found at index " + delegateIndex); var expectedYPos = data.expectedYPositions[delegateIndex] * tumbler.__style.__delegateHeight; compare(delegate.mapToItem(tumbler.__viewAt(0), 0, 0).y, expectedYPos); } } } } }