/**************************************************************************** ** ** 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) } }