File indexing completed on 2025-03-23 09:56:01
0001 /* 0002 SPDX-FileCopyrightText: 2014 Christian Mollekopf <mollekopf@kolabsys.com> 0003 SPDX-FileCopyrightText: 2014 David Faure <faure@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include <krecursivefilterproxymodel.h> 0009 0010 #include <QStandardItemModel> 0011 #include <QTest> 0012 0013 Q_DECLARE_METATYPE(QModelIndex) 0014 0015 class ModelSignalSpy : public QObject 0016 { 0017 Q_OBJECT 0018 public: 0019 explicit ModelSignalSpy(QAbstractItemModel &model) 0020 { 0021 connect(&model, SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(onRowsInserted(QModelIndex, int, int))); 0022 connect(&model, SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(onRowsRemoved(QModelIndex, int, int))); 0023 connect(&model, SIGNAL(rowsAboutToBeInserted(QModelIndex, int, int)), this, SLOT(onRowsAboutToBeInserted(QModelIndex, int, int))); 0024 connect(&model, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), this, SLOT(onRowsAboutToBeRemoved(QModelIndex, int, int))); 0025 connect(&model, SIGNAL(rowsMoved(QModelIndex, int, int, QModelIndex, int)), this, SLOT(onRowsMoved(QModelIndex, int, int, QModelIndex, int))); 0026 connect(&model, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(onDataChanged(QModelIndex, QModelIndex))); 0027 connect(&model, SIGNAL(layoutChanged()), this, SLOT(onLayoutChanged())); 0028 connect(&model, SIGNAL(modelReset()), this, SLOT(onModelReset())); 0029 } 0030 0031 QStringList mSignals; 0032 0033 private Q_SLOTS: 0034 void onRowsInserted(QModelIndex p, int start, int end) 0035 { 0036 mSignals << QLatin1String("rowsInserted(") + textForRowSpy(p, start, end) + ')'; 0037 } 0038 void onRowsRemoved(QModelIndex p, int start, int end) 0039 { 0040 mSignals << QLatin1String("rowsRemoved(") + textForRowSpy(p, start, end) + ')'; 0041 } 0042 void onRowsAboutToBeInserted(QModelIndex p, int start, int end) 0043 { 0044 mSignals << QLatin1String("rowsAboutToBeInserted(") + textForRowSpy(p, start, end) + ')'; 0045 } 0046 void onRowsAboutToBeRemoved(QModelIndex p, int start, int end) 0047 { 0048 mSignals << QLatin1String("rowsAboutToBeRemoved(") + textForRowSpy(p, start, end) + ')'; 0049 } 0050 void onRowsMoved(QModelIndex, int, int, QModelIndex, int) 0051 { 0052 mSignals << QStringLiteral("rowsMoved"); 0053 } 0054 void onDataChanged(const QModelIndex &from, const QModelIndex &) 0055 { 0056 mSignals << QStringLiteral("dataChanged(%1)").arg(from.data().toString()); 0057 } 0058 void onLayoutChanged() 0059 { 0060 mSignals << QStringLiteral("layoutChanged"); 0061 } 0062 void onModelReset() 0063 { 0064 mSignals << QStringLiteral("modelReset"); 0065 } 0066 0067 private: 0068 QString textForRowSpy(const QModelIndex &parent, int start, int end) 0069 { 0070 QString txt = parent.data().toString(); 0071 if (!txt.isEmpty()) { 0072 txt += QLatin1Char('.'); 0073 } 0074 txt += QString::number(start + 1); 0075 if (start != end) { 0076 txt += QLatin1Char('-') + QString::number(end + 1); 0077 } 0078 return txt; 0079 } 0080 }; 0081 0082 class TestModel : public KRecursiveFilterProxyModel 0083 { 0084 Q_OBJECT 0085 public: 0086 TestModel(QAbstractItemModel *sourceModel) 0087 : KRecursiveFilterProxyModel() 0088 { 0089 setSourceModel(sourceModel); 0090 } 0091 0092 bool acceptRow(int sourceRow, const QModelIndex &sourceParent) const override 0093 { 0094 // qDebug() << sourceModel()->index(sourceRow, 0, sourceParent).data().toString() 0095 // << sourceModel()->index(sourceRow, 0, sourceParent).data(Qt::UserRole+1).toBool(); 0096 return sourceModel()->index(sourceRow, 0, sourceParent).data(Qt::UserRole + 1).toBool(); 0097 } 0098 }; 0099 0100 // Represents this tree 0101 // - A 0102 // - - B 0103 // - - - C 0104 // - - - D 0105 // - - E 0106 // as a single string, englobing children in brackets, like this: 0107 // [A[B[C D] E]] 0108 // In addition, items that match the filtering (data(UserRole+1) == true) have a * after their value. 0109 static QString treeAsString(const QAbstractItemModel &model, const QModelIndex &parent = QModelIndex()) 0110 { 0111 QString ret; 0112 const int rowCount = model.rowCount(parent); 0113 if (rowCount > 0) { 0114 ret += QLatin1Char('['); 0115 for (int row = 0; row < rowCount; ++row) { 0116 if (row > 0) { 0117 ret += ' '; 0118 } 0119 const QModelIndex child = model.index(row, 0, parent); 0120 ret += child.data().toString(); 0121 if (child.data(Qt::UserRole + 1).toBool()) { 0122 ret += QLatin1Char('*'); 0123 } 0124 ret += treeAsString(model, child); 0125 } 0126 ret += QLatin1Char(']'); 0127 } 0128 return ret; 0129 } 0130 0131 // Fill a tree model based on a string representation (see treeAsString) 0132 static void fillModel(QStandardItemModel &model, const QString &str) 0133 { 0134 QCOMPARE(str.count('['), str.count(']')); 0135 QStandardItem *item = nullptr; 0136 QString data; 0137 for (int i = 0; i < str.length(); ++i) { 0138 const QChar ch = str.at(i); 0139 if ((ch == '[' || ch == ']' || ch == ' ') && !data.isEmpty()) { 0140 if (data.endsWith('*')) { 0141 item->setData(true); 0142 data.chop(1); 0143 } 0144 item->setText(data); 0145 data.clear(); 0146 } 0147 if (ch == '[') { 0148 // Create new child 0149 QStandardItem *child = new QStandardItem; 0150 if (item) { 0151 item->appendRow(child); 0152 } else { 0153 model.appendRow(child); 0154 } 0155 item = child; 0156 } else if (ch == ']') { 0157 // Go up to parent 0158 item = item->parent(); 0159 } else if (ch == ' ') { 0160 // Create new sibling 0161 QStandardItem *child = new QStandardItem; 0162 QStandardItem *parent = item->parent(); 0163 if (parent) { 0164 parent->appendRow(child); 0165 } else { 0166 model.appendRow(child); 0167 } 0168 item = child; 0169 } else { 0170 data += ch; 0171 } 0172 } 0173 } 0174 0175 class KRecursiveFilterProxyModelTest : public QObject 0176 { 0177 Q_OBJECT 0178 private: 0179 private Q_SLOTS: 0180 void testInitialFiltering_data() 0181 { 0182 QTest::addColumn<QString>("sourceStr"); 0183 QTest::addColumn<QString>("proxyStr"); 0184 0185 QTest::newRow("empty") << "[]" 0186 << ""; 0187 QTest::newRow("no") << "[1]" 0188 << ""; 0189 QTest::newRow("yes") << "[1*]" 0190 << "[1*]"; 0191 QTest::newRow("second") << "[1 2*]" 0192 << "[2*]"; 0193 QTest::newRow("child_yes") << "[1 2[2.1*]]" 0194 << "[2[2.1*]]"; 0195 QTest::newRow("grandchild_yes") << "[1 2[2.1[2.1.1*]]]" 0196 << "[2[2.1[2.1.1*]]]"; 0197 // 1, 3.1 and 4.2.1 match, so their parents are in the model 0198 QTest::newRow("more") << "[1* 2[2.1] 3[3.1*] 4[4.1 4.2[4.2.1*]]]" 0199 << "[1* 3[3.1*] 4[4.2[4.2.1*]]]"; 0200 } 0201 0202 void testInitialFiltering() 0203 { 0204 QFETCH(QString, sourceStr); 0205 QFETCH(QString, proxyStr); 0206 0207 QStandardItemModel model; 0208 fillModel(model, sourceStr); 0209 QCOMPARE(treeAsString(model), sourceStr); 0210 0211 TestModel proxy(&model); 0212 QCOMPARE(treeAsString(proxy), proxyStr); 0213 } 0214 0215 // Test changing a role that is unrelated to the filtering. 0216 void testUnrelatedDataChange() 0217 { 0218 QStandardItemModel model; 0219 const QString sourceStr = QStringLiteral("[1[1.1[1.1.1*]]]"); 0220 fillModel(model, sourceStr); 0221 QCOMPARE(treeAsString(model), sourceStr); 0222 0223 TestModel proxy(&model); 0224 QCOMPARE(treeAsString(proxy), sourceStr); 0225 0226 ModelSignalSpy spy(proxy); 0227 QStandardItem *item_1_1_1 = model.item(0)->child(0)->child(0); 0228 0229 // When changing the text on the item 0230 item_1_1_1->setText(QStringLiteral("ME")); 0231 0232 QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[ME*]]]")); 0233 0234 QCOMPARE(spy.mSignals, 0235 QStringList() << QStringLiteral("dataChanged(ME)") 0236 << QStringLiteral("dataChanged(1.1)") // ### yep, unneeded, but the proxy has no way to know that... 0237 << QStringLiteral("dataChanged(1)") // ### unneeded too 0238 ); 0239 } 0240 0241 // Test changing a role that is unrelated to the filtering, in a hidden item. 0242 void testHiddenDataChange() 0243 { 0244 QStandardItemModel model; 0245 const QString sourceStr = QStringLiteral("[1[1.1[1.1.1]]]"); 0246 fillModel(model, sourceStr); 0247 QCOMPARE(treeAsString(model), sourceStr); 0248 0249 TestModel proxy(&model); 0250 QCOMPARE(treeAsString(proxy), QString()); 0251 0252 ModelSignalSpy spy(proxy); 0253 QStandardItem *item_1_1_1 = model.item(0)->child(0)->child(0); 0254 0255 // When changing the text on a hidden item 0256 item_1_1_1->setText(QStringLiteral("ME")); 0257 0258 QCOMPARE(treeAsString(proxy), QString()); 0259 QCOMPARE(spy.mSignals, QStringList()); 0260 } 0261 0262 // Test that we properly react to a data-changed signal in a descendant and include all required rows 0263 void testDataChangeIn_data() 0264 { 0265 QTest::addColumn<QString>("sourceStr"); 0266 QTest::addColumn<QString>("initialProxyStr"); 0267 QTest::addColumn<QString>("add"); // set the flag on this item 0268 QTest::addColumn<QString>("expectedProxyStr"); 0269 QTest::addColumn<QStringList>("expectedSignals"); 0270 0271 QTest::newRow("toplevel") << "[1]" 0272 << "" 0273 << "1" 0274 << "[1*]" << (QStringList() << QStringLiteral("rowsAboutToBeInserted(1)") << QStringLiteral("rowsInserted(1)")); 0275 QTest::newRow("show_parents") << "[1[1.1[1.1.1]]]" 0276 << "" 0277 << "1.1.1" 0278 << "[1[1.1[1.1.1*]]]" 0279 << (QStringList() << QStringLiteral("rowsAboutToBeInserted(1)") << QStringLiteral("rowsInserted(1)")); 0280 0281 const QStringList insert_1_1_1 = QStringList() << QStringLiteral("rowsAboutToBeInserted(1.1.1)") << QStringLiteral("rowsInserted(1.1.1)") 0282 << QStringLiteral("dataChanged(1.1)") << QStringLiteral("dataChanged(1)"); // both unneeded 0283 QTest::newRow("parent_visible") << "[1[1.1*[1.1.1]]]" 0284 << "[1[1.1*]]" 0285 << "1.1.1" 0286 << "[1[1.1*[1.1.1*]]]" << insert_1_1_1; 0287 0288 QTest::newRow("sibling_visible") << "[1[1.1[1.1.1 1.1.2*]]]" 0289 << "[1[1.1[1.1.2*]]]" 0290 << "1.1.1" 0291 << "[1[1.1[1.1.1* 1.1.2*]]]" << insert_1_1_1; 0292 0293 QTest::newRow("visible_cousin") << "[1[1.1[1.1.1 1.1.2[1.1.2.1*]]]]" 0294 << "[1[1.1[1.1.2[1.1.2.1*]]]]" 0295 << "1.1.1" 0296 << "[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]" << insert_1_1_1; 0297 0298 QTest::newRow("show_parent") << "[1[1.1[1.1.1 1.1.2] 1.2*]]" 0299 << "[1[1.2*]]" 0300 << "1.1.1" 0301 << "[1[1.1[1.1.1*] 1.2*]]" 0302 << (QStringList() << QStringLiteral("rowsAboutToBeInserted(1.1)") << QStringLiteral("rowsInserted(1.1)") 0303 << QStringLiteral("dataChanged(1)")); // unneeded 0304 0305 QTest::newRow("with_children") << "[1[1.1[1.1.1[1.1.1.1*]]] 2*]" 0306 << "[1[1.1[1.1.1[1.1.1.1*]]] 2*]" 0307 << "1.1.1" 0308 << "[1[1.1[1.1.1*[1.1.1.1*]]] 2*]" 0309 << (QStringList() << QStringLiteral("dataChanged(1.1.1)") << QStringLiteral("dataChanged(1.1)") 0310 << QStringLiteral("dataChanged(1)") // both unneeded 0311 ); 0312 } 0313 0314 void testDataChangeIn() 0315 { 0316 QFETCH(QString, sourceStr); 0317 QFETCH(QString, initialProxyStr); 0318 QFETCH(QString, add); 0319 QFETCH(QString, expectedProxyStr); 0320 QFETCH(QStringList, expectedSignals); 0321 0322 QStandardItemModel model; 0323 fillModel(model, sourceStr); 0324 QCOMPARE(treeAsString(model), sourceStr); 0325 0326 TestModel proxy(&model); 0327 QCOMPARE(treeAsString(proxy), initialProxyStr); 0328 0329 ModelSignalSpy spy(proxy); 0330 // When changing the data on the designated item to show this row 0331 QStandardItem *itemToChange = itemByText(model, add); 0332 QVERIFY(!itemToChange->data().toBool()); 0333 itemToChange->setData(true); 0334 0335 // The proxy should update as expected 0336 QCOMPARE(treeAsString(proxy), expectedProxyStr); 0337 0338 // qDebug() << spy.mSignals; 0339 QCOMPARE(spy.mSignals, expectedSignals); 0340 } 0341 0342 void testDataChangeOut_data() 0343 { 0344 QTest::addColumn<QString>("sourceStr"); 0345 QTest::addColumn<QString>("initialProxyStr"); 0346 QTest::addColumn<QString>("remove"); // unset the flag on this item 0347 QTest::addColumn<QString>("expectedProxyStr"); 0348 QTest::addColumn<QStringList>("expectedSignals"); 0349 0350 const QStringList remove1_1_1 = (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1.1.1)") << QStringLiteral("rowsRemoved(1.1.1)") 0351 << QStringLiteral("dataChanged(1.1)") // unneeded 0352 << QStringLiteral("dataChanged(1)") // unneeded 0353 ); 0354 0355 QTest::newRow("toplevel") << "[1*]" 0356 << "[1*]" 0357 << "1" 0358 << "" << (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1)") << QStringLiteral("rowsRemoved(1)")); 0359 0360 QTest::newRow("hide_parent") << "[1[1.1[1.1.1*]]]" 0361 << "[1[1.1[1.1.1*]]]" 0362 << "1.1.1" 0363 << "" 0364 << (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1.1.1)") // ### unneeded but the proxy has no way to know that... 0365 << QStringLiteral("rowsRemoved(1.1.1)") // unneeded 0366 << QStringLiteral("rowsAboutToBeRemoved(1.1)") // unneeded 0367 << QStringLiteral("rowsRemoved(1.1)") // unneeded 0368 << QStringLiteral("rowsAboutToBeRemoved(1)") << QStringLiteral("rowsRemoved(1)")); 0369 0370 QTest::newRow("parent_visible") << "[1[1.1*[1.1.1*]]]" 0371 << "[1[1.1*[1.1.1*]]]" 0372 << "1.1.1" 0373 << "[1[1.1*]]" << remove1_1_1; 0374 0375 QTest::newRow("visible") << "[1[1.1[1.1.1* 1.1.2*]]]" 0376 << "[1[1.1[1.1.1* 1.1.2*]]]" 0377 << "1.1.1" 0378 << "[1[1.1[1.1.2*]]]" << remove1_1_1; 0379 QTest::newRow("visible_cousin") << "[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]" 0380 << "[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]" 0381 << "1.1.1" 0382 << "[1[1.1[1.1.2[1.1.2.1*]]]]" << remove1_1_1; 0383 0384 // The following tests trigger the removal of an ascendant. 0385 QTest::newRow("remove_parent") << "[1[1.1[1.1.1* 1.1.2] 1.2*]]" 0386 << "[1[1.1[1.1.1*] 1.2*]]" 0387 << "1.1.1" 0388 << "[1[1.2*]]" 0389 << (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1.1.1)") << QStringLiteral("rowsRemoved(1.1.1)") 0390 << QStringLiteral("rowsAboutToBeRemoved(1.1)") << QStringLiteral("rowsRemoved(1.1)") 0391 << QStringLiteral("dataChanged(1)") // unneeded 0392 ); 0393 0394 QTest::newRow("with_children") << "[1[1.1[1.1.1*[1.1.1.1*]]] 2*]" 0395 << "[1[1.1[1.1.1*[1.1.1.1*]]] 2*]" 0396 << "1.1.1" 0397 << "[1[1.1[1.1.1[1.1.1.1*]]] 2*]" 0398 << (QStringList() << QStringLiteral("dataChanged(1.1.1)") << QStringLiteral("dataChanged(1.1)") 0399 << QStringLiteral("dataChanged(1)") // both unneeded 0400 ); 0401 0402 QTest::newRow("last_visible") << "[1[1.1[1.1.1* 1.1.2]]]" 0403 << "[1[1.1[1.1.1*]]]" 0404 << "1.1.1" 0405 << "" 0406 << (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1.1.1)") << QStringLiteral("rowsRemoved(1.1.1)") // unneeded 0407 << QStringLiteral("rowsAboutToBeRemoved(1.1)") << QStringLiteral("rowsRemoved(1.1)") // unneeded 0408 << QStringLiteral("rowsAboutToBeRemoved(1)") << QStringLiteral("rowsRemoved(1)")); 0409 } 0410 0411 void testDataChangeOut() 0412 { 0413 QFETCH(QString, sourceStr); 0414 QFETCH(QString, initialProxyStr); 0415 QFETCH(QString, remove); 0416 QFETCH(QString, expectedProxyStr); 0417 QFETCH(QStringList, expectedSignals); 0418 0419 QStandardItemModel model; 0420 fillModel(model, sourceStr); 0421 QCOMPARE(treeAsString(model), sourceStr); 0422 0423 TestModel proxy(&model); 0424 QCOMPARE(treeAsString(proxy), initialProxyStr); 0425 0426 ModelSignalSpy spy(proxy); 0427 0428 // When changing the data on the designated item to exclude this row again 0429 QStandardItem *itemToChange = itemByText(model, remove); 0430 QVERIFY(itemToChange->data().toBool()); 0431 itemToChange->setData(false); 0432 0433 // The proxy should update as expected 0434 QCOMPARE(treeAsString(proxy), expectedProxyStr); 0435 0436 // qDebug() << spy.mSignals; 0437 QCOMPARE(spy.mSignals, expectedSignals); 0438 } 0439 0440 void testInsert() 0441 { 0442 QStandardItemModel model; 0443 const QString sourceStr = QStringLiteral("[1[1.1[1.1.1]]]"); 0444 fillModel(model, sourceStr); 0445 QCOMPARE(treeAsString(model), sourceStr); 0446 0447 TestModel proxy(&model); 0448 QCOMPARE(treeAsString(proxy), QString()); 0449 0450 ModelSignalSpy spy(proxy); 0451 QStandardItem *item_1_1_1 = model.item(0)->child(0)->child(0); 0452 QStandardItem *item_1_1_1_1 = new QStandardItem(QStringLiteral("1.1.1.1")); 0453 item_1_1_1_1->setData(true); 0454 item_1_1_1->appendRow(item_1_1_1_1); 0455 QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[1.1.1[1.1.1.1*]]]]")); 0456 0457 QCOMPARE(spy.mSignals, QStringList() << QStringLiteral("rowsAboutToBeInserted(1)") << QStringLiteral("rowsInserted(1)")); 0458 } 0459 0460 // Start from [1[1.1[1.1.1 1.1.2[1.1.2.1*]]]] 0461 // where 1.1.1 is hidden but 1.1 is shown, we want to insert a shown child in 1.1.1. 0462 // The proxy ensures dataChanged is called on 1.1, 0463 // so that 1.1.1 and 1.1.1.1 are included in the model. 0464 void testInsertCousin() 0465 { 0466 QStandardItemModel model; 0467 const QString sourceStr = QStringLiteral("[1[1.1[1.1.1 1.1.2[1.1.2.1*]]]]"); 0468 fillModel(model, sourceStr); 0469 QCOMPARE(treeAsString(model), sourceStr); 0470 0471 TestModel proxy(&model); 0472 QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[1.1.2[1.1.2.1*]]]]")); 0473 0474 ModelSignalSpy spy(proxy); 0475 { 0476 QStandardItem *item_1_1_1_1 = new QStandardItem(QStringLiteral("1.1.1.1")); 0477 item_1_1_1_1->setData(true); 0478 QStandardItem *item_1_1_1 = model.item(0)->child(0)->child(0); 0479 item_1_1_1->appendRow(item_1_1_1_1); 0480 } 0481 0482 QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[1.1.1[1.1.1.1*] 1.1.2[1.1.2.1*]]]]")); 0483 QCOMPARE(spy.mSignals, QStringList() << QStringLiteral("rowsAboutToBeInserted(1.1.1)") << QStringLiteral("rowsInserted(1.1.1)")); 0484 } 0485 0486 void testInsertWithChildren() 0487 { 0488 QStandardItemModel model; 0489 const QString sourceStr = QStringLiteral("[1[1.1]]"); 0490 fillModel(model, sourceStr); 0491 QCOMPARE(treeAsString(model), sourceStr); 0492 0493 TestModel proxy(&model); 0494 QCOMPARE(treeAsString(proxy), QString()); 0495 0496 ModelSignalSpy spy(proxy); 0497 { 0498 QStandardItem *item_1_1_1 = new QStandardItem(QStringLiteral("1.1.1")); 0499 QStandardItem *item_1_1_1_1 = new QStandardItem(QStringLiteral("1.1.1.1")); 0500 item_1_1_1_1->setData(true); 0501 item_1_1_1->appendRow(item_1_1_1_1); 0502 0503 QStandardItem *item_1_1 = model.item(0)->child(0); 0504 item_1_1->appendRow(item_1_1_1); 0505 } 0506 0507 QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[1.1.1[1.1.1.1*]]]]")); 0508 QCOMPARE(spy.mSignals, QStringList() << QStringLiteral("rowsAboutToBeInserted(1)") << QStringLiteral("rowsInserted(1)")); 0509 } 0510 0511 void testInsertIntoVisibleWithChildren() 0512 { 0513 QStandardItemModel model; 0514 const QString sourceStr = QStringLiteral("[1[1.1[1.1.1*]]]"); 0515 fillModel(model, sourceStr); 0516 QCOMPARE(treeAsString(model), sourceStr); 0517 0518 TestModel proxy(&model); 0519 QCOMPARE(treeAsString(proxy), sourceStr); 0520 0521 ModelSignalSpy spy(proxy); 0522 { 0523 QStandardItem *item_1_1_2 = new QStandardItem(QStringLiteral("1.1.2")); 0524 QStandardItem *item_1_1_2_1 = new QStandardItem(QStringLiteral("1.1.2.1")); 0525 item_1_1_2_1->setData(true); 0526 item_1_1_2->appendRow(item_1_1_2_1); 0527 0528 QStandardItem *item_1_1 = model.item(0)->child(0); 0529 item_1_1->appendRow(item_1_1_2); 0530 } 0531 0532 QCOMPARE(treeAsString(proxy), QStringLiteral("[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]")); 0533 QCOMPARE(spy.mSignals, QStringList() << QStringLiteral("rowsAboutToBeInserted(1.1.2)") << QStringLiteral("rowsInserted(1.1.2)")); 0534 } 0535 0536 void testInsertHidden() // inserting filtered-out rows shouldn't emit anything 0537 { 0538 QStandardItemModel model; 0539 const QString sourceStr = QStringLiteral("[1[1.1]]"); 0540 fillModel(model, sourceStr); 0541 QCOMPARE(treeAsString(model), sourceStr); 0542 0543 TestModel proxy(&model); 0544 QCOMPARE(treeAsString(proxy), QString()); 0545 0546 ModelSignalSpy spy(proxy); 0547 { 0548 QStandardItem *item_1_1_1 = new QStandardItem(QStringLiteral("1.1.1")); 0549 QStandardItem *item_1_1_1_1 = new QStandardItem(QStringLiteral("1.1.1.1")); 0550 item_1_1_1->appendRow(item_1_1_1_1); 0551 0552 QStandardItem *item_1_1 = model.item(0)->child(0); 0553 item_1_1->appendRow(item_1_1_1); 0554 } 0555 0556 QCOMPARE(treeAsString(proxy), QString()); 0557 QCOMPARE(spy.mSignals, QStringList()); 0558 } 0559 0560 void testConsecutiveInserts_data() 0561 { 0562 testInitialFiltering_data(); 0563 } 0564 0565 void testConsecutiveInserts() 0566 { 0567 QFETCH(QString, sourceStr); 0568 QFETCH(QString, proxyStr); 0569 0570 QStandardItemModel model; 0571 TestModel proxy(&model); // this time the proxy listens to the model while we fill it 0572 0573 fillModel(model, sourceStr); 0574 QCOMPARE(treeAsString(model), sourceStr); 0575 QCOMPARE(treeAsString(proxy), proxyStr); 0576 } 0577 0578 void testRemove_data() 0579 { 0580 QTest::addColumn<QString>("sourceStr"); 0581 QTest::addColumn<QString>("initialProxyStr"); 0582 QTest::addColumn<QString>("remove"); // remove this item 0583 QTest::addColumn<QString>("expectedProxyStr"); 0584 QTest::addColumn<QStringList>("expectedSignals"); 0585 0586 const QStringList remove1_1_1 = (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1.1.1)") << QStringLiteral("rowsRemoved(1.1.1)")); 0587 0588 QTest::newRow("toplevel") << "[1* 2* 3*]" 0589 << "[1* 2* 3*]" 0590 << "1" 0591 << "[2* 3*]" << (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1)") << QStringLiteral("rowsRemoved(1)")); 0592 0593 QTest::newRow("remove_hidden") << "[1 2* 3*]" 0594 << "[2* 3*]" 0595 << "1" 0596 << "[2* 3*]" << QStringList(); 0597 0598 QTest::newRow("parent_hidden") << "[1[1.1[1.1.1]]]" 0599 << "" 0600 << "1.1.1" 0601 << "" << QStringList(); 0602 0603 QTest::newRow("child_hidden") << "[1[1.1*[1.1.1]]]" 0604 << "[1[1.1*]]" 0605 << "1.1.1" 0606 << "[1[1.1*]]" << QStringList(); 0607 0608 QTest::newRow("parent_visible") << "[1[1.1*[1.1.1*]]]" 0609 << "[1[1.1*[1.1.1*]]]" 0610 << "1.1.1" 0611 << "[1[1.1*]]" << remove1_1_1; 0612 0613 QTest::newRow("visible") << "[1[1.1[1.1.1* 1.1.2*]]]" 0614 << "[1[1.1[1.1.1* 1.1.2*]]]" 0615 << "1.1.1" 0616 << "[1[1.1[1.1.2*]]]" << remove1_1_1; 0617 QTest::newRow("visible_cousin") << "[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]" 0618 << "[1[1.1[1.1.1* 1.1.2[1.1.2.1*]]]]" 0619 << "1.1.1" 0620 << "[1[1.1[1.1.2[1.1.2.1*]]]]" << remove1_1_1; 0621 0622 // The following tests trigger the removal of an ascendant. 0623 // We could optimize the rows{AboutToBe,}Removed(1.1.1) away, but that would 0624 // require a filterAcceptsRow variant that ignores the about-to-be-removed row, 0625 // in order to move the going-up loop from Removed to AboutToBeRemoved. 0626 // It doesn't really hurt to have both pairs of signals though. 0627 0628 QTest::newRow("remove_parent") << "[1[1.1[1.1.1* 1.1.2] 1.2*]]" 0629 << "[1[1.1[1.1.1*] 1.2*]]" 0630 << "1.1.1" 0631 << "[1[1.2*]]" 0632 << (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1.1.1)") << QStringLiteral("rowsRemoved(1.1.1)") 0633 << QStringLiteral("rowsAboutToBeRemoved(1.1)") << QStringLiteral("rowsRemoved(1.1)")); 0634 0635 QTest::newRow("with_children") << "[1[1.1[1.1.1[1.1.1.1*]]] 2*]" 0636 << "[1[1.1[1.1.1[1.1.1.1*]]] 2*]" 0637 << "1.1.1" 0638 << "[2*]" 0639 << (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1.1.1)") << QStringLiteral("rowsRemoved(1.1.1)") 0640 << QStringLiteral("rowsAboutToBeRemoved(1)") << QStringLiteral("rowsRemoved(1)")); 0641 0642 QTest::newRow("last_visible") << "[1[1.1[1.1.1* 1.1.2]]]" 0643 << "[1[1.1[1.1.1*]]]" 0644 << "1.1.1" 0645 << "" 0646 << (QStringList() << QStringLiteral("rowsAboutToBeRemoved(1.1.1)") << QStringLiteral("rowsRemoved(1.1.1)") 0647 << QStringLiteral("rowsAboutToBeRemoved(1)") << QStringLiteral("rowsRemoved(1)")); 0648 } 0649 0650 void testRemove() 0651 { 0652 QFETCH(QString, sourceStr); 0653 QFETCH(QString, initialProxyStr); 0654 QFETCH(QString, remove); 0655 QFETCH(QString, expectedProxyStr); 0656 QFETCH(QStringList, expectedSignals); 0657 0658 QStandardItemModel model; 0659 fillModel(model, sourceStr); 0660 QCOMPARE(treeAsString(model), sourceStr); 0661 0662 TestModel proxy(&model); 0663 QCOMPARE(treeAsString(proxy), initialProxyStr); 0664 0665 ModelSignalSpy spy(proxy); 0666 QStandardItem *itemToRemove = itemByText(model, remove); 0667 QVERIFY(itemToRemove); 0668 if (itemToRemove->parent()) { 0669 itemToRemove->parent()->removeRow(itemToRemove->row()); 0670 } else { 0671 model.removeRow(itemToRemove->row()); 0672 } 0673 QCOMPARE(treeAsString(proxy), expectedProxyStr); 0674 0675 // qDebug() << spy.mSignals; 0676 QCOMPARE(spy.mSignals, expectedSignals); 0677 } 0678 0679 private: 0680 QStandardItem *itemByText(const QStandardItemModel &model, const QString &text) const 0681 { 0682 QModelIndexList list = model.match(model.index(0, 0), Qt::DisplayRole, text, 1, Qt::MatchRecursive); 0683 return list.isEmpty() ? nullptr : model.itemFromIndex(list.first()); 0684 } 0685 }; 0686 0687 QTEST_MAIN(KRecursiveFilterProxyModelTest) 0688 0689 #include "krecursivefilterproxymodeltest.moc"