summaryrefslogtreecommitdiff
path: root/tests/auto/qquicktreemodeladaptor
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/auto/qquicktreemodeladaptor
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/auto/qquicktreemodeladaptor')
-rw-r--r--tests/auto/qquicktreemodeladaptor/qquicktreemodeladaptor.pro12
-rw-r--r--tests/auto/qquicktreemodeladaptor/tst_qquicktreemodeladaptor.cpp1135
2 files changed, 1147 insertions, 0 deletions
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"