You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

632 lines
27 KiB

/*
Copyright (c) 2014 Christian Mollekopf <mollekopf@kolabsys.com>
Copyright (c) 2014 David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
*/
#include <QTest>
#include <krecursivefilterproxymodel.h>
#include <QStandardItemModel>
Q_DECLARE_METATYPE(QModelIndex)
class ModelSignalSpy : public QObject {
Q_OBJECT
public:
explicit ModelSignalSpy(QAbstractItemModel &model) {
connect(&model, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(onRowsInserted(QModelIndex,int,int)));
connect(&model, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(onRowsRemoved(QModelIndex,int,int)));
connect(&model, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), this, SLOT(onRowsAboutToBeInserted(QModelIndex,int,int)));
connect(&model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(onRowsAboutToBeRemoved(QModelIndex,int,int)));
connect(&model, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(onRowsMoved(QModelIndex,int,int,QModelIndex,int)));
connect(&model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(onDataChanged(QModelIndex,QModelIndex)));
connect(&model, SIGNAL(layoutChanged()), this, SLOT(onLayoutChanged()));
connect(&model, SIGNAL(modelReset()), this, SLOT(onModelReset()));
}
QStringList mSignals;
private Q_SLOTS:
void onRowsInserted(QModelIndex p, int start, int end) {
mSignals << QLatin1String("rowsInserted(") + textForRowSpy(p, start, end) + ')';
}
void onRowsRemoved(QModelIndex p, int start, int end) {
mSignals << QLatin1String("rowsRemoved(") + textForRowSpy(p, start, end) + ')';
}
void onRowsAboutToBeInserted(QModelIndex p, int start, int end) {
mSignals << QLatin1String("rowsAboutToBeInserted(") + textForRowSpy(p, start, end) + ')';
}
void onRowsAboutToBeRemoved(QModelIndex p, int start, int end) {
mSignals << QLatin1String("rowsAboutToBeRemoved(") + textForRowSpy(p, start, end) + ')';
}
void onRowsMoved(QModelIndex,int,int,QModelIndex,int) {
mSignals << QStringLiteral("rowsMoved");
}
void onDataChanged(const QModelIndex &from, const QModelIndex& ) {
mSignals << QStringLiteral("dataChanged(%1)").arg(from.data().toString());
}
void onLayoutChanged() {
mSignals << QStringLiteral("layoutChanged");
}
void onModelReset() {
mSignals << QStringLiteral("modelReset");
}
private:
QString textForRowSpy(const QModelIndex &parent, int start, int end)
{
QString txt = parent.data().toString();
if (!txt.isEmpty())
txt += QLatin1Char('.');
txt += QString::number(start+1);
if (start != end)
txt += QLatin1Char('-') + QString::number(end+1);
return txt;
}
};
class TestModel : public KRecursiveFilterProxyModel
{
Q_OBJECT
public:
TestModel(QAbstractItemModel *sourceModel)
: KRecursiveFilterProxyModel()
{
setSourceModel(sourceModel);
}
bool acceptRow(int sourceRow, const QModelIndex &sourceParent) const override
{
// qDebug() << sourceModel()->index(sourceRow, 0, sourceParent).data().toString() << sourceModel()->index(sourceRow, 0, sourceParent).data(Qt::UserRole+1).toBool();
return sourceModel()->index(sourceRow, 0, sourceParent).data(Qt::UserRole+1).toBool();
}
};
// Represents this tree
// - A
// - - B
// - - - C
// - - - D
// - - E
// as a single string, englobing children in brackets, like this:
// [A[B[C D] E]]
// In addition, items that match the filtering (data(UserRole+1) == true) have a * after their value.
static QString treeAsString(const QAbstractItemModel &model, const QModelIndex &parent = QModelIndex())
{
QString ret;
const int rowCount = model.rowCount(parent);
if (rowCount > 0) {
ret += QLatin1Char('[');
for (int row = 0 ; row < rowCount; ++row) {
if (row > 0) {
ret += ' ';
}
const QModelIndex child = model.index(row, 0, parent);
ret += child.data().toString();
if (child.data(Qt::UserRole+1).toBool())
ret += QLatin1Char('*');
ret += treeAsString(model, child);
}
ret += QLatin1Char(']');
}
return ret;
}
// Fill a tree model based on a string representation (see treeAsString)
static void fillModel(QStandardItemModel &model, const QString &str)
{
QCOMPARE(str.count('['), str.count(']'));
QStandardItem *item = nullptr;
QString data;
for ( int i = 0 ; i < str.length() ; ++i ) {
const QChar ch = str.at(i);
if ((ch == '[' || ch == ']' || ch == ' ') && !data.isEmpty()) {
if (data.endsWith('*')) {
item->setData(true);
data.chop(1);
}
item->setText(data);
data.clear();
}
if (ch == '[') {
// Create new child
QStandardItem *child = new QStandardItem;
if (item)
item->appendRow(child);
else
model.appendRow(child);
item = child;
} else if (ch == ']') {
// Go up to parent
item = item->parent();
} else if (ch == ' ') {
// Create new sibling
QStandardItem *child = new QStandardItem;
QStandardItem *parent = item->parent();
if (parent)
parent->appendRow(child);
else
model.appendRow(child);
item = child;
} else {
data += ch;
}
}
}
class KRecursiveFilterProxyModelTest : public QObject
{
Q_OBJECT
private:
private Q_SLOTS:
void testInitialFiltering_data()
{
QTest::addColumn<QString>("sourceStr");
QTest::addColumn<QString>("proxyStr");
QTest::newRow("empty") << "[]" << "";
QTest::newRow("no") << "[1]" << "";
QTest::newRow("yes") << "[1*]" << "[1*]";
QTest::newRow("second") << "[1 2*]" << "[2*]";
QTest::newRow("child_yes") << "[1 2[2.1*]]" << "[2[2.1*]]";
QTest::newRow("grandchild_yes") << "[1 2[2.1[2.1.1*]]]" << "[2[2.1[2.1.1*]]]";
// 1, 3.1 and 4.2.1 match, so their parents are in the model
QTest::newRow("more") << "[1* 2[2.1] 3[3.1*] 4[4.1 4.2[4.2.1*]]]" << "[1* 3[3.1*] 4[4.2[4.2.1*]]]";
}
void testInitialFiltering()
{
QFETCH(QString, sourceStr);
QFETCH(QString, proxyStr);
QStandardItemModel model;
fillModel(model, sourceStr);
QCOMPARE(treeAsString(model), sourceStr);
TestModel proxy(&model);
QCOMPARE(treeAsString(proxy), proxyStr);
}
// Test changing a role that is unrelated to the filtering.
void testUnrelatedDataChange()
{
QStandardItemModel model;
const QString sourceStr = QStringLiteral("[1[1.1[1.1.1*]]]");
fillModel(model, sourceStr);
QCOMPARE(treeAsString(model), sourceStr);
TestModel proxy(&model);
QCOMPARE(treeAsString(proxy), sourceStr);
ModelSignalSpy spy(proxy);
QStandardItem *item_1_1_1 = model.item(0)->child(0)->child(0);
// When changing the text on the item
item_1_1_1->setText(QStringLiteral("ME"));
QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[ME*]]]"));
QCOMPARE(spy.mSignals, QStringList()
<< QStringLiteral("dataChanged(ME)")
<< QStringLiteral("dataChanged(1.1)") // ### yep, unneeded, but the proxy has no way to know that...
<< QStringLiteral("dataChanged(1)") // ### unneeded too
);
}
// Test changing a role that is unrelated to the filtering, in a hidden item.
void testHiddenDataChange()
{
QStandardItemModel model;
const QString sourceStr = QStringLiteral("[1[1.1[1.1.1]]]");
fillModel(model, sourceStr);
QCOMPARE(treeAsString(model), sourceStr);
TestModel proxy(&model);
QCOMPARE(treeAsString(proxy), QString());
ModelSignalSpy spy(proxy);
QStandardItem *item_1_1_1 = model.item(0)->child(0)->child(0);
// When changing the text on a hidden item
item_1_1_1->setText(QStringLiteral("ME"));
QCOMPARE(treeAsString(proxy), QString());
QCOMPARE(spy.mSignals, QStringList());
}
// Test that we properly react to a data-changed signal in a descendant and include all required rows
void testDataChangeIn_data()
{
QTest::addColumn<QString>("sourceStr");
QTest::addColumn<QString>("initialProxyStr");
QTest::addColumn<QString>("add"); // set the flag on this item
QTest::addColumn<QString>("expectedProxyStr");
QTest::addColumn<QStringList>("expectedSignals");
QTest::newRow("toplevel") << "[1]" << "" << "1" << "[1*]"
<< (QStringList() << QStringLiteral("rowsAboutToBeInserted(1)") << QStringLiteral("rowsInserted(1)"));
QTest::newRow("show_parents") << "[1[1.1[1.1.1]]]" << "" << "1.1.1" << "[1[1.1[1.1.1*]]]"
<< (QStringList() << QStringLiteral("rowsAboutToBeInserted(1)") << QStringLiteral("rowsInserted(1)"));
const QStringList insert_1_1_1 = QStringList() << QStringLiteral("rowsAboutToBeInserted(1.1.1)") << QStringLiteral("rowsInserted(1.1.1)")
<< QStringLiteral("dataChanged(1.1)") << QStringLiteral("dataChanged(1)"); // both unneeded
QTest::newRow("parent_visible") << "[1[1.1*[1.1.1]]]" << "[1[1.1*]]" << "1.1.1" << "[1[1.1*[1.1.1*]]]"
<< insert_1_1_1;
QTest::newRow("sibling_visible") << "[1[1.1[1.1.1 1.1.2*]]]" << "[1[1.1[1.1.2*]]]" << "1.1.1" << "[1[1.1[1.1.1* 1.1.2*]]]"
<< insert_1_1_1;
QTest::newRow("visible_cousin") << "[1[1.1[1.1.1 1.1.2[1.1.2.1*]]]]" << "[1[1.1[1.1.2[1.1.2.1*]]]]" << "1.1.1" << "[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]"
<< insert_1_1_1;
QTest::newRow("show_parent") << "[1[1.1[1.1.1 1.1.2] 1.2*]]" << "[1[1.2*]]" << "1.1.1" << "[1[1.1[1.1.1*] 1.2*]]"
<< (QStringList() << QStringLiteral("rowsAboutToBeInserted(1.1)") << QStringLiteral("rowsInserted(1.1)")
<< QStringLiteral("dataChanged(1)")); // unneeded
QTest::newRow("with_children") << "[1[1.1[1.1.1[1.1.1.1*]]] 2*]" << "[1[1.1[1.1.1[1.1.1.1*]]] 2*]" << "1.1.1" << "[1[1.1[1.1.1*[1.1.1.1*]]] 2*]"
<< (QStringList() << QStringLiteral("dataChanged(1.1.1)")
<< QStringLiteral("dataChanged(1.1)") << QStringLiteral("dataChanged(1)") // both unneeded
);
}
void testDataChangeIn()
{
QFETCH(QString, sourceStr);
QFETCH(QString, initialProxyStr);
QFETCH(QString, add);
QFETCH(QString, expectedProxyStr);
QFETCH(QStringList, expectedSignals);
QStandardItemModel model;
fillModel(model, sourceStr);
QCOMPARE(treeAsString(model), sourceStr);
TestModel proxy(&model);
QCOMPARE(treeAsString(proxy), initialProxyStr);
ModelSignalSpy spy(proxy);
// When changing the data on the designated item to show this row
QStandardItem *itemToChange = itemByText(model, add);
QVERIFY(!itemToChange->data().toBool());
itemToChange->setData(true);
// The proxy should update as expected
QCOMPARE(treeAsString(proxy), expectedProxyStr);
//qDebug() << spy.mSignals;
QCOMPARE(spy.mSignals, expectedSignals);
}
void testDataChangeOut_data()
{
QTest::addColumn<QString>("sourceStr");
QTest::addColumn<QString>("initialProxyStr");
QTest::addColumn<QString>("remove"); // unset the flag on this item
QTest::addColumn<QString>("expectedProxyStr");
QTest::addColumn<QStringList>("expectedSignals");
const QStringList remove1_1_1 = (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1.1.1)") << QStringLiteral("rowsRemoved(1.1.1)")
<< QStringLiteral("dataChanged(1.1)") // unneeded
<< QStringLiteral("dataChanged(1)") // unneeded
);
QTest::newRow("toplevel") << "[1*]" << "[1*]" << "1" << ""
<< (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1)") << QStringLiteral("rowsRemoved(1)"));
QTest::newRow("hide_parent") << "[1[1.1[1.1.1*]]]" << "[1[1.1[1.1.1*]]]" << "1.1.1" << "" <<
(QStringList()
<< QStringLiteral("rowsAboutToBeRemoved(1.1.1)") // ### unneeded but the proxy has no way to know that...
<< QStringLiteral("rowsRemoved(1.1.1)") // unneeded
<< QStringLiteral("rowsAboutToBeRemoved(1.1)") // unneeded
<< QStringLiteral("rowsRemoved(1.1)") // unneeded
<< QStringLiteral("rowsAboutToBeRemoved(1)")
<< QStringLiteral("rowsRemoved(1)")
);
QTest::newRow("parent_visible") << "[1[1.1*[1.1.1*]]]" << "[1[1.1*[1.1.1*]]]" << "1.1.1" << "[1[1.1*]]"
<< remove1_1_1;
QTest::newRow("visible") << "[1[1.1[1.1.1* 1.1.2*]]]" << "[1[1.1[1.1.1* 1.1.2*]]]" << "1.1.1" << "[1[1.1[1.1.2*]]]"
<< remove1_1_1;
QTest::newRow("visible_cousin") << "[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]" << "[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]" << "1.1.1" << "[1[1.1[1.1.2[1.1.2.1*]]]]"
<< remove1_1_1;
// The following tests trigger the removal of an ascendant.
QTest::newRow("remove_parent") << "[1[1.1[1.1.1* 1.1.2] 1.2*]]" << "[1[1.1[1.1.1*] 1.2*]]" << "1.1.1" << "[1[1.2*]]"
<< (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1.1.1)") << QStringLiteral("rowsRemoved(1.1.1)")
<< QStringLiteral("rowsAboutToBeRemoved(1.1)") << QStringLiteral("rowsRemoved(1.1)")
<< QStringLiteral("dataChanged(1)") // unneeded
);
QTest::newRow("with_children") << "[1[1.1[1.1.1*[1.1.1.1*]]] 2*]" << "[1[1.1[1.1.1*[1.1.1.1*]]] 2*]" << "1.1.1" << "[1[1.1[1.1.1[1.1.1.1*]]] 2*]"
<< (QStringList() << QStringLiteral("dataChanged(1.1.1)")
<< QStringLiteral("dataChanged(1.1)") << QStringLiteral("dataChanged(1)") // both unneeded
);
QTest::newRow("last_visible") << "[1[1.1[1.1.1* 1.1.2]]]" << "[1[1.1[1.1.1*]]]" << "1.1.1" << ""
<< (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1.1.1)") << QStringLiteral("rowsRemoved(1.1.1)") // unneeded
<< QStringLiteral("rowsAboutToBeRemoved(1.1)") << QStringLiteral("rowsRemoved(1.1)") // unneeded
<< QStringLiteral("rowsAboutToBeRemoved(1)") << QStringLiteral("rowsRemoved(1)")
);
}
void testDataChangeOut()
{
QFETCH(QString, sourceStr);
QFETCH(QString, initialProxyStr);
QFETCH(QString, remove);
QFETCH(QString, expectedProxyStr);
QFETCH(QStringList, expectedSignals);
QStandardItemModel model;
fillModel(model, sourceStr);
QCOMPARE(treeAsString(model), sourceStr);
TestModel proxy(&model);
QCOMPARE(treeAsString(proxy), initialProxyStr);
ModelSignalSpy spy(proxy);
// When changing the data on the designated item to exclude this row again
QStandardItem *itemToChange = itemByText(model, remove);
QVERIFY(itemToChange->data().toBool());
itemToChange->setData(false);
// The proxy should update as expected
QCOMPARE(treeAsString(proxy), expectedProxyStr);
//qDebug() << spy.mSignals;
QCOMPARE(spy.mSignals, expectedSignals);
}
void testInsert()
{
QStandardItemModel model;
const QString sourceStr = QStringLiteral("[1[1.1[1.1.1]]]");
fillModel(model, sourceStr);
QCOMPARE(treeAsString(model), sourceStr);
TestModel proxy(&model);
QCOMPARE(treeAsString(proxy), QString());
ModelSignalSpy spy(proxy);
QStandardItem *item_1_1_1 = model.item(0)->child(0)->child(0);
QStandardItem *item_1_1_1_1 = new QStandardItem(QStringLiteral("1.1.1.1"));
item_1_1_1_1->setData(true);
item_1_1_1->appendRow(item_1_1_1_1);
QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[1.1.1[1.1.1.1*]]]]"));
QCOMPARE(spy.mSignals, QStringList() << QStringLiteral("rowsAboutToBeInserted(1)")
<< QStringLiteral("rowsInserted(1)"));
}
// Start from [1[1.1[1.1.1 1.1.2[1.1.2.1*]]]]
// where 1.1.1 is hidden but 1.1 is shown, we want to insert a shown child in 1.1.1.
// The proxy ensures dataChanged is called on 1.1,
// so that 1.1.1 and 1.1.1.1 are included in the model.
void testInsertCousin()
{
QStandardItemModel model;
const QString sourceStr = QStringLiteral("[1[1.1[1.1.1 1.1.2[1.1.2.1*]]]]");
fillModel(model, sourceStr);
QCOMPARE(treeAsString(model), sourceStr);
TestModel proxy(&model);
QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[1.1.2[1.1.2.1*]]]]"));
ModelSignalSpy spy(proxy);
{
QStandardItem *item_1_1_1_1 = new QStandardItem(QStringLiteral("1.1.1.1"));
item_1_1_1_1->setData(true);
QStandardItem *item_1_1_1 = model.item(0)->child(0)->child(0);
item_1_1_1->appendRow(item_1_1_1_1);
}
QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[1.1.1[1.1.1.1*] 1.1.2[1.1.2.1*]]]]"));
QCOMPARE(spy.mSignals, QStringList()
<< QStringLiteral("rowsAboutToBeInserted(1.1.1)")
<< QStringLiteral("rowsInserted(1.1.1)"));
}
void testInsertWithChildren()
{
QStandardItemModel model;
const QString sourceStr = QStringLiteral("[1[1.1]]");
fillModel(model, sourceStr);
QCOMPARE(treeAsString(model), sourceStr);
TestModel proxy(&model);
QCOMPARE(treeAsString(proxy), QString());
ModelSignalSpy spy(proxy);
{
QStandardItem *item_1_1_1 = new QStandardItem(QStringLiteral("1.1.1"));
QStandardItem *item_1_1_1_1 = new QStandardItem(QStringLiteral("1.1.1.1"));
item_1_1_1_1->setData(true);
item_1_1_1->appendRow(item_1_1_1_1);
QStandardItem *item_1_1 = model.item(0)->child(0);
item_1_1->appendRow(item_1_1_1);
}
QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[1.1.1[1.1.1.1*]]]]"));
QCOMPARE(spy.mSignals, QStringList()
<< QStringLiteral("rowsAboutToBeInserted(1)")
<< QStringLiteral("rowsInserted(1)"));
}
void testInsertIntoVisibleWithChildren()
{
QStandardItemModel model;
const QString sourceStr = QStringLiteral("[1[1.1[1.1.1*]]]");
fillModel(model, sourceStr);
QCOMPARE(treeAsString(model), sourceStr);
TestModel proxy(&model);
QCOMPARE(treeAsString(proxy), sourceStr);
ModelSignalSpy spy(proxy);
{
QStandardItem *item_1_1_2 = new QStandardItem(QStringLiteral("1.1.2"));
QStandardItem *item_1_1_2_1 = new QStandardItem(QStringLiteral("1.1.2.1"));
item_1_1_2_1->setData(true);
item_1_1_2->appendRow(item_1_1_2_1);
QStandardItem *item_1_1 = model.item(0)->child(0);
item_1_1->appendRow(item_1_1_2);
}
QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]"));
QCOMPARE(spy.mSignals, QStringList()
<< QStringLiteral("rowsAboutToBeInserted(1.1.2)")
<< QStringLiteral("rowsInserted(1.1.2)"));
}
void testInsertHidden() // inserting filtered-out rows shouldn't emit anything
{
QStandardItemModel model;
const QString sourceStr = QStringLiteral("[1[1.1]]");
fillModel(model, sourceStr);
QCOMPARE(treeAsString(model), sourceStr);
TestModel proxy(&model);
QCOMPARE(treeAsString(proxy), QString());
ModelSignalSpy spy(proxy);
{
QStandardItem *item_1_1_1 = new QStandardItem(QStringLiteral("1.1.1"));
QStandardItem *item_1_1_1_1 = new QStandardItem(QStringLiteral("1.1.1.1"));
item_1_1_1->appendRow(item_1_1_1_1);
QStandardItem *item_1_1 = model.item(0)->child(0);
item_1_1->appendRow(item_1_1_1);
}
QCOMPARE(treeAsString(proxy), QString());
QCOMPARE(spy.mSignals, QStringList());
}
void testConsecutiveInserts_data()
{
testInitialFiltering_data();
}
void testConsecutiveInserts()
{
QFETCH(QString, sourceStr);
QFETCH(QString, proxyStr);
QStandardItemModel model;
TestModel proxy(&model); // this time the proxy listens to the model while we fill it
fillModel(model, sourceStr);
QCOMPARE(treeAsString(model), sourceStr);
QCOMPARE(treeAsString(proxy), proxyStr);
}
void testRemove_data()
{
QTest::addColumn<QString>("sourceStr");
QTest::addColumn<QString>("initialProxyStr");
QTest::addColumn<QString>("remove"); // remove this item
QTest::addColumn<QString>("expectedProxyStr");
QTest::addColumn<QStringList>("expectedSignals");
const QStringList remove1_1_1 = (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1.1.1)") << QStringLiteral("rowsRemoved(1.1.1)"));
QTest::newRow("toplevel") << "[1* 2* 3*]" << "[1* 2* 3*]" << "1" << "[2* 3*]"
<< (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1)") << QStringLiteral("rowsRemoved(1)"));
QTest::newRow("remove_hidden") << "[1 2* 3*]" << "[2* 3*]" << "1" << "[2* 3*]" << QStringList();
QTest::newRow("parent_hidden") << "[1[1.1[1.1.1]]]" << "" << "1.1.1" << "" << QStringList();
QTest::newRow("child_hidden") << "[1[1.1*[1.1.1]]]" << "[1[1.1*]]" << "1.1.1" << "[1[1.1*]]" << QStringList();
QTest::newRow("parent_visible") << "[1[1.1*[1.1.1*]]]" << "[1[1.1*[1.1.1*]]]" << "1.1.1" << "[1[1.1*]]"
<< remove1_1_1;
QTest::newRow("visible") << "[1[1.1[1.1.1* 1.1.2*]]]" << "[1[1.1[1.1.1* 1.1.2*]]]" << "1.1.1" << "[1[1.1[1.1.2*]]]"
<< remove1_1_1;
QTest::newRow("visible_cousin") << "[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]" << "[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]" << "1.1.1" << "[1[1.1[1.1.2[1.1.2.1*]]]]"
<< remove1_1_1;
// The following tests trigger the removal of an ascendant.
// We could optimize the rows{AboutToBe,}Removed(1.1.1) away, but that would
// require a filterAcceptsRow variant that ignores the about-to-be-removed row,
// in order to move the going-up loop from Removed to AboutToBeRemoved.
// It doesn't really hurt to have both pairs of signals though.
QTest::newRow("remove_parent") << "[1[1.1[1.1.1* 1.1.2] 1.2*]]" << "[1[1.1[1.1.1*] 1.2*]]" << "1.1.1" << "[1[1.2*]]"
<< (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1.1.1)") << QStringLiteral("rowsRemoved(1.1.1)")
<< QStringLiteral("rowsAboutToBeRemoved(1.1)") << QStringLiteral("rowsRemoved(1.1)")
);
QTest::newRow("with_children") << "[1[1.1[1.1.1[1.1.1.1*]]] 2*]" << "[1[1.1[1.1.1[1.1.1.1*]]] 2*]" << "1.1.1" << "[2*]"
<< (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1.1.1)") << QStringLiteral("rowsRemoved(1.1.1)")
<< QStringLiteral("rowsAboutToBeRemoved(1)") << QStringLiteral("rowsRemoved(1)")
);
QTest::newRow("last_visible") << "[1[1.1[1.1.1* 1.1.2]]]" << "[1[1.1[1.1.1*]]]" << "1.1.1" << ""
<< (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1.1.1)") << QStringLiteral("rowsRemoved(1.1.1)")
<< QStringLiteral("rowsAboutToBeRemoved(1)") << QStringLiteral("rowsRemoved(1)")
);
}
void testRemove()
{
QFETCH(QString, sourceStr);
QFETCH(QString, initialProxyStr);
QFETCH(QString, remove);
QFETCH(QString, expectedProxyStr);
QFETCH(QStringList, expectedSignals);
QStandardItemModel model;
fillModel(model, sourceStr);
QCOMPARE(treeAsString(model), sourceStr);
TestModel proxy(&model);
QCOMPARE(treeAsString(proxy), initialProxyStr);
ModelSignalSpy spy(proxy);
QStandardItem *itemToRemove = itemByText(model, remove);
QVERIFY(itemToRemove);
if (itemToRemove->parent())
itemToRemove->parent()->removeRow(itemToRemove->row());
else
model.removeRow(itemToRemove->row());
QCOMPARE(treeAsString(proxy), expectedProxyStr);
//qDebug() << spy.mSignals;
QCOMPARE(spy.mSignals, expectedSignals);
}
private:
QStandardItem *itemByText(const QStandardItemModel& model, const QString &text) const {
QModelIndexList list = model.match(model.index(0, 0), Qt::DisplayRole, text, 1, Qt::MatchRecursive);
return list.isEmpty() ? nullptr : model.itemFromIndex(list.first());
}
};
QTEST_MAIN(KRecursiveFilterProxyModelTest)
#include "krecursivefilterproxymodeltest.moc"