blob: c3e77cf9b98307a1d875bb792e864afaba017b25 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.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, QQuickTreeModelAdaptor1 &tma, const QModelIndex &idx, TestModel &model, bool expanded = false);
void compareModels(QQuickTreeModelAdaptor1 &tma, TestModel &model);
void expandAndTest(const QModelIndex &idx, QQuickTreeModelAdaptor1 &tma, bool expandable, int expectedRowCountDifference);
void collapseAndTest(const QModelIndex &idx, QQuickTreeModelAdaptor1 &tma, bool expandable, int expectedRowCountDifference);
bool rowRoleWasChanged(const QSignalSpy &captured, int row, int role);
private slots:
void initTestCase();
void cleanup();
void setModel();
void modelDestroyed();
void modelReset();
void rootIndex();
void dataAccess();
void dataChange();
void groupedDataChange();
void expandAndCollapse_data();
void expandAndCollapse();
void expandAndCollapse2ndLevel();
void layoutChange();
void removeRows_data();
void removeRows();
void removeRowsChildrenAndParent();
void removeChildrenMoveParent();
void removeChildrenRelayoutParent();
void insertRows_data();
void insertRows();
void moveRows_data();
void moveRows();
void moveRowsDataChanged_data();
void moveRowsDataChanged();
void reparentOnSameRow();
void moveAllChildren();
void selectionForRowRange();
void hasChildrenEmit();
};
void tst_QQuickTreeModelAdaptor::initTestCase()
{
}
void tst_QQuickTreeModelAdaptor::cleanup()
{
}
void tst_QQuickTreeModelAdaptor::compareData(int row, QQuickTreeModelAdaptor1 &tma, const QModelIndex &modelIdx, TestModel &model, bool expanded)
{
const QModelIndex &tmaIdx = tma.index(row);
const int indexDepth = model.level(modelIdx) - model.level(tma.rootIndex()) - 1;
QCOMPARE(tma.mapToModel(tmaIdx), modelIdx);
QCOMPARE(tma.data(tmaIdx, Qt::DisplayRole).toString(), model.displayData(modelIdx));
QCOMPARE(tma.data(tmaIdx, QQuickTreeModelAdaptor1::DepthRole).toInt(), indexDepth);
QCOMPARE(tma.data(tmaIdx, QQuickTreeModelAdaptor1::ExpandedRole).toBool(), expanded);
QCOMPARE(tma.data(tmaIdx, QQuickTreeModelAdaptor1::HasChildrenRole).toBool(), model.hasChildren(modelIdx));
}
void tst_QQuickTreeModelAdaptor::expandAndTest(const QModelIndex &idx, QQuickTreeModelAdaptor1 &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, QQuickTreeModelAdaptor1::ExpandedRole).toBool(), expandable);
if (expandable && expectedRowCountDifference != 0) {
// 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, QQuickTreeModelAdaptor1::ExpandedRole));
} else {
QCOMPARE(tma.rowCount(), oldRowCount);
QCOMPARE(rowsAboutToBeInsertedSpy.count(), 0);
QCOMPARE(rowsInsertedSpy.count(), 0);
}
}
void tst_QQuickTreeModelAdaptor::collapseAndTest(const QModelIndex &idx, QQuickTreeModelAdaptor1 &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, QQuickTreeModelAdaptor1::ExpandedRole).toBool(), false);
if (expandable && expectedRowCountDifference != 0) {
// 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)
QVERIFY(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, QQuickTreeModelAdaptor1::ExpandedRole));
} else {
QCOMPARE(tma.rowCount(), oldRowCount);
QCOMPARE(rowsAboutToBeRemovedSpy.count(), 0);
QCOMPARE(rowsRemovedSpy.count(), 0);
}
}
bool tst_QQuickTreeModelAdaptor::rowRoleWasChanged(const QSignalSpy &captured, int row, int role)
{
for (const QVariantList &args : captured) {
const int startRow = args.at(0).toModelIndex().row();
const int endRow = args.at(1).toModelIndex().row();
if (row >= startRow && row <= endRow) {
const QVector<int> roles = args.at(2).value<QVector<int>>();
if (roles.contains(role))
return true;
}
}
return false;
}
void tst_QQuickTreeModelAdaptor::compareModels(QQuickTreeModelAdaptor1 &tma, TestModel &model)
{
QModelIndex parent = tma.rootIndex();
QStack<QModelIndex> parents;
QModelIndex idx = model.index(0, 0, parent);
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);
QQuickTreeModelAdaptor1 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::modelDestroyed()
{
TestModel *model = new TestModel(5, 1);
QQuickTreeModelAdaptor1 tma;
QSignalSpy modelChangedSpy(&tma, SIGNAL(modelChanged(QAbstractItemModel*)));
tma.setModel(model);
QCOMPARE(modelChangedSpy.count(), 1);
QCOMPARE(tma.model(), model);
QModelIndex idx = model->index(0, 0);
modelChangedSpy.clear();
delete model;
QCOMPARE(modelChangedSpy.count(), 1);
QCOMPARE(tma.model(), (QAbstractItemModel *)0);
tma.expand(idx); // No crash, all fine
}
void tst_QQuickTreeModelAdaptor::modelReset()
{
TestModel model(5, 1);
QQuickTreeModelAdaptor1 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::rootIndex()
{
TestModel model(5, 1);
QQuickTreeModelAdaptor1 tma;
tma.setModel(&model);
QVERIFY(!tma.rootIndex().isValid());
compareModels(tma, model);
QSignalSpy rootIndexSpy(&tma, SIGNAL(rootIndexChanged()));
QModelIndex rootIndex = model.index(0, 0);
tma.setRootIndex(rootIndex);
QCOMPARE(tma.rootIndex(), rootIndex);
QCOMPARE(rootIndexSpy.count(), 1);
compareModels(tma, model);
rootIndexSpy.clear();
rootIndex = model.index(2, 2, tma.rootIndex());
tma.setRootIndex(rootIndex);
QCOMPARE(tma.rootIndex(), rootIndex);
QCOMPARE(rootIndexSpy.count(), 1);
compareModels(tma, model);
// Expand 1st visible item, business as usual
expandAndTest(model.index(0, 0, rootIndex), tma, true /*expandable*/, 5);
// Expand non root item descendant item, nothing should happen
expandAndTest(model.index(0, 0), tma, true /*expandable*/, 0);
// Collapse 1st visible item, business as usual
collapseAndTest(model.index(0, 0, rootIndex), tma, true /*expandable*/, 5);
// Collapse non root item descendant item, nothing should happen
collapseAndTest(model.index(0, 0), tma, true /*expandable*/, 0);
}
void tst_QQuickTreeModelAdaptor::dataAccess()
{
TestModel model(5, 1);
QQuickTreeModelAdaptor1 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);
QQuickTreeModelAdaptor1 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);
QQuickTreeModelAdaptor1 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);
QQuickTreeModelAdaptor1 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));
QQuickTreeModelAdaptor1 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);
QQuickTreeModelAdaptor1 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);
QQuickTreeModelAdaptor1 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::removeRowsChildrenAndParent()
{
TestModel model(ModelRowCount, 1);
QQuickTreeModelAdaptor1 tma;
tma.setModel(&model);
// Expand the first node
const QModelIndex &parent = model.index(0, 0);
tma.expand(parent);
QVERIFY(tma.isExpanded(parent));
QSignalSpy rowsAboutToBeRemovedSpy(&tma, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)));
QSignalSpy rowsRemovedSpy(&tma, SIGNAL(rowsRemoved(const QModelIndex&, int, int)));
// Remove the first node children
int expectedRemovedCount = model.rowCount(parent);
int tmaItemIdx = tma.itemIndex(model.index(0, 0, parent));
QCOMPARE(tmaItemIdx, tma.itemIndex(parent) + 1);
model.removeRows(0, expectedRemovedCount, parent);
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);
// Remove the first node
rowsAboutToBeRemovedSpy.clear();
rowsRemovedSpy.clear();
model.removeRows(0, 1, QModelIndex());
QCOMPARE(rowsAboutToBeRemovedSpy.count(), 1);
QCOMPARE(rowsRemovedSpy.count(), 1);
rowsAboutToBeRemovedArgs = rowsAboutToBeRemovedSpy.first();
rowsRemovedArgs = rowsRemovedSpy.first();
QCOMPARE(rowsAboutToBeRemovedArgs, rowsRemovedArgs);
QCOMPARE(rowsAboutToBeRemovedArgs.at(0).toModelIndex(), QModelIndex());
QCOMPARE(rowsAboutToBeRemovedArgs.at(1).toInt(), 0);
QCOMPARE(rowsAboutToBeRemovedArgs.at(2).toInt(), 0);
}
void tst_QQuickTreeModelAdaptor::removeChildrenMoveParent()
{
TestModel model(ModelRowCount, 1);
QQuickTreeModelAdaptor1 tma;
tma.setModel(&model);
// Expand the first node
const QModelIndex &parent = model.index(0, 0);
tma.expand(parent);
QVERIFY(tma.isExpanded(parent));
// Remove the first node children
QSignalSpy rowsAboutToBeRemovedSpy(&tma, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)));
QSignalSpy rowsRemovedSpy(&tma, SIGNAL(rowsRemoved(const QModelIndex&, int, int)));
int expectedRemovedCount = model.rowCount(parent);
int tmaItemIdx = tma.itemIndex(model.index(0, 0, parent));
QCOMPARE(tmaItemIdx, tma.itemIndex(parent) + 1);
model.removeRows(0, expectedRemovedCount, parent);
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);
// Move the first node
QSignalSpy rowsAboutToBeMovedSpy(&tma, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)));
QSignalSpy rowsMovedSpy(&tma, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)));
model.moveRows(QModelIndex(), 0, 1, QModelIndex(), 3);
QCOMPARE(rowsAboutToBeMovedSpy.count(), 1);
QCOMPARE(rowsRemovedSpy.count(), 1);
QVariantList rowsAboutToBeMovedArgs = rowsAboutToBeMovedSpy.first();
QVariantList rowsMovedArgs = rowsMovedSpy.first();
QCOMPARE(rowsAboutToBeMovedArgs, rowsMovedArgs);
QCOMPARE(rowsAboutToBeMovedArgs.at(0).toModelIndex(), QModelIndex());
QCOMPARE(rowsAboutToBeMovedArgs.at(1).toInt(), 0);
QCOMPARE(rowsAboutToBeMovedArgs.at(2).toInt(), 0);
QCOMPARE(rowsAboutToBeMovedArgs.at(3).toModelIndex(), QModelIndex());
QCOMPARE(rowsAboutToBeMovedArgs.at(4).toInt(), 3);
}
void tst_QQuickTreeModelAdaptor::removeChildrenRelayoutParent()
{
TestModel model(ModelRowCount, 1);
QQuickTreeModelAdaptor1 tma;
tma.setModel(&model);
// Expand the first node
const QModelIndex &parent = model.index(0, 0);
tma.expand(parent);
QVERIFY(tma.isExpanded(parent));
QSignalSpy rowsAboutToBeRemovedSpy(&tma, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)));
QSignalSpy rowsRemovedSpy(&tma, SIGNAL(rowsRemoved(const QModelIndex&, int, int)));
// Remove the first node children
int expectedRemovedCount = model.rowCount(parent);
int tmaItemIdx = tma.itemIndex(model.index(0, 0, parent));
QCOMPARE(tmaItemIdx, tma.itemIndex(parent) + 1);
model.removeRows(0, expectedRemovedCount, parent);
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);
// Relayout the first node
QSignalSpy dataChanged(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
QList<QPersistentModelIndex> parents;
parents << parent;
model.changeLayout(parents);
QCOMPARE(dataChanged.count(), 0);
}
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);
QQuickTreeModelAdaptor1 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;
QQuickTreeModelAdaptor1 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::moveRowsDataChanged_data()
{
/* This is the list of rows in the *list* model which are expanded. */
QTest::addColumn<QVector<int>>("expandedRows");
/* Here's the row to be moved (always just one) and the destination
* position. We use a QVector<int> to identify a single row of the tree
* model: the array value at index n represents the row number at the depth
* n.
*/
QTest::addColumn<QVector<int>>("sourcePath");
QTest::addColumn<QVector<int>>("destPath");
/* This is the list of expected changed rows in the *list* model. */
QTest::addColumn<QSet<int>>("expectedRowChanges");
QTest::newRow("From and to top-level parent")
<< QVector<int> {}
<< QVector<int> { 4 }
<< QVector<int> { 3 }
<< QSet<int> { 3, 4 };
QTest::newRow("From and to top-level parent, expanded")
<< QVector<int> { 3, 5 }
<< QVector<int> { 4 }
<< QVector<int> { 3 }
<< QSet<int> { 3, 4, 5, 6, 7, 8, 9 };
QTest::newRow("From and to top-level parent, expanded, down")
<< QVector<int> { 3, 5 }
<< QVector<int> { 3 }
<< QVector<int> { 5 }
<< QSet<int> { 3, 4, 5, 6, 7, 8, 9 };
/* Expected visible result:
* A A
* D D
* `-E |-E
* F `-H
* G -> F
* |-H G
* |-I |-I
* `-L `-L
* M M
*/
QTest::newRow("Visible move, different parents")
<< QVector<int> { 3, 1 }
<< QVector<int> { 3, 0 }
<< QVector<int> { 1, 1 }
<< QSet<int> { 3, 4, 5, 6, 7 };
QTest::newRow("Move to non expanded parent")
<< QVector<int> {}
<< QVector<int> { 3 }
<< QVector<int> { 1, 0 }
<< QSet<int> { 3 };
}
void tst_QQuickTreeModelAdaptor::moveRowsDataChanged()
{
QFETCH(QVector<int>, expandedRows);
QFETCH(QVector<int>, sourcePath);
QFETCH(QVector<int>, destPath);
QFETCH(QSet<int>, expectedRowChanges);
TestModel model(0, 1);
model.alternateChildlessRows = false;
model.fetchMore(QModelIndex());
QQuickTreeModelAdaptor1 tma;
tma.setModel(&model);
/* Build this model:
* A
* |-B
* `-C
* D
* `-E
* F
* G
* |-H
* |-I
* | |-J
* | `-K
* `-L
* M
*/
model.insertRows(0, 5, QModelIndex());
QModelIndex index = model.index(0, 0);
model.insertRows(0, 2, index);
index = model.index(1, 0);
model.insertRows(0, 1, index);
index = model.index(3, 0);
model.insertRows(0, 3, index);
index = model.index(1, 0, index);
model.insertRows(0, 2, index);
for (int row : expandedRows) {
tma.expandRow(row);
}
/* Find the source index */
int sourceRow = sourcePath.takeLast();
QModelIndex sourceIndex;
for (int row : sourcePath) {
sourceIndex = model.index(row, 0, sourceIndex);
}
/* Find the destination index */
int destRow = destPath.takeLast();
QModelIndex destIndex;
for (int row : destPath) {
destIndex = model.index(row, 0, destIndex);
}
QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
QVERIFY(dataChangedSpy.isValid());
QVERIFY(model.moveRows(sourceIndex, sourceRow, 1, destIndex, destRow));
QSet<int> emittedChanges;
for (int i = 0; i < dataChangedSpy.count(); i++) {
QVariantList args = dataChangedSpy.at(i);
QVector<int> roles = args.at(2).value<QVector<int>>();
if (!roles.isEmpty() &&
!roles.contains(QQuickTreeModelAdaptor1::ModelIndexRole))
continue;
const QModelIndex topLeft = args.at(0).value<QModelIndex>();
const QModelIndex bottomRight = args.at(1).value<QModelIndex>();
for (int row = topLeft.row(); row <= bottomRight.row(); row++) {
emittedChanges.insert(row);
}
}
QCOMPARE(emittedChanges, expectedRowChanges);
}
void tst_QQuickTreeModelAdaptor::reparentOnSameRow()
{
TestModel model(2, 1);
model.alternateChildlessRows = false;
QQuickTreeModelAdaptor1 tma;
tma.setModel(&model);
const QModelIndex &destParent = model.index(0, 0);
const QModelIndex &sourceParent = QModelIndex();
QVERIFY(destParent.isValid());
tma.expand(destParent);
QVERIFY(tma.isExpanded(destParent));
QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
QSignalSpy rowsMovedSpy(&tma, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)));
QVERIFY(rowsMovedSpy.isValid());
QVERIFY(dataChangedSpy.isValid());
QVERIFY(model.moveRows(sourceParent, 1, 1, destParent, 2));
QModelIndex movedIndex = tma.index(3, 0, QModelIndex());
QVERIFY(movedIndex.isValid());
QCOMPARE(movedIndex.data(QQuickTreeModelAdaptor1::DepthRole).toInt(), 1);
QCOMPARE(tma.data(movedIndex, QQuickTreeModelAdaptor1::ModelIndexRole).toModelIndex(), model.index(2, 0, destParent));
// at least DepthRole and ModeIndexRole changes should have happened for the affected row
bool depthChanged = false;
bool modelIndexChanged = false;
const QList<QList<QVariant> > &changes = dataChangedSpy;
for (const QList<QVariant> &change : changes) {
if (change.at(0) == movedIndex) {
if (change.at(2).value<QVector<int> >().contains(QQuickTreeModelAdaptor1::DepthRole))
depthChanged = true;
if (change.at(2).value<QVector<int> >().contains(QQuickTreeModelAdaptor1::ModelIndexRole))
modelIndexChanged = true;
}
}
QCOMPARE(depthChanged, true);
QCOMPARE(modelIndexChanged, true);
QCOMPARE(rowsMovedSpy.count(), 0);
model.moveRow(destParent, 2, QModelIndex(), 1);
QCOMPARE(rowsMovedSpy.count(), 0);
QVERIFY(tma.testConsistency());
compareModels(tma, model);
}
void tst_QQuickTreeModelAdaptor::moveAllChildren()
{
TestModel model(0, 1);
model.alternateChildlessRows = false;
model.fetchMore(QModelIndex());
QQuickTreeModelAdaptor1 tma;
tma.setModel(&model);
/* Build this model:
* A
* B
* `-C
*/
model.insertRows(0, 2, QModelIndex());
QPersistentModelIndex aIndex(model.index(0, 0));
QPersistentModelIndex bIndex(model.index(1, 0));
model.insertRows(0, 1, bIndex);
QCOMPARE(model.rowCount(QModelIndex()), 2);
/* Expand B, then move C under A */
tma.expandRow(1);
QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
QVERIFY(dataChangedSpy.isValid());
QVERIFY(model.moveRows(bIndex, 0, 1, aIndex, 0));
/* Check the outcome */
QCOMPARE(tma.data(aIndex, QQuickTreeModelAdaptor1::HasChildrenRole).toBool(), true);
QCOMPARE(tma.data(bIndex, QQuickTreeModelAdaptor1::HasChildrenRole).toBool(), false);
QVERIFY(rowRoleWasChanged(dataChangedSpy, 0, QQuickTreeModelAdaptor1::HasChildrenRole));
QVERIFY(rowRoleWasChanged(dataChangedSpy, 1, QQuickTreeModelAdaptor1::HasChildrenRole));
dataChangedSpy.clear();
/* Move C back into under B */
QVERIFY(model.moveRows(aIndex, 0, 1, bIndex, 0));
QCOMPARE(tma.data(aIndex, QQuickTreeModelAdaptor1::HasChildrenRole).toBool(), false);
QCOMPARE(tma.data(bIndex, QQuickTreeModelAdaptor1::HasChildrenRole).toBool(), true);
QVERIFY(rowRoleWasChanged(dataChangedSpy, 0, QQuickTreeModelAdaptor1::HasChildrenRole));
QVERIFY(rowRoleWasChanged(dataChangedSpy, 1, QQuickTreeModelAdaptor1::HasChildrenRole));
/* B must not be in expanded state, since it previously lost all its children */
QCOMPARE(tma.data(bIndex, QQuickTreeModelAdaptor1::ExpandedRole).toBool(), false);
dataChangedSpy.clear();
}
void tst_QQuickTreeModelAdaptor::selectionForRowRange()
{
const int ModelRowCount = 9;
const int ModelRowCountLoopStep = 4;
TestModel model(ModelRowCount, 1);
model.alternateChildlessRows = false;
QQuickTreeModelAdaptor1 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 QModelIndex &idx = model.index(i, 0);
const QItemSelection &sel = tma.selectionForRowRange(idx, idx);
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 QModelIndex &from = model.index(i, 0);
const QModelIndex &to = model.index(i + ModelRowCountLoopStep, 0);
const QItemSelection &sel = tma.selectionForRowRange(from, to);
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 QModelIndex &from = model.index(0, 0);
const QModelIndex &to = model.index(ModelRowCount - 1, 0);
const QItemSelection &sel = tma.selectionForRowRange(from, to);
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 QModelIndex &from = tma.mapRowToModelIndex(0);
const QModelIndex &to = tma.mapRowToModelIndex(4);
const QItemSelection &sel = tma.selectionForRowRange(from, to);
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.
for (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 QModelIndex &from = tma.mapRowToModelIndex(0);
const QModelIndex &to = tma.mapRowToModelIndex(4 + ModelRowCount);
const QItemSelection &sel = tma.selectionForRowRange(from, to);
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.
for (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 QModelIndex &from = tma.mapRowToModelIndex(0);
const QModelIndex &to = tma.mapRowToModelIndex(4 + 2 * ModelRowCount);
const QItemSelection &sel = tma.selectionForRowRange(from, to);
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.
for (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 QModelIndex &from = tma.mapRowToModelIndex(0);
const QModelIndex &to = tma.mapRowToModelIndex(4);
const QItemSelection &sel = tma.selectionForRowRange(from, to);
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.
for (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 QModelIndex &from = tma.mapRowToModelIndex(0);
const QModelIndex &to = tma.mapRowToModelIndex(4 * ModelRowCount - 1);
const QItemSelection &sel = tma.selectionForRowRange(from, to);
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.
for (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 QModelIndex &from = tma.mapRowToModelIndex(8);
const QModelIndex &to = tma.mapRowToModelIndex(23);
const QItemSelection &sel = tma.selectionForRowRange(from, to);
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.
for (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");
}
}
}
void tst_QQuickTreeModelAdaptor::hasChildrenEmit()
{
TestModel model(1, 1);
model.alternateChildlessRows = false;
QQuickTreeModelAdaptor1 tma;
tma.setModel(&model);
QModelIndex root = model.index(0,0);
QVERIFY(root.isValid());
QModelIndex child = model.index(0, 0, root);
QVERIFY(child.isValid());
// Root not expanded , child not expanded, insert in child, expect no datachanged
{
QVERIFY(!tma.isExpanded(root));
QVERIFY(!tma.isExpanded(child));
QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
QCOMPARE(dataChangedSpy.count(), 0);
QCOMPARE(model.rowCount(child), 1);
model.insertRow(1, child);
QCOMPARE(model.rowCount(child), 2);
QCOMPARE(dataChangedSpy.count(), 0);
}
// Root not expanded, insert in root, expect datachanged
{
QVERIFY(!tma.isExpanded(root));
QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
QCOMPARE(dataChangedSpy.count(), 0);
QCOMPARE(model.rowCount(root), 1);
model.insertRow(1, root);
QCOMPARE(model.rowCount(root), 2);
QCOMPARE(dataChangedSpy.count(), 1);
}
// Root not expanded, child not expanded, remove in child, expect no datachanged
{
QVERIFY(!tma.isExpanded(root));
QVERIFY(!tma.isExpanded(child));
QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
QCOMPARE(dataChangedSpy.count(), 0);
QCOMPARE(model.rowCount(child), 2);
model.removeRow(1, child);
QCOMPARE(model.rowCount(child), 1);
QCOMPARE(dataChangedSpy.count(), 0);
}
// Root not expanded, remove in root, expected datachanged on hasChildren
{
QVERIFY(!tma.isExpanded(root));
QSignalSpy dataChangedSpy(&tma, SIGNAL(dataChanged(QModelIndex,QModelIndex,QVector<int>)));
QCOMPARE(dataChangedSpy.count(), 0);
QCOMPARE(model.rowCount(root), 2);
model.removeRow(1, root);
QCOMPARE(model.rowCount(root), 1);
QCOMPARE(dataChangedSpy.count(), 1);
}
}
QTEST_MAIN(tst_QQuickTreeModelAdaptor)
#include "tst_qquicktreemodeladaptor.moc"