| /**************************************************************************** |
| ** |
| ** 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 2.12 |
| import QtTest 1.0 |
| import QtQuick.Controls 2.12 |
| |
| 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 2.2; 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) |
| } |
| } |