summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorGabriel de Dietrich <gabriel.dedietrich@theqtcompany.com>2015-01-07 12:07:28 +0100
committerGabriel de Dietrich <gabriel.dedietrich@theqtcompany.com>2015-02-13 15:27:46 +0000
commit82c7760b819f73ebc7f4ba6203fa2fc92b383236 (patch)
treef5ae4743a45c43a4a95115679d3f1690a4a6268e /tests
parent6f15c206b069ed0fcf48a285bfcc4ad636927df0 (diff)
downloadqtquickcontrols-82c7760b819f73ebc7f4ba6203fa2fc92b383236.tar.gz
Introducing TreeView
The TreeView, as currently implemented, extends the TableView by adding support for hierarchical models. In the broad sense, it remains a list view with columns, like TableView. The main architecture is based on TreeModelAdaptor, that wraps the hierarchical model. It keeps track of which items are expanded or collapsed, and also relays model changes to the view. (TreeModelAdaptor is a private type and should be considered as an implementation detail.) The TreeView only supports QAbstractItemModels for the time being, and, just like TableView, relies on roles to pass the data to the view. This also means that model columns are not supported. Selection is supported by ItemSelectionModel which exposes part of the API of QItemSelectionModel. For this, support has been added for QModelIndex and related classes. This requires importing QtQml.Models 2.2 should an actual usage of the TreeView use selection. In the same way, TreeViewStyle currently extends TableViewStyle with the relevant features, like branch indicator. [ChangeLog][QtQuick.Controls] Introducing TreeView With-Help-From: Caroline Chao <caroline.chao@theqtcompany.com> Change-Id: Id3dba240a732744571e4a646b7b98678ab522da6 Reviewed-by: Caroline Chao <caroline.chao@theqtcompany.com>
Diffstat (limited to 'tests')
-rw-r--r--tests/auto/auto.pro3
-rw-r--r--tests/auto/controls/controls.pro3
-rw-r--r--tests/auto/controls/data/treeview/treeview_1.qml76
-rw-r--r--tests/auto/controls/data/treeview/treeview_2.qml81
-rw-r--r--tests/auto/controls/data/tst_treeview.qml761
-rw-r--r--tests/auto/qquicktreemodeladaptor/qquicktreemodeladaptor.pro12
-rw-r--r--tests/auto/qquicktreemodeladaptor/tst_qquicktreemodeladaptor.cpp1135
-rw-r--r--tests/auto/shared/testmodel.h333
-rw-r--r--tests/auto/testplugin/testplugin.cpp2
-rw-r--r--tests/auto/testplugin/testplugin.pro3
10 files changed, 2406 insertions, 3 deletions
diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro
index de1b45b6..86de357d 100644
--- a/tests/auto/auto.pro
+++ b/tests/auto/auto.pro
@@ -1,3 +1,4 @@
TEMPLATE = subdirs
-SUBDIRS += testplugin controls activeFocusOnTab applicationwindow dialogs extras paint
+SUBDIRS += testplugin controls activeFocusOnTab applicationwindow dialogs \
+ extras paint qquicktreemodeladaptor
controls.depends = testplugin
diff --git a/tests/auto/controls/controls.pro b/tests/auto/controls/controls.pro
index 40fa77cd..8f8d1507 100644
--- a/tests/auto/controls/controls.pro
+++ b/tests/auto/controls/controls.pro
@@ -49,4 +49,5 @@ OTHER_FILES += \
$$PWD/data/tst_splitview.qml \
$$PWD/data/tst_styles.qml \
$$PWD/data/tst_layout.qml \
- $$PWD/data/tst_keys.qml
+ $$PWD/data/tst_keys.qml \
+ $$PWD/data/tst_treeview.qml
diff --git a/tests/auto/controls/data/treeview/treeview_1.qml b/tests/auto/controls/data/treeview/treeview_1.qml
new file mode 100644
index 00000000..29c2efdc
--- /dev/null
+++ b/tests/auto/controls/data/treeview/treeview_1.qml
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.4
+import QtQuick.Controls 1.4
+import QtQuick.Controls.Styles 1.4
+import QtQuickControlsTests 1.0
+
+TreeView {
+ width: 400
+ height: 400
+ model: TreeModel {}
+
+ TableViewColumn {
+ role: "display"
+ title: "Default"
+ width: 200
+ }
+
+ style: TreeViewStyle {
+ indentation: 20
+ rowDelegate: Rectangle {
+ color: styleData.selected? "blue" : styleData.alternate ? "red" : "yellow"
+ height: 50
+ width: parent.width
+ }
+ headerDelegate: Text {
+ text: styleData.value
+ height: 50
+ width: parent.width
+ }
+ itemDelegate: Text {
+ text: styleData.value
+ height: 50
+ width: parent.width
+ }
+ branchDelegate: Rectangle {
+ color: styleData.isExpanded? "purple" : "green"
+ height: 50
+ width: 20
+ }
+ }
+}
diff --git a/tests/auto/controls/data/treeview/treeview_2.qml b/tests/auto/controls/data/treeview/treeview_2.qml
new file mode 100644
index 00000000..f8c54028
--- /dev/null
+++ b/tests/auto/controls/data/treeview/treeview_2.qml
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.4
+import QtQuick.Controls 1.4
+import QtQuickControlsTests 1.0
+
+TreeView {
+ width: 500
+ height: 500
+ property var treeModel: TreeModel {}
+
+ model: treeModel
+
+ property int collapsedCount: 0
+ property int expandedCount: 0
+ property var signalArgIndex
+
+ TableViewColumn {
+ role: "display"
+ title: "Default"
+ width: 200
+ }
+ rowDelegate: Rectangle {
+ color: styleData.selected? "blue" : styleData.alternate ? "red" : "yellow"
+ height: 50
+ width: parent.width
+ }
+ headerDelegate: Text {
+ text: styleData.value
+ height: 50
+ width: parent.width
+ }
+ itemDelegate: Text {
+ text: styleData.value
+ height: 50
+ width: parent.width
+ }
+
+ onCollapsed: {
+ signalArgIndex = index
+ collapsedCount++
+ }
+ onExpanded: {
+ signalArgIndex = index
+ expandedCount++
+ }
+}
diff --git a/tests/auto/controls/data/tst_treeview.qml b/tests/auto/controls/data/tst_treeview.qml
new file mode 100644
index 00000000..7ce5581b
--- /dev/null
+++ b/tests/auto/controls/data/tst_treeview.qml
@@ -0,0 +1,761 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.4
+import QtTest 1.0
+import QtQuick.Controls 1.4
+import QtQuickControlsTests 1.0
+
+Item {
+ id: container
+ width: 400
+ height: 400
+
+ TestCase {
+ id: testCase
+ name: "Tests_TreeView"
+ when:windowShown
+ width:400
+ height:400
+ objectName: "testCase"
+
+ SignalSpy {
+ id: spy
+ }
+
+ Component {
+ id: newColumn
+ TableViewColumn {
+ role: "name"
+ title: "Name"
+ }
+ }
+
+ property var instance_selectionModel: 'import QtQml.Models 2.2; ItemSelectionModel {}'
+ property var semiIndent: 20/2 // PM_TreeViewIndentation 20 in commonStyle
+
+ function cleanup()
+ {
+ // Make sure to delete all children even when the test has failed.
+ for (var child in container.children) {
+ if (container.children[child].objectName !== "testCase")
+ container.children[child].destroy()
+ }
+ }
+
+ function test_basic_setup()
+ {
+ var test_instanceStr =
+ 'import QtQuick 2.4; \
+ import QtQuick.Controls 1.4; \
+ import QtQuickControlsTests 1.0;\
+ TreeView { \
+ model: TreeModel {} \
+ TableViewColumn { \
+ role: "display"; \
+ title: "Default"; \
+ } \
+ }'
+
+ var tree = Qt.createQmlObject(test_instanceStr, container, '')
+ verify(!tree.currentIndex.valid)
+ compare(tree.columnCount, 1)
+ tree.addColumn(newColumn)
+ compare(tree.columnCount, 2)
+ tree.destroy()
+ }
+
+ function test_clicked_signals()
+ {
+ var component = Qt.createComponent("treeview/treeview_1.qml")
+ compare(component.status, Component.Ready)
+ var tree = component.createObject(container);
+ verify(tree !== null, "tree created is null")
+ waitForRendering(tree)
+
+ tree.forceActiveFocus()
+
+ verify(!tree.currentIndex.valid)
+ spy.clear()
+ spy.target = tree
+ spy.signalName = "clicked"
+ compare(spy.count, 0)
+ mouseClick(tree, semiIndent + 50, 120, Qt.LeftButton)
+ compare(spy.count, 1)
+ var clickedItem = spy.signalArguments[0][0]
+ verify(clickedItem.valid)
+ compare(clickedItem.row, 1)
+ compare(tree.currentIndex.row, 1)
+ compare(clickedItem.internalId, tree.currentIndex.internalId)
+
+ // TO FIX
+// spy.clear()
+// spy.target = tree
+// spy.signalName = "doubleClicked"
+// compare(spy.count, 0)
+// mouseDoubleClick(tree, semiIndent + 50, 120, Qt.LeftButton)
+// compare(spy.count, 1)
+// verify(spy.signalArguments[1][0].valid)
+// compare(spy.signalArguments[1][0].row, 2)
+// compare(tree.currentIndex.row, 2)
+
+// spy.clear()
+// spy.target = tree
+// spy.signalName = "activated"
+// compare(spy.count, 0)
+// if (!tree.__activateItemOnSingleClick)
+// mouseDoubleClick(tree, semiIndent + 50 , 120, Qt.LeftButton)
+// else
+// mouseClick(tree, semiIndent + 50, 120, Qt.LeftButton)
+// compare(spy.count, 1)
+// verify(spy.signalArguments[0][0].valid)
+// compare(spy.signalArguments[0][0].row, 1)
+// compare(tree.currentIndex.row, 1)
+// tree.destroy()
+ }
+
+ function test_headerHidden()
+ {
+ var component = Qt.createComponent("treeview/treeview_1.qml")
+ compare(component.status, Component.Ready)
+ var tree = component.createObject(container);
+ verify(tree !== null, "tree created is null")
+ waitForRendering(tree)
+
+ tree.headerVisible = false
+ tree.forceActiveFocus()
+
+ verify(!tree.currentIndex.valid)
+ spy.clear()
+ spy.target = tree
+ spy.signalName = "clicked"
+ compare(spy.count, 0)
+ mouseClick(tree, semiIndent + 50, 20, Qt.LeftButton)
+ compare(spy.count, 1)
+ verify(spy.signalArguments[0][0].valid)
+ compare(spy.signalArguments[0][0].row, 0)
+ compare(tree.currentIndex.row, 0)
+ tree.destroy()
+ }
+
+ function test_expand_collapse()
+ {
+ var component = Qt.createComponent("treeview/treeview_1.qml")
+ compare(component.status, Component.Ready)
+ var tree = component.createObject(container);
+ verify(tree !== null, "tree created is null")
+ waitForRendering(tree)
+ tree.forceActiveFocus()
+
+ spy.clear()
+ spy.target = tree
+ spy.signalName = "expanded"
+
+ // expanded on click
+ compare(spy.count, 0)
+ mouseClick(tree, semiIndent, 70, Qt.LeftButton)
+ compare(spy.count, 1)
+ var expandedIndex = spy.signalArguments[0][0]
+ verify(expandedIndex.valid)
+ compare(expandedIndex.row, 0)
+ compare(tree.isExpanded(expandedIndex), true)
+
+ // expand first child on click
+ mouseClick(tree, semiIndent * 3, 120, Qt.LeftButton)
+ compare(spy.count, 2)
+ var childIndex = spy.signalArguments[1][0]
+ verify(childIndex.valid)
+ compare(childIndex.row, 0)
+ compare(tree.isExpanded(childIndex), true)
+ compare(childIndex.parent.internalId, expandedIndex.internalId)
+
+ spy.clear()
+ spy.signalName = "collapsed"
+
+ // collapsed on click top item
+ compare(spy.count, 0)
+ mouseClick(tree, semiIndent, 70, Qt.LeftButton)
+ compare(spy.count, 1)
+ var collapsedIndex = spy.signalArguments[0][0]
+ verify(collapsedIndex.valid)
+ compare(collapsedIndex.row, 0)
+ compare(tree.isExpanded(collapsedIndex), false)
+ compare(expandedIndex.internalId, collapsedIndex.internalId)
+
+ // check hidden child is still expanded
+ compare(tree.isExpanded(childIndex), true)
+
+ // collapse child with function
+ tree.collapse(childIndex)
+ compare(tree.isExpanded(childIndex), false)
+ compare(spy.count, 2)
+ compare(spy.signalArguments[1][0].row, 0)
+
+ spy.clear()
+ spy.signalName = "expanded"
+ compare(spy.count, 0)
+
+ // expand child with function
+ tree.expand(expandedIndex)
+ compare(tree.isExpanded(expandedIndex), true)
+ compare(spy.count, 1)
+ compare(spy.signalArguments[0][0].row, 0)
+
+ tree.destroy()
+ }
+
+ function test_pressAndHold()
+ {
+ var component = Qt.createComponent("treeview/treeview_1.qml")
+ compare(component.status, Component.Ready)
+ var tree = component.createObject(container);
+ verify(tree !== null, "tree created is null")
+ waitForRendering(tree)
+
+ tree.forceActiveFocus()
+
+ var styleIndent = !!tree.style.indentation ? tree.style.indentation/2 : 6
+ verify(!tree.currentIndex.valid)
+ spy.clear()
+ spy.target = tree
+ spy.signalName = "pressAndHold"
+ compare(spy.count, 0)
+ mousePress(tree, styleIndent + 50, 70, Qt.LeftButton)
+ mouseRelease(tree, styleIndent + 50, 70, Qt.LeftButton, Qt.NoModifier, 1000)
+ compare(spy.count, 1)
+ verify(spy.signalArguments[0][0].valid)
+ compare(spy.signalArguments[0][0].row, 0)
+ compare(tree.currentIndex.row, 0)
+ tree.destroy()
+ }
+
+ function test_keys_navigation()
+ {
+ var component = Qt.createComponent("treeview/treeview_2.qml")
+ compare(component.status, Component.Ready)
+ var tree = component.createObject(container);
+ verify(tree !== null, "tree created is null")
+ waitForRendering(tree)
+
+ tree.forceActiveFocus()
+
+ // select second item with no children
+ verify(!tree.currentIndex.valid)
+ mouseClick(tree, semiIndent + 50, 120, Qt.LeftButton)
+ var secondTopItem = tree.currentIndex
+ verify(secondTopItem.valid)
+ verify(!secondTopItem.parent.valid)
+ compare(secondTopItem.row, 1)
+
+ // Press right (selected item is non expandable)
+ compare(tree.collapsedCount, 0)
+ compare(tree.expandedCount, 0)
+ keyClick(Qt.Key_Right)
+ compare(tree.collapsedCount, 0)
+ compare(tree.expandedCount, 0)
+ compare(tree.currentIndex, secondTopItem)
+
+ // Going down
+ keyClick(Qt.Key_Down)
+ var thirdTopItem = tree.currentIndex
+ compare(thirdTopItem.row, 2)
+ verify(!thirdTopItem.parent.valid)
+
+ // Press right - expand - go down - go up - collapse
+ keyClick(Qt.Key_Right)
+ compare(tree.collapsedCount, 0)
+ compare(tree.expandedCount, 1)
+ compare(tree.isExpanded(thirdTopItem), true)
+ keyClick(Qt.Key_Down)
+ var firstChild_thirdTopItem = tree.currentIndex
+ compare(firstChild_thirdTopItem.row, 0)
+ verify(firstChild_thirdTopItem.parent.valid)
+ compare(firstChild_thirdTopItem.parent.row, 2)
+ compare(firstChild_thirdTopItem.parent.internalId, thirdTopItem.internalId)
+ keyClick(Qt.Key_Up)
+ verify(!tree.currentIndex.parent.valid)
+ compare(tree.currentIndex.internalId, thirdTopItem.internalId)
+ compare(tree.currentIndex.row, 2)
+ compare(tree.isExpanded(tree.currentIndex), true)
+ keyClick(Qt.Key_Left)
+ compare(tree.isExpanded(tree.currentIndex), false)
+ compare(tree.collapsedCount, 1)
+ compare(tree.expandedCount, 1)
+ tree.destroy()
+ }
+
+ function test_selection_singleSelection()
+ {
+ var component = Qt.createComponent("treeview/treeview_1.qml")
+ compare(component.status, Component.Ready)
+ var tree = component.createObject(container);
+ verify(tree !== null, "tree created is null")
+ waitForRendering(tree)
+
+ var selectionModel = Qt.createQmlObject(testCase.instance_selectionModel, container, '')
+ selectionModel.model = tree.model
+
+ // Collect some model index
+ mouseClick(tree, semiIndent + 50, 20 + 50, Qt.LeftButton)
+ var firstItem = tree.currentIndex
+ verify(firstItem.valid)
+ compare(firstItem.row, 0)
+ mouseClick(tree, semiIndent + 50, 20 + 2*50, Qt.LeftButton)
+ var secondItem = tree.currentIndex
+ verify(secondItem.valid)
+ compare(secondItem.row, 1)
+ mouseClick(tree, semiIndent + 50, 20 + 3*50, Qt.LeftButton)
+ var thirdItem = tree.currentIndex
+ verify(thirdItem.valid)
+ compare(thirdItem.row, 2)
+ mouseClick(tree, semiIndent + 50, 20 + 4*50, Qt.LeftButton)
+ var fourthItem = tree.currentIndex
+ verify(fourthItem.valid)
+ compare(fourthItem.row, 3)
+ mouseClick(tree, semiIndent + 50, 20 + 5*50, Qt.LeftButton)
+ var fifthItem = tree.currentIndex
+ verify(fifthItem.valid)
+ compare(fifthItem.row, 4)
+ mouseClick(tree, semiIndent + 50, 20 + 6*50, Qt.LeftButton)
+ var sixthItem = tree.currentIndex
+ verify(sixthItem.valid)
+ compare(sixthItem.row, 5)
+
+ compare(tree.selection, null)
+ tree.selection = selectionModel
+ compare(tree.selection, selectionModel)
+ tree.selection.clear()
+ compare(tree.selection.hasSelection, false)
+
+ //// Single selectionModel
+ compare(tree.selectionMode, SelectionMode.SingleSelection)
+ verify(!tree.selection.currentIndex.valid)
+
+ mouseClick(tree, semiIndent + 50, 20 + 2*50, Qt.LeftButton)
+ verify(tree.selection.currentIndex.valid)
+
+ compare(secondItem.internalId, tree.currentIndex.internalId)
+ compare(secondItem.internalId, tree.selection.currentIndex.internalId)
+ expectFailContinue('', 'BUG isSelected is always false when SingleSelection')
+ compare(tree.selection.isSelected(secondItem), true)
+ expectFailContinue('', 'BUG hasSelection is always false when SingleSelection')
+ compare(tree.selection.hasSelection, true)
+ var list = tree.selection.selectedIndexes()
+ expectFailContinue('', 'BUG empty selectedIndex when SingleSelection')
+ compare(list.length, 1)
+ if (list.length === 1) {
+ compare(list.at(0).internalId, secondItem.internalId)
+ compare(tree.selection.isSelected(secondItem), true)
+ }
+
+ keyClick(Qt.Key_Down, Qt.ShiftModifier)
+ compare(thirdItem.internalId, tree.currentIndex.internalId)
+ compare(thirdItem.internalId, tree.selection.currentIndex.internalId)
+
+ keyClick(Qt.Key_Down, Qt.ControlModifier)
+
+ compare(fourthItem.internalId, tree.currentIndex.internalId)
+ expectFailContinue('', 'BUG selected state not updated with Command/Control when SingleSelection')
+ compare(fourthItem.internalId, tree.selection.currentIndex.internalId)
+ expectFailContinue('', 'BUG selected state not updated with Command/Control when SingleSelection')
+ compare(tree.selection.isSelected(fourthItem), true)
+
+ tree.destroy()
+ }
+
+ function test_selection_noSelection()
+ {
+ var component = Qt.createComponent("treeview/treeview_1.qml")
+ compare(component.status, Component.Ready)
+ var tree = component.createObject(container);
+ verify(tree !== null, "tree created is null")
+ waitForRendering(tree)
+
+ var selectionModel = Qt.createQmlObject(testCase.instance_selectionModel, container, '')
+ selectionModel.model = tree.model
+
+ // Collect some model index
+ mouseClick(tree, semiIndent + 50, 20 + 50, Qt.LeftButton)
+ var firstItem = tree.currentIndex
+ mouseClick(tree, semiIndent + 50, 20 + 2*50, Qt.LeftButton)
+ var secondItem = tree.currentIndex
+ mouseClick(tree, semiIndent + 50, 20 + 3*50, Qt.LeftButton)
+ var thirdItem = tree.currentIndex
+ mouseClick(tree, semiIndent + 50, 20 + 4*50, Qt.LeftButton)
+ var fourthItem = tree.currentIndex
+ mouseClick(tree, semiIndent + 50, 20 + 5*50, Qt.LeftButton)
+ var fifthItem = tree.currentIndex
+ mouseClick(tree, semiIndent + 50, 20 + 6*50, Qt.LeftButton)
+ var sixthItem = tree.currentIndex
+
+ compare(tree.selection, null)
+ tree.selection = selectionModel
+ compare(tree.selection, selectionModel)
+ tree.selection.clear()
+ compare(tree.selection.hasSelection, false)
+
+ //// No selection
+ tree.selectionMode = SelectionMode.NoSelection
+ compare(tree.selectionMode, SelectionMode.NoSelection)
+
+ mouseClick(tree, semiIndent + 50, 70+50, Qt.LeftButton)
+
+ compare(secondItem.internalId, tree.currentIndex.internalId)
+ verify(!tree.selection.currentIndex.valid)
+ compare(tree.selection.hasSelection, false)
+ compare(tree.selection.isSelected(secondItem), false)
+
+ keyClick(Qt.Key_Down, Qt.ShiftModifier)
+ verify(!tree.selection.currentIndex.valid)
+ compare(tree.selection.hasSelection, false)
+ compare(tree.selection.isSelected(thirdItem), false)
+
+ keyClick(Qt.Key_Down, Qt.ControlModifier)
+ verify(!tree.selection.currentIndex.valid)
+ compare(tree.selection.hasSelection, false)
+ expectFailContinue('', 'BUG selected state not updated with Command/Controls when SingleSelection')
+ compare(tree.selection.isSelected(fourthItem), true)
+
+ tree.destroy()
+ }
+
+ function test_selection_multiSelection()
+ {
+ var component = Qt.createComponent("treeview/treeview_1.qml")
+ compare(component.status, Component.Ready)
+ var tree = component.createObject(container);
+ verify(tree !== null, "tree created is null")
+ waitForRendering(tree)
+
+ var selectionModel = Qt.createQmlObject(testCase.instance_selectionModel, container, '')
+ selectionModel.model = tree.model
+
+ // Collect some model index
+ mouseClick(tree, semiIndent + 50, 20 + 50, Qt.LeftButton)
+ var firstItem = tree.currentIndex
+ mouseClick(tree, semiIndent + 50, 20 + 2*50, Qt.LeftButton)
+ var secondItem = tree.currentIndex
+ mouseClick(tree, semiIndent + 50, 20 + 3*50, Qt.LeftButton)
+ var thirdItem = tree.currentIndex
+ mouseClick(tree, semiIndent + 50, 20 + 4*50, Qt.LeftButton)
+ var fourthItem = tree.currentIndex
+ mouseClick(tree, semiIndent + 50, 20 + 5*50, Qt.LeftButton)
+ var fifthItem = tree.currentIndex
+ mouseClick(tree, semiIndent + 50, 20 + 6*50, Qt.LeftButton)
+ var sixthItem = tree.currentIndex
+
+ compare(tree.selection, null)
+ tree.selection = selectionModel
+ compare(tree.selection, selectionModel)
+ tree.selection.clear()
+ compare(tree.selection.hasSelection, false)
+
+ ////// Multi selection
+ tree.selectionMode = SelectionMode.MultiSelection
+ compare(tree.selectionMode, SelectionMode.MultiSelection)
+
+ mouseClick(tree, semiIndent + 50, 70+50, Qt.LeftButton)
+
+ compare(secondItem.internalId, tree.currentIndex.internalId)
+ compare(tree.selection.hasSelection, true)
+ compare(tree.selection.isSelected(secondItem), true)
+ var listIndexes = tree.selection.selectedIndexes()
+ compare(listIndexes.length, 1)
+ compare(listIndexes.at(0).internalId, secondItem.internalId)
+ expectFailContinue('', 'BUG selection.currentIndex is invalid when MultiSelection')
+ verify(tree.selection.currentIndex.valid)
+ if (tree.selection.currentIndex.valid)
+ compare(tree.selection.currentIndex.internalId, secondItem.internalId)
+
+ mouseClick(tree, semiIndent + 50, 70+150, Qt.LeftButton)
+ compare(fourthItem.internalId, tree.currentIndex.internalId)
+ compare(tree.selection.hasSelection, true)
+ compare(tree.selection.isSelected(secondItem), true)
+ compare(tree.selection.isSelected(fourthItem), true)
+ listIndexes = tree.selection.selectedIndexes()
+ compare(listIndexes.length, 2)
+ compare(listIndexes.at(0).internalId, secondItem.internalId)
+ compare(listIndexes.at(1).internalId, fourthItem.internalId)
+ expectFailContinue('', 'BUG selection.currentIndex is invalid when MultiSelection')
+ verify(tree.selection.currentIndex.valid)
+ if (tree.selection.currentIndex.valid)
+ compare(tree.selection.currentIndex.internalId, fourthItem.internalId)
+
+ keyPress(Qt.Key_Shift)
+ mouseClick(tree, semiIndent + 50, 70+250, Qt.LeftButton)
+ keyRelease(Qt.Key_Shift)
+ compare(sixthItem.internalId, tree.currentIndex.internalId)
+ compare(tree.selection.isSelected(secondItem), true)
+ compare(tree.selection.isSelected(fourthItem), true)
+ compare(tree.selection.isSelected(fifthItem), false)
+ compare(tree.selection.isSelected(sixthItem), true)
+
+ listIndexes = tree.selection.selectedIndexes()
+ compare(listIndexes.length, 3)
+ compare(listIndexes.at(0).internalId, secondItem.internalId)
+ compare(listIndexes.at(1).internalId, fourthItem.internalId)
+ compare(listIndexes.at(2).internalId, sixthItem.internalId)
+ expectFailContinue('', 'BUG selection.currentIndex is invalid when MultiSelection')
+ verify(tree.selection.currentIndex.valid)
+ if (tree.selection.currentIndex.valid)
+ compare(tree.selection.currentIndex.internalId, sixthItem.internalId)
+
+
+ mouseClick(tree, semiIndent + 50, 70+150, Qt.LeftButton)
+ compare(fourthItem.internalId, tree.currentIndex.internalId)
+ compare(tree.selection.isSelected(secondItem), true)
+ compare(tree.selection.isSelected(fourthItem), false)
+ compare(tree.selection.isSelected(sixthItem), true)
+
+ listIndexes = tree.selection.selectedIndexes()
+ compare(listIndexes.length, 2)
+ compare(listIndexes.at(0).internalId, secondItem.internalId)
+ compare(listIndexes.at(1).internalId, sixthItem.internalId)
+ expectFailContinue('', 'BUG selection.currentIndex is invalid when MultiSelection')
+ verify(tree.selection.currentIndex.valid)
+ if (tree.selection.currentIndex.valid) // TO VERIFY
+ verify(!tree.selection.currentIndex.valid)
+
+ mouseClick(tree, semiIndent + 50, 70+150, Qt.LeftButton)
+ compare(fourthItem.internalId, tree.currentIndex.internalId)
+ compare(tree.selection.isSelected(secondItem), true)
+ compare(tree.selection.isSelected(fourthItem), true)
+ compare(tree.selection.isSelected(sixthItem), true)
+
+ keyPress(Qt.Key_Shift)
+ keyClick(Qt.Key_Down)
+ keyClick(Qt.Key_Down)
+ keyClick(Qt.Key_Down)
+ keyRelease(Qt.Key_Shift)
+ compare(tree.selection.isSelected(fourthItem), true)
+ compare(tree.selection.isSelected(fifthItem), true)
+ compare(tree.selection.isSelected(sixthItem), false)
+ listIndexes = tree.selection.selectedIndexes()
+ compare(listIndexes.length, 4)
+
+ tree.destroy()
+ }
+
+ function test_selection_extendedSelection()
+ {
+ var component = Qt.createComponent("treeview/treeview_1.qml")
+ compare(component.status, Component.Ready)
+ var tree = component.createObject(container);
+ verify(tree !== null, "tree created is null")
+ waitForRendering(tree)
+
+ var selectionModel = Qt.createQmlObject(testCase.instance_selectionModel, container, '')
+ selectionModel.model = tree.model
+
+ // Collect some model index
+ mouseClick(tree, semiIndent + 50, 20 + 50, Qt.LeftButton)
+ var firstItem = tree.currentIndex
+ mouseClick(tree, semiIndent + 50, 20 + 2*50, Qt.LeftButton)
+ var secondItem = tree.currentIndex
+ mouseClick(tree, semiIndent + 50, 20 + 3*50, Qt.LeftButton)
+ var thirdItem = tree.currentIndex
+ mouseClick(tree, semiIndent + 50, 20 + 4*50, Qt.LeftButton)
+ var fourthItem = tree.currentIndex
+ mouseClick(tree, semiIndent + 50, 20 + 5*50, Qt.LeftButton)
+ var fifthItem = tree.currentIndex
+ mouseClick(tree, semiIndent + 50, 20 + 6*50, Qt.LeftButton)
+ var sixthItem = tree.currentIndex
+
+ compare(tree.selection, null)
+ tree.selection = selectionModel
+ compare(tree.selection, selectionModel)
+ tree.selection.clear()
+ compare(tree.selection.hasSelection, false)
+
+ ////// Extended selection
+ tree.selectionMode = SelectionMode.ExtendedSelection
+ compare(tree.selectionMode, SelectionMode.ExtendedSelection)
+
+ mouseClick(tree, semiIndent + 50, 70+50, Qt.LeftButton)
+
+ compare(secondItem.internalId, tree.currentIndex.internalId)
+ compare(tree.selection.hasSelection, true)
+ compare(tree.selection.isSelected(secondItem), true)
+ var listIndexes = tree.selection.selectedIndexes()
+ compare(listIndexes.length, 1)
+ compare(listIndexes.at(0).internalId, secondItem.internalId)
+ expectFailContinue('', 'BUG selection.currentIndex is invalid when ExtendedSelection')
+ verify(tree.selection.currentIndex.valid)
+ if (tree.selection.currentIndex.valid)
+ compare(tree.selection.currentIndex.internalId, secondItem.internalId)
+
+ // Re-click does not deselect
+ mouseClick(tree, semiIndent + 50, 70+50, Qt.LeftButton)
+ compare(tree.selection.hasSelection, true)
+ compare(tree.selection.isSelected(secondItem), true)
+ // Ctrl/Cmd click deselect
+ mouseClick(tree, semiIndent + 50, 70+52, Qt.LeftButton, Qt.ControlModifier)
+ compare(tree.selection.hasSelection, false)
+ compare(tree.selection.isSelected(secondItem), false)
+ listIndexes = tree.selection.selectedIndexes()
+ compare(listIndexes.length, 0)
+
+ mouseClick(tree, semiIndent + 50, 70+50, Qt.LeftButton)
+ keyPress(Qt.Key_Down, Qt.ShiftModifier)
+ keyPress(Qt.Key_Down, Qt.ShiftModifier)
+ keyClick(Qt.Key_Down, Qt.ShiftModifier)
+
+ listIndexes = tree.selection.selectedIndexes()
+ compare(listIndexes.length, 4)
+ compare(tree.selection.hasSelection, true)
+ compare(tree.selection.isSelected(secondItem), true)
+ compare(tree.selection.isSelected(thirdItem), true)
+ compare(tree.selection.isSelected(fourthItem), true)
+ compare(tree.selection.isSelected(fifthItem), true)
+
+ mouseClick(tree, semiIndent + 50, 70+300, Qt.LeftButton, Qt.ShiftModifier)
+ listIndexes = tree.selection.selectedIndexes()
+ compare(listIndexes.length, 6)
+
+ mouseClick(tree, semiIndent + 50, 70+150, Qt.LeftButton, Qt.ControlModifier)
+ listIndexes = tree.selection.selectedIndexes()
+ compare(listIndexes.length, 5)
+ compare(tree.selection.isSelected(fourthItem), false)
+ compare(tree.selection.hasSelection, true)
+ compare(tree.selection.isSelected(secondItem), true)
+ compare(tree.selection.isSelected(thirdItem), true)
+ compare(tree.selection.isSelected(sixthItem), true)
+ compare(tree.selection.isSelected(fifthItem), true)
+
+ tree.destroy()
+ }
+
+ function test_selection_contiguousSelection()
+ {
+ var component = Qt.createComponent("treeview/treeview_1.qml")
+ compare(component.status, Component.Ready)
+ var tree = component.createObject(container);
+ verify(tree !== null, "tree created is null")
+ waitForRendering(tree)
+
+ var selectionModel = Qt.createQmlObject(testCase.instance_selectionModel, container, '')
+ selectionModel.model = tree.model
+
+ // Collect some model index
+ mouseClick(tree, semiIndent + 50, 20 + 50, Qt.LeftButton)
+ var firstItem = tree.currentIndex
+ mouseClick(tree, semiIndent + 50, 20 + 2*50, Qt.LeftButton)
+ var secondItem = tree.currentIndex
+ mouseClick(tree, semiIndent + 50, 20 + 3*50, Qt.LeftButton)
+ var thirdItem = tree.currentIndex
+ mouseClick(tree, semiIndent + 50, 20 + 4*50, Qt.LeftButton)
+ var fourthItem = tree.currentIndex
+ mouseClick(tree, semiIndent + 50, 20 + 5*50, Qt.LeftButton)
+ var fifthItem = tree.currentIndex
+ mouseClick(tree, semiIndent + 50, 20 + 6*50, Qt.LeftButton)
+ var sixthItem = tree.currentIndex
+
+ compare(tree.selection, null)
+ tree.selection = selectionModel
+ compare(tree.selection, selectionModel)
+ tree.selection.clear()
+ compare(tree.selection.hasSelection, false)
+
+ ////// Contiguous selection
+ tree.selectionMode = SelectionMode.ContiguousSelection
+ compare(tree.selectionMode, SelectionMode.ContiguousSelection)
+
+ mouseClick(tree, semiIndent + 50, 70+50, Qt.LeftButton)
+
+ compare(secondItem.internalId, tree.currentIndex.internalId)
+ compare(tree.selection.hasSelection, true)
+ compare(tree.selection.isSelected(secondItem), true)
+ var listIndexes = tree.selection.selectedIndexes()
+ compare(listIndexes.length, 1)
+ compare(listIndexes.at(0).internalId, secondItem.internalId)
+ expectFailContinue('', 'BUG selection.currentIndex is invalid when ContiguousSelection')
+ verify(tree.selection.currentIndex.valid)
+ if (tree.selection.currentIndex.valid)
+ compare(tree.selection.currentIndex.internalId, secondItem.internalId)
+
+ // Re-click does not deselect
+ mouseClick(tree, semiIndent + 50, 70+50, Qt.LeftButton)
+ compare(tree.selection.hasSelection, true)
+ compare(tree.selection.isSelected(secondItem), true)
+ // Ctrl/Cmd click deselect
+ mouseClick(tree, semiIndent + 50, 70+52, Qt.LeftButton, Qt.ControlModifier)
+ compare(tree.selection.hasSelection, false)
+ compare(tree.selection.isSelected(secondItem), false)
+ listIndexes = tree.selection.selectedIndexes()
+ compare(listIndexes.length, 0)
+
+ mouseClick(tree, semiIndent + 50, 70+50, Qt.LeftButton)
+ keyPress(Qt.Key_Down, Qt.ShiftModifier)
+ keyPress(Qt.Key_Down, Qt.ShiftModifier)
+ keyClick(Qt.Key_Down, Qt.ShiftModifier)
+
+ listIndexes = tree.selection.selectedIndexes()
+ compare(listIndexes.length, 4)
+ compare(tree.selection.hasSelection, true)
+ compare(tree.selection.isSelected(secondItem), true)
+ compare(tree.selection.isSelected(thirdItem), true)
+ compare(tree.selection.isSelected(fourthItem), true)
+ compare(tree.selection.isSelected(fifthItem), true)
+ expectFailContinue('', 'BUG selection.currentIndex is invalid when ContiguousSelection')
+ verify(tree.selection.currentIndex.valid)
+ if (tree.selection.currentIndex.valid)
+ compare(tree.selection.currentIndex.internalId, fifthItem.internalId)
+
+ mouseClick(tree, semiIndent + 50, 70+300, Qt.LeftButton, Qt.ShiftModifier)
+ listIndexes = tree.selection.selectedIndexes()
+ compare(listIndexes.length, 6)
+
+ mouseClick(tree, semiIndent + 50, 70+150, Qt.LeftButton, Qt.ShiftModifier)
+
+ listIndexes = tree.selection.selectedIndexes()
+ compare(listIndexes.length, 3)
+ compare(tree.selection.hasSelection, true)
+ compare(tree.selection.isSelected(secondItem), true)
+ compare(tree.selection.isSelected(thirdItem), true)
+ compare(tree.selection.isSelected(fourthItem), true)
+ compare(tree.selection.isSelected(fifthItem), false)
+ compare(tree.selection.isSelected(sixthItem), false)
+
+ mouseClick(tree, semiIndent + 50, 70+100, Qt.LeftButton)
+ listIndexes = tree.selection.selectedIndexes()
+ compare(listIndexes.length, 1)
+ compare(tree.selection.hasSelection, true)
+ compare(tree.selection.isSelected(thirdItem), true)
+
+ tree.destroy()
+ }
+ }
+}
diff --git a/tests/auto/qquicktreemodeladaptor/qquicktreemodeladaptor.pro b/tests/auto/qquicktreemodeladaptor/qquicktreemodeladaptor.pro
new file mode 100644
index 00000000..cae5e4cd
--- /dev/null
+++ b/tests/auto/qquicktreemodeladaptor/qquicktreemodeladaptor.pro
@@ -0,0 +1,12 @@
+TEMPLATE = app
+TARGET = tst_qquicktreemodeladaptor
+
+CONFIG += testcase
+CONFIG -= app_bundle
+QT = core testlib
+
+INCLUDEPATH += $$PWD/../../../src/controls/Private
+HEADERS += $$PWD/../../../src/controls/Private/qquicktreemodeladaptor_p.h \
+ $$PWD/../shared/testmodel.h
+SOURCES += $$PWD/tst_qquicktreemodeladaptor.cpp \
+ $$PWD/../../../src/controls/Private/qquicktreemodeladaptor.cpp
diff --git a/tests/auto/qquicktreemodeladaptor/tst_qquicktreemodeladaptor.cpp b/tests/auto/qquicktreemodeladaptor/tst_qquicktreemodeladaptor.cpp
new file mode 100644
index 00000000..ea082bc0
--- /dev/null
+++ b/tests/auto/qquicktreemodeladaptor/tst_qquicktreemodeladaptor.cpp
@@ -0,0 +1,1135 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtTest/QtTest>
+#include <QtCore>
+#include <qquicktreemodeladaptor_p.h>
+#include "../shared/testmodel.h"
+
+class tst_QQuickTreeModelAdaptor : public QObject
+{
+ Q_OBJECT
+
+public:
+ void compareData(int row, QQuickTreeModelAdaptor &tma, const QModelIndex &idx, TestModel &model, bool expanded = false);
+ void compareModels(QQuickTreeModelAdaptor &tma, TestModel &model);
+ void expandAndTest(const QModelIndex &idx, QQuickTreeModelAdaptor &tma, bool expandable, int expectedRowCountDifference);
+ void collapseAndTest(const QModelIndex &idx, QQuickTreeModelAdaptor &tma, bool expandable, int expectedRowCountDifference);
+
+private slots:
+ void initTestCase();
+ void cleanup();
+
+ void setModel();
+ void modelReset();
+
+ void dataAccess();
+ void dataChange();
+ void groupedDataChange();
+
+ void expandAndCollapse_data();
+ void expandAndCollapse();
+ void expandAndCollapse2ndLevel();
+
+ void layoutChange();
+
+ void removeRows_data();
+ void removeRows();
+
+ void insertRows_data();
+ void insertRows();
+
+ void moveRows_data();
+ void moveRows();
+
+ void selectionForRowRange();
+};
+
+void tst_QQuickTreeModelAdaptor::initTestCase()
+{
+}
+
+void tst_QQuickTreeModelAdaptor::cleanup()
+{
+}
+
+void tst_QQuickTreeModelAdaptor::compareData(int row, QQuickTreeModelAdaptor &tma, const QModelIndex &modelIdx, TestModel &model, bool expanded)
+{
+ const QModelIndex &tmaIdx = tma.index(row);
+ QCOMPARE(tma.mapToModel(tmaIdx), modelIdx);
+ QCOMPARE(tma.data(tmaIdx, Qt::DisplayRole).toString(), model.displayData(modelIdx));
+ QCOMPARE(tma.data(tmaIdx, QQuickTreeModelAdaptor::DepthRole).toInt(), model.level(modelIdx));
+ QCOMPARE(tma.data(tmaIdx, QQuickTreeModelAdaptor::ExpandedRole).toBool(), expanded);
+ QCOMPARE(tma.data(tmaIdx, QQuickTreeModelAdaptor::HasChildrenRole).toBool(), model.hasChildren(modelIdx));
+}
+
+void tst_QQuickTreeModelAdaptor::expandAndTest(const QModelIndex &idx, QQuickTreeModelAdaptor &tma, bool expandable,
+ int expectedRowCountDifference)
+{
+ QSignalSpy rowsAboutToBeInsertedSpy(&tma, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)));
+ QSignalSpy rowsInsertedSpy(&tma, SIGNAL(rowsInserted(QModelIndex,int,int)));
+ QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
+
+ int oldRowCount = tma.rowCount();
+ tma.expand(idx);
+ QCOMPARE(tma.isExpanded(idx), expandable);
+
+ const QModelIndex &tmaIdx = tma.index(tma.itemIndex(idx));
+ QCOMPARE(tma.data(tmaIdx, QQuickTreeModelAdaptor::ExpandedRole).toBool(), expandable);
+
+ if (expandable) {
+ // Rows were added below the parent
+ QCOMPARE(tma.rowCount(), oldRowCount + expectedRowCountDifference);
+ QCOMPARE(rowsAboutToBeInsertedSpy.count(), rowsInsertedSpy.count());
+ QVERIFY(rowsInsertedSpy.count() > 0);
+ if (rowsInsertedSpy.count() == 1) {
+ const QVariantList &rowsAboutToBeInsertedArgs = rowsAboutToBeInsertedSpy.takeFirst();
+ const QVariantList &rowsInsertedArgs = rowsInsertedSpy.takeFirst();
+ for (int i = 0; i < rowsInsertedArgs.count(); i++)
+ QCOMPARE(rowsAboutToBeInsertedArgs.at(i), rowsInsertedArgs.at(i));
+ QCOMPARE(rowsInsertedArgs.at(0).toModelIndex(), QModelIndex());
+ QCOMPARE(rowsInsertedArgs.at(1).toInt(), tma.itemIndex(idx) + 1);
+ QCOMPARE(rowsInsertedArgs.at(2).toInt(), tma.itemIndex(idx) + expectedRowCountDifference);
+ }
+
+ // Data changed for the parent's ExpandedRole (value checked above)
+ QCOMPARE(dataChangedSpy.count(), 1);
+ const QVariantList &dataChangedArgs = dataChangedSpy.first();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaIdx);
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaIdx);
+ QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), QVector<int>(1, QQuickTreeModelAdaptor::ExpandedRole));
+ } else {
+ QCOMPARE(tma.rowCount(), oldRowCount);
+ QCOMPARE(rowsAboutToBeInsertedSpy.count(), 0);
+ QCOMPARE(rowsInsertedSpy.count(), 0);
+ }
+}
+
+void tst_QQuickTreeModelAdaptor::collapseAndTest(const QModelIndex &idx, QQuickTreeModelAdaptor &tma,
+ bool expandable, int expectedRowCountDifference)
+{
+ QSignalSpy rowsAboutToBeRemovedSpy(&tma, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)));
+ QSignalSpy rowsRemovedSpy(&tma, SIGNAL(rowsRemoved(QModelIndex,int,int)));
+ QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
+
+ int oldRowCount = tma.rowCount();
+ tma.collapse(idx);
+ QVERIFY(!tma.isExpanded(idx));
+
+ const QModelIndex &tmaIdx = tma.index(tma.itemIndex(idx));
+ if (tmaIdx.isValid())
+ QCOMPARE(tma.data(tmaIdx, QQuickTreeModelAdaptor::ExpandedRole).toBool(), false);
+
+ if (expandable) {
+ // Rows were removed below the parent
+ QCOMPARE(tma.rowCount(), oldRowCount - expectedRowCountDifference);
+ QCOMPARE(rowsAboutToBeRemovedSpy.count(), 1);
+ QCOMPARE(rowsRemovedSpy.count(), 1);
+ const QVariantList &rowsAboutToBeRemovedArgs = rowsAboutToBeRemovedSpy.takeFirst();
+ const QVariantList &rowsRemovedArgs = rowsRemovedSpy.takeFirst();
+ for (int i = 0; i < rowsRemovedArgs.count(); i++)
+ QCOMPARE(rowsAboutToBeRemovedArgs.at(i), rowsRemovedArgs.at(i));
+ QCOMPARE(rowsRemovedArgs.at(0).toModelIndex(), QModelIndex());
+ QCOMPARE(rowsRemovedArgs.at(1).toInt(), tma.itemIndex(idx) + 1);
+ QCOMPARE(rowsRemovedArgs.at(2).toInt(), tma.itemIndex(idx) + expectedRowCountDifference);
+
+ // Data changed for the parent's ExpandedRole (value checked above)
+ QCOMPARE(dataChangedSpy.count(), 1);
+ const QVariantList &dataChangedArgs = dataChangedSpy.first();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaIdx);
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaIdx);
+ QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), QVector<int>(1, QQuickTreeModelAdaptor::ExpandedRole));
+ } else {
+ QCOMPARE(tma.rowCount(), oldRowCount);
+ QCOMPARE(rowsAboutToBeRemovedSpy.count(), 0);
+ QCOMPARE(rowsRemovedSpy.count(), 0);
+ }
+}
+
+void tst_QQuickTreeModelAdaptor::compareModels(QQuickTreeModelAdaptor &tma, TestModel &model)
+{
+ QModelIndex parent;
+ QStack<QModelIndex> parents;
+ QModelIndex idx = model.index(0, 0);
+ int modelVisibleRows = model.rowCount(parent);
+ for (int i = 0; i < tma.rowCount(); i++) {
+ bool expanded = tma.isExpanded(i);
+ compareData(i, tma, idx, model, expanded);
+ if (expanded) {
+ parents.push(parent);
+ parent = idx;
+ modelVisibleRows += model.rowCount(parent);
+ idx = model.index(0, 0, parent);
+ } else {
+ while (idx.row() == model.rowCount(parent) - 1) {
+ if (parents.isEmpty())
+ break;
+ idx = parent;
+ parent = parents.pop();
+ }
+ idx = model.index(idx.row() + 1, 0, parent);
+ }
+ }
+ QCOMPARE(tma.rowCount(), modelVisibleRows);
+
+ // Duplicates the model inspection above, but provides extra tests
+ QVERIFY(tma.testConsistency());
+}
+
+void tst_QQuickTreeModelAdaptor::setModel()
+{
+ TestModel model(5, 1);
+ QQuickTreeModelAdaptor tma;
+
+ QSignalSpy modelChangedSpy(&tma, SIGNAL(modelChanged(QAbstractItemModel*)));
+ tma.setModel(&model);
+ QCOMPARE(modelChangedSpy.count(), 1);
+ QCOMPARE(tma.model(), &model);
+
+ // Set same model twice
+ tma.setModel(&model);
+ QCOMPARE(modelChangedSpy.count(), 1);
+
+ modelChangedSpy.clear();
+ tma.setModel(0);
+ QCOMPARE(modelChangedSpy.count(), 1);
+ QCOMPARE(tma.model(), static_cast<QAbstractItemModel *>(0));
+}
+
+void tst_QQuickTreeModelAdaptor::modelReset()
+{
+ TestModel model(5, 1);
+ QQuickTreeModelAdaptor tma;
+ tma.setModel(&model);
+
+ QSignalSpy modelAboutToBeResetSpy(&tma, SIGNAL(modelAboutToBeReset()));
+ QSignalSpy modelResetSpy(&tma, SIGNAL(modelReset()));
+
+ // Nothing expanded
+ model.resetModel();
+ QCOMPARE(modelAboutToBeResetSpy.count(), 1);
+ QCOMPARE(modelResetSpy.count(), 1);
+ QCOMPARE(tma.rowCount(), model.rowCount());
+ compareModels(tma, model);
+
+ // Expanded items should not be anymore
+ tma.expand(model.index(0, 0));
+ tma.expand(model.index(2, 0));
+ tma.expand(model.index(2, 0, model.index(2, 0)));
+ modelAboutToBeResetSpy.clear();
+ modelResetSpy.clear();
+ model.resetModel();
+ QCOMPARE(modelAboutToBeResetSpy.count(), 1);
+ QCOMPARE(modelResetSpy.count(), 1);
+ QCOMPARE(tma.rowCount(), model.rowCount());
+ compareModels(tma, model);
+}
+
+void tst_QQuickTreeModelAdaptor::dataAccess()
+{
+ TestModel model(5, 1);
+
+ QQuickTreeModelAdaptor tma;
+ tma.setModel(&model);
+
+ QCOMPARE(tma.rowCount(), model.rowCount());
+ compareModels(tma, model);
+
+ QModelIndex parentIdx = model.index(2, 0);
+ QVERIFY(model.hasChildren(parentIdx));
+ tma.expand(parentIdx);
+ QVERIFY(tma.isExpanded(parentIdx));
+ QCOMPARE(tma.rowCount(), model.rowCount() + model.rowCount(parentIdx));
+ compareModels(tma, model);
+
+ tma.collapse(parentIdx);
+ QCOMPARE(tma.rowCount(), model.rowCount());
+ compareModels(tma, model);
+}
+
+void tst_QQuickTreeModelAdaptor::dataChange()
+{
+ TestModel model(5, 1);
+
+ QQuickTreeModelAdaptor tma;
+ tma.setModel(&model);
+
+ QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
+ const QModelIndex &idx = model.index(2, 0);
+ model.setData(idx, QVariant(), Qt::DisplayRole);
+ QCOMPARE(dataChangedSpy.count(), 1);
+ const QVariantList &dataChangedArgs = dataChangedSpy.first();
+ const QModelIndex &tmaIdx = tma.index(tma.itemIndex(idx));
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaIdx);
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaIdx);
+ QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), QVector<int>(1, Qt::DisplayRole));
+ compareModels(tma, model);
+
+ {
+ // Non expanded children shouldn't emit any signal
+ dataChangedSpy.clear();
+ const QModelIndex &childIdx = model.index(4, 0, idx);
+ model.setData(childIdx, QVariant(), Qt::DisplayRole);
+ QCOMPARE(dataChangedSpy.count(), 0);
+ compareModels(tma, model);
+
+ // But expanded children should
+ tma.expand(idx);
+ QVERIFY(tma.isExpanded(idx));
+ dataChangedSpy.clear(); // expand() emits dataChanged() with ExpandedRole
+ model.setData(childIdx, QVariant(), Qt::DisplayRole);
+ QCOMPARE(dataChangedSpy.count(), 1);
+ const QVariantList &dataChangedArgs = dataChangedSpy.first();
+ const QModelIndex &tmaIdx = tma.index(tma.itemIndex(childIdx));
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaIdx);
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaIdx);
+ QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), QVector<int>(1, Qt::DisplayRole));
+ compareModels(tma, model);
+ }
+}
+
+void tst_QQuickTreeModelAdaptor::groupedDataChange()
+{
+ TestModel model(10, 1);
+ const QModelIndex &topLeftIdx = model.index(1, 0);
+ const QModelIndex &bottomRightIdx = model.index(7, 0);
+
+ QQuickTreeModelAdaptor tma;
+ tma.setModel(&model);
+
+ QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
+ const QVector<int> roles(1, Qt::DisplayRole);
+
+ {
+ // No expanded items
+ model.groupedSetData(topLeftIdx, bottomRightIdx, roles);
+ QCOMPARE(dataChangedSpy.count(), 1);
+ compareModels(tma, model);
+
+ const QModelIndex &tmaTLIdx = tma.index(tma.itemIndex(topLeftIdx));
+ const QModelIndex &tmaBRIdx = tma.index(tma.itemIndex(bottomRightIdx));
+ const QVariantList &dataChangedArgs = dataChangedSpy.first();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaTLIdx);
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaBRIdx);
+ QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), roles);
+ }
+
+ // One item expanded in the group range
+ const QModelIndex &expandedIdx = model.index(4, 0);
+ tma.expand(expandedIdx);
+ QVERIFY(tma.isExpanded(expandedIdx));
+
+ for (int i = 0; i < 2; i++) {
+ const QModelIndex &tmaTLIdx = tma.index(tma.itemIndex(topLeftIdx));
+ const QModelIndex &tmaExpandedIdx = tma.index(tma.itemIndex(expandedIdx));
+ const QModelIndex &tmaExpandedSiblingIdx = tma.index(tma.itemIndex(expandedIdx.sibling(expandedIdx.row() + 1, 0)));
+ const QModelIndex &tmaBRIdx = tma.index(tma.itemIndex(bottomRightIdx));
+
+ dataChangedSpy.clear(); // expand() sends a dataChaned() signal
+ model.groupedSetData(topLeftIdx, bottomRightIdx, roles);
+ QCOMPARE(dataChangedSpy.count(), 2);
+ compareModels(tma, model);
+
+ QVariantList dataChangedArgs = dataChangedSpy.takeFirst();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaTLIdx);
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaExpandedIdx);
+ QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), roles);
+
+ dataChangedArgs = dataChangedSpy.takeFirst();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaExpandedSiblingIdx);
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaBRIdx);
+ QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), roles);
+
+ // Further expanded descendants should not change grouping
+ tma.expand(model.index(0, 0, expandedIdx));
+ QVERIFY(tma.isExpanded(expandedIdx));
+ }
+ tma.collapse(model.index(0, 0, expandedIdx));
+
+ // Let's expand one more and see what happens...
+ const QModelIndex &otherExpandedIdx = model.index(6, 0);
+ tma.expand(otherExpandedIdx);
+ QVERIFY(tma.isExpanded(otherExpandedIdx));
+
+ for (int i = 0; i < 3; i++) {
+ const QModelIndex &tmaTLIdx = tma.index(tma.itemIndex(topLeftIdx));
+ const QModelIndex &tmaExpandedIdx = tma.index(tma.itemIndex(expandedIdx));
+ const QModelIndex &tmaExpandedSiblingIdx = tma.index(tma.itemIndex(expandedIdx.sibling(expandedIdx.row() + 1, 0)));
+ const QModelIndex &tmaOtherExpandedIdx = tma.index(tma.itemIndex(otherExpandedIdx));
+ const QModelIndex &tmaOtherExpandedSiblingIdx = tma.index(tma.itemIndex(otherExpandedIdx.sibling(otherExpandedIdx.row() + 1, 0)));
+ const QModelIndex &tmaBRIdx = tma.index(tma.itemIndex(bottomRightIdx));
+
+ dataChangedSpy.clear(); // expand() sends a dataChaned() signal
+ model.groupedSetData(topLeftIdx, bottomRightIdx, roles);
+ QCOMPARE(dataChangedSpy.count(), 3);
+ compareModels(tma, model);
+
+ QVariantList dataChangedArgs = dataChangedSpy.takeFirst();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaTLIdx);
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaExpandedIdx);
+ QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), roles);
+
+ dataChangedArgs = dataChangedSpy.takeFirst();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaExpandedSiblingIdx);
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaOtherExpandedIdx);
+ QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), roles);
+
+ dataChangedArgs = dataChangedSpy.takeFirst();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tmaOtherExpandedSiblingIdx);
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tmaBRIdx);
+ QCOMPARE(dataChangedArgs.at(2).value<QVector<int> >(), roles);
+
+ // Further expanded descendants should not change grouping
+ if (i == 0) {
+ tma.expand(model.index(0, 0, expandedIdx));
+ QVERIFY(tma.isExpanded(expandedIdx));
+ } else {
+ tma.expand(model.index(0, 0, otherExpandedIdx));
+ QVERIFY(tma.isExpanded(expandedIdx));
+ }
+ }
+}
+
+void tst_QQuickTreeModelAdaptor::expandAndCollapse_data()
+{
+ QTest::addColumn<int>("parentRow");
+ QTest::newRow("First") << 0;
+ QTest::newRow("Middle") << 2;
+ QTest::newRow("Last") << 4;
+ QTest::newRow("Non expandable") << 3;
+}
+
+void tst_QQuickTreeModelAdaptor::expandAndCollapse()
+{
+ QFETCH(int, parentRow);
+ TestModel model(5, 1);
+ const QModelIndex &parentIdx = model.index(parentRow, 0);
+ bool expandable = model.hasChildren(parentIdx);
+
+ QQuickTreeModelAdaptor tma;
+ tma.setModel(&model);
+
+ expandAndTest(parentIdx, tma, expandable, model.rowCount(parentIdx));
+ compareModels(tma, model);
+
+ collapseAndTest(parentIdx, tma, expandable, model.rowCount(parentIdx));
+ compareModels(tma, model);
+}
+
+void tst_QQuickTreeModelAdaptor::expandAndCollapse2ndLevel()
+{
+ const int expandRows[] = { 0, 2, 4, 3 };
+ const int expandRowsCount = sizeof(expandRows) / sizeof(expandRows[0]);
+ for (int i = 0; i < expandRowsCount - 1; i++) { // Skip last non-expandable row
+ TestModel model(5, 1);
+ const QModelIndex &parentIdx = model.index(expandRows[i], 0);
+ QVERIFY(model.hasChildren(parentIdx));
+
+ QQuickTreeModelAdaptor tma;
+ tma.setModel(&model);
+
+ tma.expand(parentIdx);
+ QVERIFY(tma.isExpanded(parentIdx));
+ QCOMPARE(tma.rowCount(), model.rowCount() + model.rowCount(parentIdx));
+
+ for (int j = 0; j < expandRowsCount; j++) {
+ const QModelIndex &childIdx = model.index(expandRows[j], 0, parentIdx);
+ bool expandable = model.hasChildren(childIdx);
+
+ // Expand child
+ expandAndTest(childIdx, tma, expandable, model.rowCount(childIdx));
+ compareModels(tma, model);
+ // Collapse child
+ collapseAndTest(childIdx, tma, expandable, model.rowCount(childIdx));
+ compareModels(tma, model);
+
+ // Expand child again
+ expandAndTest(childIdx, tma, expandable, model.rowCount(childIdx));
+ compareModels(tma, model);
+ // Collapse parent -> child node invisible, but expanded
+ collapseAndTest(parentIdx, tma, true, model.rowCount(parentIdx) + model.rowCount(childIdx));
+ compareModels(tma, model);
+ QCOMPARE(tma.isExpanded(childIdx), expandable);
+ // Expand parent again
+ expandAndTest(parentIdx, tma, true, model.rowCount(parentIdx) + model.rowCount(childIdx));
+ compareModels(tma, model);
+
+ // Collapse parent -> child node invisible, but expanded
+ collapseAndTest(parentIdx, tma, true, model.rowCount(parentIdx) + model.rowCount(childIdx));
+ compareModels(tma, model);
+ QCOMPARE(tma.isExpanded(childIdx), expandable);
+ // Collapse child -> nothing should change
+ collapseAndTest(childIdx, tma, false, 0);
+ compareModels(tma, model);
+ // Expand parent again
+ expandAndTest(parentIdx, tma, true, model.rowCount(parentIdx));
+ compareModels(tma, model);
+
+ // Expand child, one last time
+ expandAndTest(childIdx, tma, expandable, model.rowCount(childIdx));
+ compareModels(tma, model);
+ // Collapse child, and done
+ collapseAndTest(childIdx, tma, expandable, model.rowCount(childIdx));
+ compareModels(tma, model);
+ }
+ }
+}
+
+void tst_QQuickTreeModelAdaptor::layoutChange()
+{
+ TestModel model(5, 1);
+ const QModelIndex &idx = model.index(0, 0);
+ const QModelIndex &idx2 = model.index(2, 0);
+
+ QQuickTreeModelAdaptor tma;
+ tma.setModel(&model);
+
+ // Nothing expanded
+ QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
+ model.changeLayout();
+ QCOMPARE(dataChangedSpy.count(), 1);
+ QVariantList dataChangedArgs = dataChangedSpy.takeFirst();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tma.index(0));
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tma.index(tma.rowCount() - 1));
+ QVERIFY(dataChangedArgs.at(2).value<QVector<int> >().isEmpty());
+ compareModels(tma, model);
+
+ // One item expanded
+ tma.expand(idx);
+ QVERIFY(tma.isExpanded(idx));
+ dataChangedSpy.clear();
+ model.changeLayout();
+ QCOMPARE(dataChangedSpy.count(), 1);
+ dataChangedArgs = dataChangedSpy.takeFirst();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tma.index(0));
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tma.index(tma.rowCount() - 1));
+ QVERIFY(dataChangedArgs.at(2).value<QVector<int> >().isEmpty());
+ compareModels(tma, model);
+
+ // One parent layout change, expanded
+ dataChangedSpy.clear();
+ QList<QPersistentModelIndex> parents;
+ parents << idx;
+ model.changeLayout(parents);
+ QCOMPARE(dataChangedSpy.count(), 1);
+ dataChangedArgs = dataChangedSpy.takeFirst();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tma.index(tma.itemIndex(model.index(0, 0, idx))));
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tma.index(tma.itemIndex(model.index(model.rowCount(idx) - 1, 0, idx))));
+ QVERIFY(dataChangedArgs.at(2).value<QVector<int> >().isEmpty());
+ compareModels(tma, model);
+
+ // One parent layout change, collapsed
+ tma.collapse(idx);
+ dataChangedSpy.clear();
+ model.changeLayout(parents);
+ QCOMPARE(dataChangedSpy.count(), 0);
+ compareModels(tma, model);
+
+ // Two-parent layout change, both collapsed
+ parents << idx2;
+ dataChangedSpy.clear();
+ model.changeLayout(parents);
+ QCOMPARE(dataChangedSpy.count(), 0);
+ compareModels(tma, model);
+
+ // Two-parent layout change, only one expanded
+ tma.expand(idx2);
+ QVERIFY(tma.isExpanded(idx2));
+ dataChangedSpy.clear();
+ model.changeLayout(parents);
+ QCOMPARE(dataChangedSpy.count(), 1);
+ dataChangedArgs = dataChangedSpy.takeFirst();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tma.index(tma.itemIndex(model.index(0, 0, idx2))));
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tma.index(tma.itemIndex(model.index(model.rowCount(idx2) - 1, 0, idx2))));
+ QVERIFY(dataChangedArgs.at(2).value<QVector<int> >().isEmpty());
+ compareModels(tma, model);
+
+ // Two-parent layout change, both expanded
+ tma.expand(idx);
+ QVERIFY(tma.isExpanded(idx));
+ dataChangedSpy.clear();
+ model.changeLayout(parents);
+ QCOMPARE(dataChangedSpy.count(), 2);
+ dataChangedArgs = dataChangedSpy.takeFirst();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tma.index(tma.itemIndex(model.index(0, 0, idx))));
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tma.index(tma.itemIndex(model.index(model.rowCount(idx) - 1, 0, idx))));
+ QVERIFY(dataChangedArgs.at(2).value<QVector<int> >().isEmpty());
+ dataChangedArgs = dataChangedSpy.takeFirst();
+ QCOMPARE(dataChangedArgs.at(0).toModelIndex(), tma.index(tma.itemIndex(model.index(0, 0, idx2))));
+ QCOMPARE(dataChangedArgs.at(1).toModelIndex(), tma.index(tma.itemIndex(model.index(model.rowCount(idx2) - 1, 0, idx2))));
+ QVERIFY(dataChangedArgs.at(2).value<QVector<int> >().isEmpty());
+ compareModels(tma, model);
+}
+
+static const int ModelRowCount = 9;
+
+void tst_QQuickTreeModelAdaptor::removeRows_data()
+{
+ QTest::addColumn<int>("removeFromRow");
+ QTest::addColumn<int>("removeCount");
+ QTest::addColumn<int>("removeParentRow");
+ QTest::addColumn<int>("expandRow");
+ QTest::addColumn<int>("expandParentRow");
+ QTest::addColumn<int>("expectedRemovedCount");
+
+ QTest::newRow("Nothing expanded, remove 1st row") << 0 << 1 << -1 << -1 << -1 << 1;
+ QTest::newRow("Expand 1st row, remove 1st row") << 0 << 1 << -1 << 0 << -1 << 1 + ModelRowCount;
+ QTest::newRow("Expand last row, remove 1st row") << 0 << 1 << -1 << ModelRowCount - 1 << -1 << 1;
+ QTest::newRow("Nothing expanded, remove last row") << ModelRowCount - 1 << 1 << -1 << -1 << -1 << 1;
+ QTest::newRow("Expand 1st row, remove last row") << ModelRowCount - 1 << 1 << -1 << 0 << -1 << 1;
+ QTest::newRow("Expand last row, remove last row") << ModelRowCount - 1 << 1 << -1 << ModelRowCount - 1 << -1 << 1 + ModelRowCount;
+ QTest::newRow("Remove child row, parent collapsed") << 2 << 1 << 0 << -1 << -1 << 0;
+ QTest::newRow("Remove child row, parent expanded") << 2 << 1 << 0 << 0 << -1 << 1;
+ QTest::newRow("Remove several rows, nothing expanded") << 2 << 5 << -1 << -1 << -1 << 5;
+ QTest::newRow("Remove several rows, 1st row expanded") << 2 << 5 << -1 << 0 << -1 << 5;
+ QTest::newRow("Remove several rows, last row expanded") << 2 << 5 << -1 << ModelRowCount - 1 << -1 << 5;
+ QTest::newRow("Remove several rows, one of them expanded") << 2 << 5 << -1 << 4 << -1 << 5 + ModelRowCount;
+ QTest::newRow("Remove all rows, nothing expanded") << 0 << ModelRowCount << -1 << -1 << -1 << ModelRowCount;
+ QTest::newRow("Remove all rows, 1st row expanded") << 0 << ModelRowCount << -1 << 0 << -1 << ModelRowCount * 2;
+ QTest::newRow("Remove all rows, last row expanded") << 0 << ModelRowCount << -1 << ModelRowCount - 1 << -1 << ModelRowCount * 2;
+ QTest::newRow("Remove all rows, random one expanded") << 0 << ModelRowCount << -1 << 4 << -1 << ModelRowCount * 2;
+}
+
+void tst_QQuickTreeModelAdaptor::removeRows()
+{
+ QFETCH(int, removeFromRow);
+ QFETCH(int, removeCount);
+ QFETCH(int, removeParentRow);
+ QFETCH(int, expandRow);
+ QFETCH(int, expandParentRow);
+ QFETCH(int, expectedRemovedCount);
+
+ TestModel model(ModelRowCount, 1);
+ QQuickTreeModelAdaptor tma;
+ tma.setModel(&model);
+
+ const QModelIndex &expandParentIdx = expandParentRow == -1 ? QModelIndex() : model.index(expandParentRow, 0);
+ if (expandParentIdx.isValid()) {
+ tma.expand(expandParentIdx);
+ QVERIFY(tma.isExpanded(expandParentIdx));
+ }
+ const QModelIndex &expandIdx = model.index(expandRow, 0, expandParentIdx);
+ if (expandIdx.isValid()) {
+ tma.expand(expandIdx);
+ QVERIFY(tma.isExpanded(expandIdx));
+ }
+
+ const QModelIndex &removeParentIdx = removeParentRow == -1 ? QModelIndex() : model.index(removeParentRow, 0);
+ const QModelIndex &removeIdx = model.index(removeFromRow, 0, removeParentIdx);
+ int tmaItemIdx = tma.itemIndex(removeIdx);
+
+ QSignalSpy rowsAboutToBeRemovedSpy(&tma, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)));
+ QSignalSpy rowsRemovedSpy(&tma, SIGNAL(rowsRemoved(const QModelIndex&, int, int)));
+ model.removeRows(removeFromRow, removeCount, removeParentIdx);
+ if (expectedRemovedCount == 0) {
+ QCOMPARE(rowsAboutToBeRemovedSpy.count(), 0);
+ QCOMPARE(rowsRemovedSpy.count(), 0);
+ } else {
+ QCOMPARE(rowsAboutToBeRemovedSpy.count(), 1);
+ QCOMPARE(rowsRemovedSpy.count(), 1);
+ QVariantList rowsAboutToBeRemovedArgs = rowsAboutToBeRemovedSpy.first();
+ QVariantList rowsRemovedArgs = rowsRemovedSpy.first();
+ QCOMPARE(rowsAboutToBeRemovedArgs, rowsRemovedArgs);
+ QCOMPARE(rowsAboutToBeRemovedArgs.at(0).toModelIndex(), QModelIndex());
+ QCOMPARE(rowsAboutToBeRemovedArgs.at(1).toInt(), tmaItemIdx);
+ QCOMPARE(rowsAboutToBeRemovedArgs.at(2).toInt(), tmaItemIdx + expectedRemovedCount - 1);
+ }
+}
+
+void tst_QQuickTreeModelAdaptor::insertRows_data()
+{
+ QTest::addColumn<int>("insertFromRow");
+ QTest::addColumn<int>("insertCount");
+ QTest::addColumn<int>("insertParentRow");
+ QTest::addColumn<int>("expandRow");
+ QTest::addColumn<int>("expandParentRow");
+ QTest::addColumn<int>("expectedInsertedCount");
+
+ QTest::newRow("Nothing expanded, insert 1st row") << 0 << 1 << -1 << -1 << -1 << 1;
+ QTest::newRow("Expand 1st row, insert 1st row") << 0 << 1 << -1 << 0 << -1 << 1;
+ QTest::newRow("Expand last row, insert 1st row") << 0 << 1 << -1 << ModelRowCount - 1 << -1 << 1;
+ QTest::newRow("Nothing expanded, insert before the last row") << ModelRowCount - 1 << 1 << -1 << -1 << -1 << 1;
+ QTest::newRow("Nothing expanded, insert after the last row") << ModelRowCount << 1 << -1 << -1 << -1 << 1;
+ QTest::newRow("Expand 1st row, insert before the last row") << ModelRowCount - 1 << 1 << -1 << 0 << -1 << 1;
+ QTest::newRow("Expand 1st row, insert after the last row") << ModelRowCount << 1 << -1 << 0 << -1 << 1;
+ QTest::newRow("Expand last row, insert before the last row") << ModelRowCount - 1 << 1 << -1 << ModelRowCount - 1 << -1 << 1;
+ QTest::newRow("Expand last row, insert after the last row") << ModelRowCount << 1 << -1 << ModelRowCount - 1 << -1 << 1;
+ QTest::newRow("Insert child row, parent collapsed") << 2 << 1 << 0 << -1 << -1 << 0;
+ QTest::newRow("Insert child row, parent expanded") << 2 << 1 << 0 << 0 << -1 << 1;
+ QTest::newRow("Insert several rows, nothing expanded") << 2 << 5 << -1 << -1 << -1 << 5;
+ QTest::newRow("Insert several rows, 1st row expanded") << 2 << 5 << -1 << 0 << -1 << 5;
+ QTest::newRow("Insert several rows, last row expanded") << 2 << 5 << -1 << ModelRowCount - 1 << -1 << 5;
+}
+
+void tst_QQuickTreeModelAdaptor::insertRows()
+{
+ QFETCH(int, insertFromRow);
+ QFETCH(int, insertCount);
+ QFETCH(int, insertParentRow);
+ QFETCH(int, expandRow);
+ QFETCH(int, expandParentRow);
+ QFETCH(int, expectedInsertedCount);
+
+ TestModel model(ModelRowCount, 1);
+ QQuickTreeModelAdaptor tma;
+ tma.setModel(&model);
+
+ const QModelIndex &expandParentIdx = expandParentRow == -1 ? QModelIndex() : model.index(expandParentRow, 0);
+ if (expandParentIdx.isValid()) {
+ tma.expand(expandParentIdx);
+ QVERIFY(tma.isExpanded(expandParentIdx));
+ }
+ const QModelIndex &expandIdx = model.index(expandRow, 0, expandParentIdx);
+ if (expandIdx.isValid()) {
+ tma.expand(expandIdx);
+ QVERIFY(tma.isExpanded(expandIdx));
+ }
+
+ const QModelIndex &insertParentIdx = insertParentRow == -1 ? QModelIndex() : model.index(insertParentRow, 0);
+ const QModelIndex &insertIdx = model.index(insertFromRow, 0, insertParentIdx);
+ int tmaItemIdx = insertFromRow == model.rowCount(insertParentIdx) ? tma.rowCount() : tma.itemIndex(insertIdx);
+
+ QSignalSpy rowsAboutToBeInsertedSpy(&tma, SIGNAL(rowsAboutToBeInserted(const QModelIndex&, int, int)));
+ QSignalSpy rowsInsertedSpy(&tma, SIGNAL(rowsInserted(const QModelIndex&, int, int)));
+ model.insertRows(insertFromRow, insertCount, insertParentIdx);
+ if (expectedInsertedCount == 0) {
+ QCOMPARE(rowsAboutToBeInsertedSpy.count(), 0);
+ QCOMPARE(rowsInsertedSpy.count(), 0);
+ } else {
+ QCOMPARE(rowsAboutToBeInsertedSpy.count(), 1);
+ QCOMPARE(rowsInsertedSpy.count(), 1);
+ QVariantList rowsAboutToBeInsertedArgs = rowsAboutToBeInsertedSpy.first();
+ QVariantList rowsInsertedArgs = rowsInsertedSpy.first();
+ QCOMPARE(rowsAboutToBeInsertedArgs, rowsInsertedArgs);
+ QCOMPARE(rowsAboutToBeInsertedArgs.at(0).toModelIndex(), QModelIndex());
+ QCOMPARE(rowsAboutToBeInsertedArgs.at(1).toInt(), tmaItemIdx);
+ QCOMPARE(rowsAboutToBeInsertedArgs.at(2).toInt(), tmaItemIdx + expectedInsertedCount - 1);
+ QCOMPARE(tma.itemIndex(model.index(insertFromRow, 0, insertParentIdx)), tmaItemIdx);
+ }
+}
+
+enum MoveSignalType {
+ RowsMoved = 0, RowsInserted, RowsRemoved
+};
+
+void tst_QQuickTreeModelAdaptor::moveRows_data()
+{
+ QTest::addColumn<int>("sourceRow");
+ QTest::addColumn<bool>("expandSource");
+ QTest::addColumn<int>("moveCount");
+ QTest::addColumn<int>("sourceParentRow");
+ QTest::addColumn<bool>("expandSourceParent");
+ QTest::addColumn<int>("destRow");
+ QTest::addColumn<bool>("expandDest");
+ QTest::addColumn<int>("destParentRow");
+ QTest::addColumn<bool>("expandDestParent");
+ QTest::addColumn<int>("expandRow");
+ QTest::addColumn<int>("expandParentRow");
+ QTest::addColumn<int>("signalType");
+ QTest::addColumn<int>("expectedMovedCount");
+
+ QTest::newRow("From and to top-level parent")
+ << 0 << false << 1 << -1 << false
+ << 3 << false << -1 << false
+ << -1 << -1 << (int)RowsMoved << 1;
+ QTest::newRow("From and to top-level parent, expanded")
+ << 0 << true << 1 << -1 << false
+ << 3 << false << -1 << false
+ << -1 << -1 << (int)RowsMoved << ModelRowCount + 1;
+ QTest::newRow("From and to top-level parent, backwards")
+ << 4 << false << 1 << -1 << false
+ << 0 << false << -1 << false
+ << -1 << -1 << (int)RowsMoved << 1;
+ QTest::newRow("From and to top-level parent, expanded, backwards")
+ << 4 << true << 1 << -1 << false
+ << 0 << false << -1 << false
+ << -1 << -1 << (int)RowsMoved << ModelRowCount + 1;
+ QTest::newRow("Moving between collapsed parents")
+ << 0 << false << 1 << 0 << false
+ << 0 << false << 2 << false
+ << -1 << -1 << (int)RowsMoved << 0;
+ QTest::newRow("From expanded parent to collapsed parent")
+ << 0 << false << 1 << 0 << true
+ << 0 << false << 2 << false
+ << -1 << -1 << (int)RowsRemoved << 1;
+ QTest::newRow("From collapsed parent to expanded parent")
+ << 0 << false << 1 << 0 << false
+ << 0 << false << 2 << true
+ << -1 << -1 << (int)RowsInserted << 1;
+ QTest::newRow("From and to same expanded parent")
+ << 0 << false << 1 << 0 << true
+ << 2 << false << 0 << false
+ << -1 << -1 << (int)RowsMoved << 1;
+ QTest::newRow("From expanded parent to collapsed parent, expanded row")
+ << 0 << true << 1 << 0 << true
+ << 0 << false << 2 << false
+ << -1 << -1 << (int)RowsRemoved << ModelRowCount + 1;
+ QTest::newRow("From collapsed parent to expanded parent, expanded row")
+ << 0 << true << 1 << 0 << false
+ << 0 << false << 2 << true
+ << -1 << -1 << (int)RowsInserted << ModelRowCount + 1;
+ QTest::newRow("From and to same expanded parent, expanded row, forward")
+ << 0 << true << 1 << 0 << true
+ << 5 << false << 0 << false
+ << -1 << -1 << (int)RowsMoved << ModelRowCount + 1;
+ QTest::newRow("From and to same expanded parent, expanded row, last row")
+ << 0 << true << 1 << 0 << true
+ << ModelRowCount << false << 0 << false
+ << -1 << -1 << (int)RowsMoved << ModelRowCount + 1;
+ QTest::newRow("From and to same expanded parent, expanded row, several")
+ << 0 << true << 3 << 0 << true
+ << 5 << false << 0 << false
+ << -1 << -1 << (int)RowsMoved << ModelRowCount + 3;
+ QTest::newRow("From and to same expanded parent, expanded row, backward")
+ << 6 << true << 1 << 0 << true
+ << 0 << false << 0 << false
+ << -1 << -1 << (int)RowsMoved << ModelRowCount + 1;
+ QTest::newRow("From and to same expanded parent, expanded row, several, backward")
+ << 6 << true << 2 << 0 << true
+ << 0 << false << 0 << false
+ << -1 << -1 << (int)RowsMoved << ModelRowCount + 2;
+ QTest::newRow("From and to different expanded parents")
+ << 0 << false << 1 << 0 << true
+ << 1 << false << 4 << true
+ << -1 << -1 << (int)RowsMoved << 1;
+ QTest::newRow("From and to different expanded parents, backward")
+ << 0 << false << 1 << 4 << true
+ << 2 << false << 0 << true
+ << -1 << -1 << (int)RowsMoved << 1;
+ QTest::newRow("From and to different expanded parents, up in level")
+ << 0 << false << 1 << 0 << true
+ << 5 << true << -1 << true
+ << -1 << -1 << (int)RowsMoved << 1;
+ QTest::newRow("From and to different expanded parents, up in level, backwards")
+ << 0 << false << 1 << 4 << true
+ << 1 << false << -1 << true
+ << -1 << -1 << (int)RowsMoved << 1;
+ QTest::newRow("From and to different expanded parents, up in level, as 1st item")
+ << 0 << false << 1 << 0 << true
+ << 0 << false << -1 << true
+ << -1 << -1 << (int)RowsMoved << 1;
+ QTest::newRow("From and to different expanded parents, backward, up in level")
+ << 0 << false << 1 << 4 << true
+ << 2 << false << 0 << true
+ << -1 << -1 << (int)RowsMoved << 1;
+}
+
+void tst_QQuickTreeModelAdaptor::moveRows()
+{
+ QFETCH(int, sourceRow);
+ QFETCH(bool, expandSource);
+ QFETCH(int, moveCount);
+ QFETCH(int, sourceParentRow);
+ QFETCH(bool, expandSourceParent);
+ QFETCH(int, destRow);
+ QFETCH(bool, expandDest);
+ QFETCH(int, destParentRow);
+ QFETCH(bool, expandDestParent);
+ QFETCH(int, expandRow);
+ QFETCH(int, expandParentRow);
+ QFETCH(int, signalType);
+ QFETCH(int, expectedMovedCount);
+
+ TestModel model(ModelRowCount, 1);
+ model.alternateChildlessRows = false;
+ QQuickTreeModelAdaptor tma;
+ tma.setModel(&model);
+
+ const QModelIndex &expandParentIdx = expandParentRow == -1 ? QModelIndex() : model.index(expandParentRow, 0);
+ if (expandParentIdx.isValid()) {
+ tma.expand(expandParentIdx);
+ QVERIFY(tma.isExpanded(expandParentIdx));
+ }
+ const QModelIndex &expandIdx = model.index(expandRow, 0, expandParentIdx);
+ if (expandIdx.isValid()) {
+ tma.expand(expandIdx);
+ QVERIFY(tma.isExpanded(expandIdx));
+ }
+
+ const QModelIndex &sourceParentIdx = sourceParentRow == -1 ? QModelIndex() : model.index(sourceParentRow, 0);
+ if (expandSourceParent && sourceParentIdx.isValid()) {
+ tma.expand(sourceParentIdx);
+ QVERIFY(tma.isExpanded(sourceParentIdx));
+ }
+ const QModelIndex &sourceIdx = model.index(sourceRow, 0, sourceParentIdx);
+ if (expandSource) {
+ tma.expand(sourceIdx);
+ QVERIFY(tma.isExpanded(sourceIdx));
+ }
+
+ const QModelIndex &destParentIdx = destParentRow == -1 ? QModelIndex() : model.index(destParentRow, 0);
+ if (expandDestParent && destParentIdx.isValid()) {
+ tma.expand(destParentIdx);
+ QVERIFY(tma.isExpanded(destParentIdx));
+ }
+ const QModelIndex &destIdx = model.index(destRow, 0, destParentIdx);
+ if (expandDest) {
+ tma.expand(destIdx);
+ QVERIFY(tma.isExpanded(destIdx));
+ }
+
+ int tmaSourceItemIdx = signalType == RowsInserted ? -1 // Not tested if RowsInserted
+ : tma.itemIndex(sourceIdx);
+ int tmaDestItemIdx = signalType == RowsRemoved ? -1 : // Not tested if RowsRemoved
+ destRow == model.rowCount(destParentIdx) ? -1 /* FIXME */ : tma.itemIndex(destIdx);
+
+ QSignalSpy rowsAboutToBeMovedSpy(&tma, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)));
+ QSignalSpy rowsMovedSpy(&tma, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)));
+ QSignalSpy rowsAboutToBeInsertedSpy(&tma, SIGNAL(rowsAboutToBeInserted(const QModelIndex&, int, int)));
+ QSignalSpy rowsInsertedSpy(&tma, SIGNAL(rowsInserted(const QModelIndex&, int, int)));
+ QSignalSpy rowsAboutToBeRemovedSpy(&tma, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)));
+ QSignalSpy rowsRemovedSpy(&tma, SIGNAL(rowsRemoved(const QModelIndex&, int, int)));
+
+ QVERIFY(model.moveRows(sourceParentIdx, sourceRow, moveCount, destParentIdx, destRow));
+
+ if (signalType != RowsMoved || expectedMovedCount == 0) {
+ QCOMPARE(rowsAboutToBeMovedSpy.count(), 0);
+ QCOMPARE(rowsMovedSpy.count(), 0);
+ }
+ if (signalType != RowsInserted || expectedMovedCount == 0) {
+ QCOMPARE(rowsAboutToBeInsertedSpy.count(), 0);
+ QCOMPARE(rowsInsertedSpy.count(), 0);
+ }
+ if (signalType != RowsRemoved || expectedMovedCount == 0) {
+ QCOMPARE(rowsAboutToBeRemovedSpy.count(), 0);
+ QCOMPARE(rowsRemovedSpy.count(), 0);
+ }
+
+ if (expectedMovedCount != 0) {
+ if (signalType == RowsMoved) {
+ QCOMPARE(rowsAboutToBeMovedSpy.count(), 1);
+ QCOMPARE(rowsMovedSpy.count(), 1);
+ QVariantList rowsAboutToBeMovedArgs = rowsAboutToBeMovedSpy.first();
+ QVariantList rowsMovedArgs = rowsMovedSpy.first();
+ QCOMPARE(rowsAboutToBeMovedArgs, rowsMovedArgs);
+ QCOMPARE(rowsAboutToBeMovedArgs.at(0).toModelIndex(), QModelIndex());
+ QCOMPARE(rowsAboutToBeMovedArgs.at(1).toInt(), tmaSourceItemIdx);
+ QCOMPARE(rowsAboutToBeMovedArgs.at(2).toInt(), tmaSourceItemIdx + expectedMovedCount - 1);
+ QCOMPARE(rowsAboutToBeMovedArgs.at(3).toModelIndex(), QModelIndex());
+ if (tmaDestItemIdx != -1)
+ QCOMPARE(rowsAboutToBeMovedArgs.at(4).toInt(), tmaDestItemIdx);
+ } else if (signalType == RowsInserted) {
+ // We only test with one level of expanded children here, so we can do
+ // exhaustive testing depending on whether the moved row is expanded.
+ int signalCount = expandSource ? 2 : 1;
+ QCOMPARE(rowsAboutToBeInsertedSpy.count(), signalCount);
+ QCOMPARE(rowsInsertedSpy.count(), signalCount);
+ QVariantList rowsAboutToBeInsertedArgs = rowsAboutToBeInsertedSpy.takeFirst();
+ QVariantList rowsInsertedArgs = rowsInsertedSpy.takeFirst();
+ QCOMPARE(rowsAboutToBeInsertedArgs, rowsInsertedArgs);
+ QCOMPARE(rowsAboutToBeInsertedArgs.at(0).toModelIndex(), QModelIndex());
+ QCOMPARE(rowsAboutToBeInsertedArgs.at(1).toInt(), tmaDestItemIdx);
+ if (expandSource) {
+ QCOMPARE(rowsAboutToBeInsertedArgs.at(2).toInt(), tmaDestItemIdx);
+ rowsAboutToBeInsertedArgs = rowsAboutToBeInsertedSpy.first();
+ rowsInsertedArgs = rowsInsertedSpy.first();
+ QCOMPARE(rowsAboutToBeInsertedArgs, rowsInsertedArgs);
+ QCOMPARE(rowsAboutToBeInsertedArgs.at(0).toModelIndex(), QModelIndex());
+ QCOMPARE(rowsAboutToBeInsertedArgs.at(1).toInt(), tmaDestItemIdx + 1);
+ }
+ QCOMPARE(rowsAboutToBeInsertedArgs.at(2).toInt(), tmaDestItemIdx + expectedMovedCount - 1);
+ QCOMPARE(tma.itemIndex(model.index(destRow, 0, destParentIdx)), tmaDestItemIdx);
+ } else if (signalType == RowsRemoved) {
+ QCOMPARE(rowsAboutToBeRemovedSpy.count(), 1);
+ QCOMPARE(rowsRemovedSpy.count(), 1);
+ QVariantList rowsAboutToBeRemovedArgs = rowsAboutToBeRemovedSpy.first();
+ QVariantList rowsRemovedArgs = rowsRemovedSpy.first();
+ QCOMPARE(rowsAboutToBeRemovedArgs, rowsRemovedArgs);
+ QCOMPARE(rowsAboutToBeRemovedArgs.at(0).toModelIndex(), QModelIndex());
+ QCOMPARE(rowsAboutToBeRemovedArgs.at(1).toInt(), tmaSourceItemIdx);
+ QCOMPARE(rowsAboutToBeRemovedArgs.at(2).toInt(), tmaSourceItemIdx + expectedMovedCount - 1);
+ }
+ }
+ QVERIFY(tma.testConsistency());
+ compareModels(tma, model);
+}
+
+void tst_QQuickTreeModelAdaptor::selectionForRowRange()
+{
+ const int ModelRowCount = 9;
+ const int ModelRowCountLoopStep = 4;
+
+ TestModel model(ModelRowCount, 1);
+ model.alternateChildlessRows = false;
+ QQuickTreeModelAdaptor tma;
+ tma.setModel(&model);
+
+ // NOTE: Some selections may look a bit cryptic. Insert a call to
+ // tma.dump() before each block if you need to see what's going on.
+
+ for (int i = 0; i < ModelRowCount; i += ModelRowCountLoopStep) {
+ // Single row selection
+ const QItemSelection &sel = tma.selectionForRowRange(i, i);
+ QCOMPARE(sel.count(), 1);
+ const QItemSelectionRange &range = sel.first();
+ QCOMPARE(QModelIndex(range.topLeft()), model.index(i, 0));
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(i, 0));
+ }
+
+ for (int i = 0; i < ModelRowCount - ModelRowCountLoopStep; i += ModelRowCountLoopStep) {
+ // Single range selection
+ const QItemSelection &sel = tma.selectionForRowRange(i, i + ModelRowCountLoopStep);
+ QCOMPARE(sel.count(), 1);
+ const QItemSelectionRange &range = sel.first();
+ QCOMPARE(QModelIndex(range.topLeft()), model.index(i, 0));
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(i + ModelRowCountLoopStep, 0));
+ }
+
+ { // Select all, no branch expanded
+ const QItemSelection &sel = tma.selectionForRowRange(0, ModelRowCount - 1);
+ QCOMPARE(sel.count(), 1);
+ const QItemSelectionRange &range = sel.first();
+ QCOMPARE(QModelIndex(range.topLeft()), model.index(0, 0));
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0));
+ }
+
+ // Expand 1st top-level item
+ const QModelIndex &parent = model.index(0, 0);
+ tma.expand(parent);
+
+ { // 1st item expanded, select first 5 rows
+ const QItemSelection &sel = tma.selectionForRowRange(0, 4);
+ QCOMPARE(sel.count(), 2);
+ // We don't know in which order the selection ranges are
+ // being added, so we iterate and try to find what we expect.
+ foreach (const QItemSelectionRange &range, sel) {
+ if (range.topLeft() == model.index(0, 0))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(0, 0));
+ else if (range.topLeft() == model.index(0, 0, parent))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(3, 0, parent));
+ else
+ QFAIL("Unexpected selection range");
+ }
+ }
+
+ { // 1st item expanded, select first 5 top-level items
+ const QItemSelection &sel = tma.selectionForRowRange(0, 4 + ModelRowCount);
+ QCOMPARE(sel.count(), 2);
+ // We don't know in which order the selection ranges are
+ // being added, so we iterate and try to find what we expect.
+ foreach (const QItemSelectionRange &range, sel) {
+ if (range.topLeft() == model.index(0, 0))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(4, 0));
+ else if (range.topLeft() == model.index(0, 0, parent))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0, parent));
+ else
+ QFAIL("Unexpected selection range");
+ }
+ }
+
+ // Expand 2nd top-level item
+ const QModelIndex &parent2 = model.index(1, 0);
+ tma.expand(parent2);
+
+ { // 1st two items expanded, select first 5 top-level items
+ const QItemSelection &sel = tma.selectionForRowRange(0, 4 + 2 * ModelRowCount);
+ QCOMPARE(sel.count(), 3);
+ // We don't know in which order the selection ranges are
+ // being added, so we iterate and try to find what we expect.
+ foreach (const QItemSelectionRange &range, sel) {
+ if (range.topLeft() == model.index(0, 0))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(4, 0));
+ else if (range.topLeft() == model.index(0, 0, parent))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0, parent));
+ else if (range.topLeft() == model.index(0, 0, parent2))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0, parent2));
+ else
+ QFAIL("Unexpected selection range");
+ }
+ }
+
+ // Expand 1st child of 1st top-level item
+ const QModelIndex &parent3 = model.index(0, 0, parent);
+ tma.expand(parent3);
+
+ { // 1st two items, and 1st child of 1st item expanded, select first 5 rows
+ const QItemSelection &sel = tma.selectionForRowRange(0, 4);
+ QCOMPARE(sel.count(), 3);
+ // We don't know in which order the selection ranges are
+ // being added, so we iterate and try to find what we expect.
+ foreach (const QItemSelectionRange &range, sel) {
+ if (range.topLeft() == model.index(0, 0))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(0, 0));
+ else if (range.topLeft() == model.index(0, 0, parent))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(0, 0, parent));
+ else if (range.topLeft() == model.index(0, 0, parent3))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(2, 0, parent3));
+ else
+ QFAIL("Unexpected selection range");
+ }
+ }
+
+ { // 1st two items, and 1st child of 1st item expanded, select all
+ const QItemSelection &sel = tma.selectionForRowRange(0, 4 * ModelRowCount - 1);
+ QCOMPARE(sel.count(), 4);
+ // We don't know in which order the selection ranges are
+ // being added, so we iterate and try to find what we expect.
+ foreach (const QItemSelectionRange &range, sel) {
+ if (range.topLeft() == model.index(0, 0))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0));
+ else if (range.topLeft() == model.index(0, 0, parent))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0, parent));
+ else if (range.topLeft() == model.index(0, 0, parent2))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0, parent2));
+ else if (range.topLeft() == model.index(0, 0, parent3))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0, parent3));
+ else
+ QFAIL("Unexpected selection range");
+ }
+ }
+
+ { // 1st two items, and 1st child of 1st item expanded, select rows across branches
+ const QItemSelection &sel = tma.selectionForRowRange(8, 23);
+ QCOMPARE(sel.count(), 4);
+ // We don't know in which order the selection ranges are
+ // being added, so we iterate and try to find what we expect.
+ foreach (const QItemSelectionRange &range, sel) {
+ if (range.topLeft() == model.index(1, 0))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(1, 0));
+ else if (range.topLeft() == model.index(1, 0, parent))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0, parent));
+ else if (range.topLeft() == model.index(0, 0, parent2))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(3, 0, parent2));
+ else if (range.topLeft() == model.index(6, 0, parent3))
+ QCOMPARE(QModelIndex(range.bottomRight()), model.index(ModelRowCount - 1, 0, parent3));
+ else
+ QFAIL("Unexpected selection range");
+ }
+ }
+}
+
+QTEST_MAIN(tst_QQuickTreeModelAdaptor)
+#include "tst_qquicktreemodeladaptor.moc"
diff --git a/tests/auto/shared/testmodel.h b/tests/auto/shared/testmodel.h
new file mode 100644
index 00000000..0bc06757
--- /dev/null
+++ b/tests/auto/shared/testmodel.h
@@ -0,0 +1,333 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore>
+#include <QAbstractItemModel>
+
+class TestModel: public QAbstractItemModel
+{
+ Q_OBJECT
+public:
+ TestModel(QObject *parent = 0): QAbstractItemModel(parent),
+ fetched(false), rows(10), cols(1), levels(INT_MAX), wrongIndex(false) { init(); }
+
+ TestModel(int _rows, int _cols, QObject *parent = 0): QAbstractItemModel(parent),
+ fetched(false), rows(_rows), cols(_cols), levels(INT_MAX), wrongIndex(false) { init(); }
+
+ enum {
+ NameRole = Qt::UserRole + 1
+ };
+
+ void init() {
+ decorationsEnabled = false;
+ alternateChildlessRows = true;
+ tree = new Node(rows);
+ }
+
+ inline qint32 level(const QModelIndex &index) const {
+ Node *n = (Node *)index.internalPointer();
+ if (!n)
+ return -1;
+ int l = -1;
+ while (n != tree) {
+ n = n->parent;
+ ++l;
+ }
+ return l;
+ }
+
+ void resetModel()
+ {
+ beginResetModel();
+ fetched = false;
+ delete tree;
+ tree = new Node(rows);
+ endResetModel();
+ }
+
+ QString displayData(const QModelIndex &idx) const
+ {
+ return QString("[%1,%2,%3,%4]").arg(idx.row()).arg(idx.column()).arg(idx.internalId()).arg(hasChildren(idx));
+ }
+
+ bool canFetchMore(const QModelIndex &) const {
+ return !fetched;
+ }
+
+ void fetchMore(const QModelIndex &) {
+ fetched = true;
+ }
+
+ bool hasChildren(const QModelIndex &parent = QModelIndex()) const {
+ bool hasFetched = fetched;
+ fetched = true;
+ bool r = QAbstractItemModel::hasChildren(parent);
+ fetched = hasFetched;
+ return r;
+ }
+
+ int rowCount(const QModelIndex& parent = QModelIndex()) const {
+ if (!fetched)
+ qFatal("%s: rowCount should not be called before fetching", Q_FUNC_INFO);
+ if ((parent.column() > 0) || (level(parent) > levels)
+ || (alternateChildlessRows && parent.row() > 0 && (parent.row() & 1)))
+ return 0;
+ Node *n = (Node*)parent.internalPointer();
+ if (!n)
+ n = tree;
+ return n->children.count();
+ }
+
+ int columnCount(const QModelIndex& parent = QModelIndex()) const {
+ if ((parent.column() > 0) || (level(parent) > levels)
+ || (alternateChildlessRows && parent.row() > 0 && (parent.row() & 1)))
+ return 0;
+ return cols;
+ }
+
+ bool isEditable(const QModelIndex &index) const {
+ if (index.isValid())
+ return true;
+ return false;
+ }
+
+ Q_INVOKABLE QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const
+ {
+ if (row < 0 || column < 0 || (level(parent) > levels) || column >= cols)
+ return QModelIndex();
+ Node *pn = (Node*)parent.internalPointer();
+ if (!pn)
+ pn = tree;
+ if (row >= pn->children.count())
+ return QModelIndex();
+
+ Node *n = pn->children.at(row);
+ if (!n) {
+ n = new Node(rows, pn);
+ pn->children[row] = n;
+ }
+ return createIndex(row, column, n);
+ }
+
+ QModelIndex parent(const QModelIndex &index) const
+ {
+ Node *n = (Node *)index.internalPointer();
+ if (!n || n->parent == tree)
+ return QModelIndex();
+ Q_ASSERT(n->parent->parent);
+ int parentRow = n->parent->parent->children.indexOf(n->parent);
+ Q_ASSERT(parentRow != -1);
+ return createIndex(parentRow, 0, n->parent);
+ }
+
+ QVariant data(const QModelIndex &idx, int role) const
+ {
+ if (!idx.isValid())
+ return QVariant();
+
+ Node *pn = (Node *)idx.internalPointer();
+ if (!pn)
+ pn = tree;
+ if (pn != tree)
+ pn = pn->parent;
+ if (idx.row() < 0 || idx.column() < 0 || idx.column() >= cols
+ || idx.row() >= pn->children.count()) {
+ wrongIndex = true;
+ qWarning("Invalid modelIndex [%d,%d,%p]", idx.row(), idx.column(),
+ idx.internalPointer());
+ return QVariant();
+ }
+
+ if (role == Qt::DisplayRole)
+ return displayData(idx);
+
+ if (role == NameRole)
+ return QString("Name-%1-%2-%3-%4").arg(idx.row()).arg(idx.column()).arg(idx.internalId()).arg(hasChildren(idx));
+
+ return QVariant();
+ }
+
+ bool setData(const QModelIndex &index, const QVariant &value, int role)
+ {
+ Q_UNUSED(value);
+ QVector<int> changedRole(1, role);
+ emit dataChanged(index, index, changedRole);
+ return true;
+ }
+
+ void groupedSetData(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
+ {
+ emit dataChanged(topLeft, bottomRight, roles);
+ }
+
+ void changeLayout(const QList<QPersistentModelIndex> &parents = QList<QPersistentModelIndex>())
+ {
+ emit layoutAboutToBeChanged(parents);
+ emit layoutChanged(parents);
+ }
+
+ bool removeRows(int row, int count, const QModelIndex &parent)
+ {
+ beginRemoveRows(parent, row, row + count - 1);
+ Node *n = (Node *)parent.internalPointer();
+ if (!n)
+ n = tree;
+ n->removeRows(row, count);
+ endRemoveRows();
+ return true;
+ }
+
+ void removeLastColumn()
+ {
+ beginRemoveColumns(QModelIndex(), cols - 1, cols - 1);
+ --cols;
+ endRemoveColumns();
+ }
+
+ void removeAllColumns()
+ {
+ beginRemoveColumns(QModelIndex(), 0, cols - 1);
+ cols = 0;
+ endRemoveColumns();
+ }
+
+ bool insertRows(int row, int count, const QModelIndex &parent)
+ {
+ beginInsertRows(parent, row, row + count - 1);
+ Node *n = (Node *)parent.internalPointer();
+ if (!n)
+ n = tree;
+ n->addRows(row, count);
+ endInsertRows();
+ return true;
+ }
+
+ bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild)
+ {
+ Q_ASSERT_X(sourceRow >= 0 && sourceRow < rowCount(sourceParent)
+ && count > 0 && sourceRow + count < rowCount(sourceParent)
+ && destinationChild >= 0 && destinationChild <= rowCount(destinationParent),
+ Q_FUNC_INFO, "Rows out of range.");
+ Q_ASSERT_X(!(sourceParent == destinationParent && destinationChild >= sourceRow && destinationChild < sourceRow + count),
+ Q_FUNC_INFO, "Moving rows onto themselves.");
+ if (!beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild))
+ return false;
+ Node *src = (Node *)sourceParent.internalPointer();
+ if (!src)
+ src = tree;
+ Node *dest = (Node *)destinationParent.internalPointer();
+ if (!dest)
+ dest = tree;
+ QVector<Node *> buffer = src->children.mid(sourceRow, count);
+ if (src != dest) {
+ src->removeRows(sourceRow, count, true /* keep alive */);
+ dest->addRows(destinationChild, count);
+ } else {
+ QVector<Node *> &c = dest->children;
+ if (sourceRow < destinationChild) {
+ memmove(&c[sourceRow], &c[sourceRow + count], sizeof(Node *) * (destinationChild - sourceRow - count));
+ destinationChild -= count;
+ } else {
+ memmove(&c[destinationChild + count], &c[destinationChild], sizeof(Node *) * (sourceRow - destinationChild));
+ }
+ }
+ for (int i = 0; i < count; i++) {
+ Node *n = buffer[i];
+ n->parent = dest;
+ dest->children[i + destinationChild] = n;
+ }
+
+ endMoveRows();
+ return true;
+ }
+
+ void setDecorationsEnabled(bool enable)
+ {
+ decorationsEnabled = enable;
+ }
+
+ mutable bool fetched;
+ bool decorationsEnabled;
+ bool alternateChildlessRows;
+ int rows, cols;
+ int levels;
+ mutable bool wrongIndex;
+
+ struct Node {
+ Node *parent;
+ QVector<Node *> children;
+
+ Node(int rows, Node *p = 0) : parent(p)
+ {
+ addRows(0, rows);
+ }
+
+ ~Node()
+ {
+ foreach (Node *n, children)
+ delete n;
+ }
+
+ void addRows(int row, int count)
+ {
+ if (count > 0) {
+ children.reserve(children.count() + count);
+ children.insert(row, count, (Node *)0);
+ }
+ }
+
+ void removeRows(int row, int count, bool keepAlive = false)
+ {
+ int newCount = qMax(children.count() - count, 0);
+ int effectiveCountDiff = children.count() - newCount;
+ if (effectiveCountDiff > 0) {
+ if (!keepAlive)
+ for (int i = 0; i < effectiveCountDiff; i++)
+ delete children[i + row];
+ children.remove(row, effectiveCountDiff);
+ }
+ }
+ };
+
+ QHash<int, QByteArray> roleNames() const
+ {
+ QHash<int, QByteArray> rn = QAbstractItemModel::roleNames();
+ rn[NameRole] = "name";
+ return rn;
+ }
+
+ Node *tree;
+};
diff --git a/tests/auto/testplugin/testplugin.cpp b/tests/auto/testplugin/testplugin.cpp
index cece3939..0f23f0b4 100644
--- a/tests/auto/testplugin/testplugin.cpp
+++ b/tests/auto/testplugin/testplugin.cpp
@@ -40,12 +40,14 @@
#include <QStringList>
#include "testplugin.h"
#include "testcppmodels.h"
+#include "../shared/testmodel.h"
void TestPlugin::registerTypes(const char *uri)
{
// cpp models
qmlRegisterType<TestObject>(uri, 1, 0, "TestObject");
qmlRegisterType<TestItemModel>(uri, 1, 0, "TestItemModel");
+ qmlRegisterType<TestModel>(uri, 1, 0, "TreeModel");
}
void TestPlugin::initializeEngine(QQmlEngine *engine, const char * /*uri*/)
diff --git a/tests/auto/testplugin/testplugin.pro b/tests/auto/testplugin/testplugin.pro
index 3914c7ee..dfdd1a81 100644
--- a/tests/auto/testplugin/testplugin.pro
+++ b/tests/auto/testplugin/testplugin.pro
@@ -18,7 +18,8 @@ SOURCES += \
HEADERS += \
$$PWD/testplugin.h \
- $$PWD/testcppmodels.h
+ $$PWD/testcppmodels.h \
+ $$PWD/../shared/testmodel.h
mac {
LIBS += -framework Carbon