diff options
Diffstat (limited to 'tests/auto/quickcontrols2/controls/data/tst_stackview.qml')
-rw-r--r-- | tests/auto/quickcontrols2/controls/data/tst_stackview.qml | 1433 |
1 files changed, 1433 insertions, 0 deletions
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) + } +} |