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
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"
|
|
|