File indexing completed on 2024-04-28 05:45:21

0001 /*
0002  * SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com>
0003  * SPDX-FileCopyrightText: 2011 Frank Reininghaus <frank78ac@googlemail.com>
0004  *
0005  * SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include <QMimeData>
0009 #include <QRandomGenerator>
0010 #include <QSignalSpy>
0011 #include <QStandardPaths>
0012 #include <QTest>
0013 #include <QTimer>
0014 
0015 #include <KDirLister>
0016 #include <KIO/SimpleJob>
0017 
0018 #include "kitemviews/kfileitemmodel.h"
0019 #include "testdir.h"
0020 
0021 void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
0022 {
0023     Q_UNUSED(context)
0024 
0025     switch (type) {
0026     case QtDebugMsg:
0027         break;
0028     case QtWarningMsg:
0029         break;
0030     case QtCriticalMsg:
0031         fprintf(stderr, "Critical: %s\n", msg.toLocal8Bit().data());
0032         break;
0033     case QtFatalMsg:
0034         fprintf(stderr, "Fatal: %s\n", msg.toLocal8Bit().data());
0035         abort();
0036     default:
0037         break;
0038     }
0039 }
0040 
0041 Q_DECLARE_METATYPE(KItemRange)
0042 Q_DECLARE_METATYPE(KItemRangeList)
0043 Q_DECLARE_METATYPE(QList<int>)
0044 
0045 class KFileItemModelTest : public QObject
0046 {
0047     Q_OBJECT
0048 
0049 private Q_SLOTS:
0050     void init();
0051     void initTestCase();
0052     void cleanup();
0053 
0054     void testDefaultRoles();
0055     void testDefaultSortRole();
0056     void testDefaultGroupedSorting();
0057     void testNewItems();
0058     void testRemoveItems();
0059     void testDirLoadingCompleted();
0060     void testSetData();
0061     void testSetDataWithModifiedSortRole_data();
0062     void testSetDataWithModifiedSortRole();
0063     void testChangeSortRole();
0064     void testResortAfterChangingName();
0065     void testModelConsistencyWhenInsertingItems();
0066     void testItemRangeConsistencyWhenInsertingItems();
0067     void testExpandItems();
0068     void testExpandParentItems();
0069     void testMakeExpandedItemHidden();
0070     void testRemoveFilteredExpandedItems();
0071     void testSorting();
0072     void testIndexForKeyboardSearch();
0073     void testNameFilter();
0074     void testEmptyPath();
0075     void testRefreshExpandedItem();
0076     void testAddItemToFilteredExpandedFolder();
0077     void testDeleteItemsWithExpandedFolderWithFilter();
0078     void testRefreshItemsWithFilter();
0079     void testRefreshExpandedFolderWithFilter();
0080     void testRemoveHiddenItems();
0081     void collapseParentOfHiddenItems();
0082     void removeParentOfHiddenItems();
0083     void testGeneralParentChildRelationships();
0084     void testNameRoleGroups();
0085     void testNameRoleGroupsWithExpandedItems();
0086     void testInconsistentModel();
0087     void testChangeRolesForFilteredItems();
0088     void testChangeSortRoleWhileFiltering();
0089     void testRefreshFilteredItems();
0090     void testCollapseFolderWhileLoading();
0091     void testCreateMimeData();
0092     void testDeleteFileMoreThanOnce();
0093     void testInsertAfterExpand();
0094     void testCurrentDirRemoved();
0095     void testSizeSortingAfterRefresh();
0096 
0097 private:
0098     QStringList itemsInModel() const;
0099 
0100 private:
0101     KFileItemModel *m_model;
0102     TestDir *m_testDir;
0103 };
0104 
0105 void KFileItemModelTest::initTestCase()
0106 {
0107     QStandardPaths::setTestModeEnabled(true);
0108 }
0109 
0110 void KFileItemModelTest::init()
0111 {
0112     // The item-model tests result in a huge number of debugging
0113     // output from kdelibs. Only show critical and fatal messages.
0114     qInstallMessageHandler(myMessageOutput);
0115 
0116     qRegisterMetaType<KItemRange>("KItemRange");
0117     qRegisterMetaType<KItemRangeList>("KItemRangeList");
0118     qRegisterMetaType<KFileItemList>("KFileItemList");
0119 
0120     m_testDir = new TestDir();
0121     m_model = new KFileItemModel();
0122     m_model->m_dirLister->setAutoUpdate(false);
0123 
0124     // Reduce the timer interval to make the test run faster.
0125     m_model->m_resortAllItemsTimer->setInterval(0);
0126 }
0127 
0128 void KFileItemModelTest::cleanup()
0129 {
0130     delete m_model;
0131     m_model = nullptr;
0132 
0133     delete m_testDir;
0134     m_testDir = nullptr;
0135 }
0136 
0137 void KFileItemModelTest::testDefaultRoles()
0138 {
0139     const QSet<QByteArray> roles = m_model->roles();
0140     QCOMPARE(roles.count(), 4);
0141     QVERIFY(roles.contains("text"));
0142     QVERIFY(roles.contains("isDir"));
0143     QVERIFY(roles.contains("isLink"));
0144     QVERIFY(roles.contains("isHidden"));
0145 }
0146 
0147 void KFileItemModelTest::testDefaultSortRole()
0148 {
0149     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
0150     QVERIFY(itemsInsertedSpy.isValid());
0151 
0152     QCOMPARE(m_model->sortRole(), QByteArray("text"));
0153 
0154     m_testDir->createFiles({"c.txt", "a.txt", "b.txt"});
0155 
0156     m_model->loadDirectory(m_testDir->url());
0157     QVERIFY(itemsInsertedSpy.wait());
0158 
0159     QCOMPARE(m_model->count(), 3);
0160     QCOMPARE(m_model->data(0).value("text").toString(), QString("a.txt"));
0161     QCOMPARE(m_model->data(1).value("text").toString(), QString("b.txt"));
0162     QCOMPARE(m_model->data(2).value("text").toString(), QString("c.txt"));
0163 }
0164 
0165 void KFileItemModelTest::testDefaultGroupedSorting()
0166 {
0167     QCOMPARE(m_model->groupedSorting(), false);
0168 }
0169 
0170 void KFileItemModelTest::testNewItems()
0171 {
0172     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
0173 
0174     m_testDir->createFiles({"a.txt", "b.txt", "c.txt"});
0175 
0176     m_model->loadDirectory(m_testDir->url());
0177     QVERIFY(itemsInsertedSpy.wait());
0178 
0179     QCOMPARE(m_model->count(), 3);
0180 
0181     QVERIFY(m_model->isConsistent());
0182 }
0183 
0184 void KFileItemModelTest::testRemoveItems()
0185 {
0186     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
0187     QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved);
0188 
0189     m_testDir->createFiles({"a.txt", "b.txt"});
0190     m_model->loadDirectory(m_testDir->url());
0191     QVERIFY(itemsInsertedSpy.wait());
0192     QCOMPARE(m_model->count(), 2);
0193     QVERIFY(m_model->isConsistent());
0194 
0195     m_testDir->removeFile("a.txt");
0196     m_model->m_dirLister->updateDirectory(m_testDir->url());
0197     QVERIFY(itemsRemovedSpy.wait());
0198     QCOMPARE(m_model->count(), 1);
0199     QVERIFY(m_model->isConsistent());
0200 }
0201 
0202 void KFileItemModelTest::testDirLoadingCompleted()
0203 {
0204     QSignalSpy loadingCompletedSpy(m_model, &KFileItemModel::directoryLoadingCompleted);
0205     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
0206     QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved);
0207 
0208     m_testDir->createFiles({"a.txt", "b.txt", "c.txt"});
0209 
0210     m_model->loadDirectory(m_testDir->url());
0211     QVERIFY(loadingCompletedSpy.wait());
0212     QCOMPARE(loadingCompletedSpy.count(), 1);
0213     QCOMPARE(itemsInsertedSpy.count(), 1);
0214     QCOMPARE(itemsRemovedSpy.count(), 0);
0215     QCOMPARE(m_model->count(), 3);
0216 
0217     m_testDir->createFiles({"d.txt", "e.txt"});
0218     m_model->m_dirLister->updateDirectory(m_testDir->url());
0219     QVERIFY(loadingCompletedSpy.wait());
0220     QCOMPARE(loadingCompletedSpy.count(), 2);
0221     QCOMPARE(itemsInsertedSpy.count(), 2);
0222     QCOMPARE(itemsRemovedSpy.count(), 0);
0223     QCOMPARE(m_model->count(), 5);
0224 
0225     m_testDir->removeFile("a.txt");
0226     m_testDir->createFile("f.txt");
0227     m_model->m_dirLister->updateDirectory(m_testDir->url());
0228     QVERIFY(loadingCompletedSpy.wait());
0229     QCOMPARE(loadingCompletedSpy.count(), 3);
0230     QCOMPARE(itemsInsertedSpy.count(), 3);
0231     QCOMPARE(itemsRemovedSpy.count(), 1);
0232     QCOMPARE(m_model->count(), 5);
0233 
0234     m_testDir->removeFile("b.txt");
0235     m_model->m_dirLister->updateDirectory(m_testDir->url());
0236     QVERIFY(itemsRemovedSpy.wait());
0237     QCOMPARE(loadingCompletedSpy.count(), 4);
0238     QCOMPARE(itemsInsertedSpy.count(), 3);
0239     QCOMPARE(itemsRemovedSpy.count(), 2);
0240     QCOMPARE(m_model->count(), 4);
0241 
0242     QVERIFY(m_model->isConsistent());
0243 }
0244 
0245 void KFileItemModelTest::testSetData()
0246 {
0247     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
0248     QVERIFY(itemsInsertedSpy.isValid());
0249     QSignalSpy itemsChangedSpy(m_model, &KFileItemModel::itemsChanged);
0250     QVERIFY(itemsChangedSpy.isValid());
0251 
0252     m_testDir->createFile("a.txt");
0253 
0254     m_model->loadDirectory(m_testDir->url());
0255     QVERIFY(itemsInsertedSpy.wait());
0256 
0257     QHash<QByteArray, QVariant> values;
0258     values.insert("customRole1", "Test1");
0259     values.insert("customRole2", "Test2");
0260 
0261     m_model->setData(0, values);
0262     QCOMPARE(itemsChangedSpy.count(), 1);
0263 
0264     values = m_model->data(0);
0265     QCOMPARE(values.value("customRole1").toString(), QString("Test1"));
0266     QCOMPARE(values.value("customRole2").toString(), QString("Test2"));
0267     QVERIFY(m_model->isConsistent());
0268 }
0269 
0270 void KFileItemModelTest::testSetDataWithModifiedSortRole_data()
0271 {
0272     QTest::addColumn<int>("changedIndex");
0273     QTest::addColumn<int>("changedRating");
0274     QTest::addColumn<bool>("expectMoveSignal");
0275     QTest::addColumn<int>("ratingIndex0");
0276     QTest::addColumn<int>("ratingIndex1");
0277     QTest::addColumn<int>("ratingIndex2");
0278 
0279     // Default setup:
0280     // Index 0 = rating 2
0281     // Index 1 = rating 4
0282     // Index 2 = rating 6
0283 
0284     QTest::newRow("Index 0: Rating 3") << 0 << 3 << false << 3 << 4 << 6;
0285     QTest::newRow("Index 0: Rating 5") << 0 << 5 << true << 4 << 5 << 6;
0286     QTest::newRow("Index 0: Rating 8") << 0 << 8 << true << 4 << 6 << 8;
0287 
0288     QTest::newRow("Index 2: Rating 1") << 2 << 1 << true << 1 << 2 << 4;
0289     QTest::newRow("Index 2: Rating 3") << 2 << 3 << true << 2 << 3 << 4;
0290     QTest::newRow("Index 2: Rating 5") << 2 << 5 << false << 2 << 4 << 5;
0291 }
0292 
0293 void KFileItemModelTest::testSetDataWithModifiedSortRole()
0294 {
0295     QFETCH(int, changedIndex);
0296     QFETCH(int, changedRating);
0297     QFETCH(bool, expectMoveSignal);
0298     QFETCH(int, ratingIndex0);
0299     QFETCH(int, ratingIndex1);
0300     QFETCH(int, ratingIndex2);
0301 
0302     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
0303     QVERIFY(itemsInsertedSpy.isValid());
0304     QSignalSpy itemsMovedSpy(m_model, &KFileItemModel::itemsMoved);
0305     QVERIFY(itemsMovedSpy.isValid());
0306 
0307     // Changing the value of a sort-role must result in
0308     // a reordering of the items.
0309     QCOMPARE(m_model->sortRole(), QByteArray("text"));
0310     m_model->setSortRole("rating");
0311     QCOMPARE(m_model->sortRole(), QByteArray("rating"));
0312 
0313     m_testDir->createFiles({"a.txt", "b.txt", "c.txt"});
0314 
0315     m_model->loadDirectory(m_testDir->url());
0316     QVERIFY(itemsInsertedSpy.wait());
0317 
0318     // Fill the "rating" role of each file:
0319     // a.txt -> 2
0320     // b.txt -> 4
0321     // c.txt -> 6
0322 
0323     QHash<QByteArray, QVariant> ratingA;
0324     ratingA.insert("rating", 2);
0325     m_model->setData(0, ratingA);
0326 
0327     QHash<QByteArray, QVariant> ratingB;
0328     ratingB.insert("rating", 4);
0329     m_model->setData(1, ratingB);
0330 
0331     QHash<QByteArray, QVariant> ratingC;
0332     ratingC.insert("rating", 6);
0333     m_model->setData(2, ratingC);
0334 
0335     QCOMPARE(m_model->data(0).value("rating").toInt(), 2);
0336     QCOMPARE(m_model->data(1).value("rating").toInt(), 4);
0337     QCOMPARE(m_model->data(2).value("rating").toInt(), 6);
0338 
0339     // Now change the rating from a.txt. This usually results
0340     // in reordering of the items.
0341     QHash<QByteArray, QVariant> rating;
0342     rating.insert("rating", changedRating);
0343     m_model->setData(changedIndex, rating);
0344 
0345     if (expectMoveSignal) {
0346         QVERIFY(itemsMovedSpy.wait());
0347     }
0348 
0349     QCOMPARE(m_model->data(0).value("rating").toInt(), ratingIndex0);
0350     QCOMPARE(m_model->data(1).value("rating").toInt(), ratingIndex1);
0351     QCOMPARE(m_model->data(2).value("rating").toInt(), ratingIndex2);
0352     QVERIFY(m_model->isConsistent());
0353 }
0354 
0355 void KFileItemModelTest::testChangeSortRole()
0356 {
0357     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
0358     QSignalSpy itemsMovedSpy(m_model, &KFileItemModel::itemsMoved);
0359     QVERIFY(itemsMovedSpy.isValid());
0360 
0361     QCOMPARE(m_model->sortRole(), QByteArray("text"));
0362 
0363     m_testDir->createFiles({"a.txt", "b.jpg", "c.txt"});
0364 
0365     m_model->loadDirectory(m_testDir->url());
0366     QVERIFY(itemsInsertedSpy.wait());
0367     QCOMPARE(itemsInModel(),
0368              QStringList() << "a.txt"
0369                            << "b.jpg"
0370                            << "c.txt");
0371 
0372     // Simulate that KFileItemModelRolesUpdater determines the mime type.
0373     // Resorting the files by 'type' will only work immediately if their
0374     // mime types are known.
0375     for (int index = 0; index < m_model->count(); ++index) {
0376         m_model->fileItem(index).determineMimeType();
0377     }
0378 
0379     // Now: sort by type.
0380     m_model->setSortRole("type");
0381     QCOMPARE(m_model->sortRole(), QByteArray("type"));
0382     QVERIFY(!itemsMovedSpy.isEmpty());
0383 
0384     // The actual order of the files might depend on the translation of the
0385     // result of KFileItem::mimeComment() in the user's language.
0386     QStringList version1;
0387     version1 << "b.jpg"
0388              << "a.txt"
0389              << "c.txt";
0390 
0391     QStringList version2;
0392     version2 << "a.txt"
0393              << "c.txt"
0394              << "b.jpg";
0395 
0396     const bool ok1 = (itemsInModel() == version1);
0397     const bool ok2 = (itemsInModel() == version2);
0398 
0399     QVERIFY(ok1 || ok2);
0400 }
0401 
0402 void KFileItemModelTest::testResortAfterChangingName()
0403 {
0404     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
0405     QSignalSpy itemsMovedSpy(m_model, &KFileItemModel::itemsMoved);
0406     QVERIFY(itemsMovedSpy.isValid());
0407 
0408     // We sort by size in a directory where all files have the same size.
0409     // Therefore, the files are sorted by their names.
0410     m_model->setSortRole("size");
0411 
0412     m_testDir->createFiles({"a.txt", "b.txt", "c.txt"});
0413 
0414     m_model->loadDirectory(m_testDir->url());
0415     QVERIFY(itemsInsertedSpy.wait());
0416     QCOMPARE(itemsInModel(),
0417              QStringList() << "a.txt"
0418                            << "b.txt"
0419                            << "c.txt");
0420 
0421     // We rename a.txt to d.txt. Even though the size has not changed at all,
0422     // the model must re-sort the items.
0423     QHash<QByteArray, QVariant> data;
0424     data.insert("text", "d.txt");
0425     m_model->setData(0, data);
0426 
0427     QVERIFY(itemsMovedSpy.wait());
0428     QCOMPARE(itemsInModel(),
0429              QStringList() << "b.txt"
0430                            << "c.txt"
0431                            << "d.txt");
0432 
0433     // We rename d.txt back to a.txt using the dir lister's refreshItems() signal.
0434     const KFileItem fileItemD = m_model->fileItem(2);
0435     KFileItem fileItemA = fileItemD;
0436     QUrl urlA = fileItemA.url().adjusted(QUrl::RemoveFilename);
0437     urlA.setPath(urlA.path() + "a.txt");
0438     fileItemA.setUrl(urlA);
0439 
0440     m_model->slotRefreshItems({qMakePair(fileItemD, fileItemA)});
0441 
0442     QVERIFY(itemsMovedSpy.wait());
0443     QCOMPARE(itemsInModel(),
0444              QStringList() << "a.txt"
0445                            << "b.txt"
0446                            << "c.txt");
0447 }
0448 
0449 void KFileItemModelTest::testModelConsistencyWhenInsertingItems()
0450 {
0451     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
0452 
0453     // KFileItemModel prevents that inserting a punch of items sequentially
0454     // results in an itemsInserted()-signal for each item. Instead internally
0455     // a timeout is given that collects such operations and results in only
0456     // one itemsInserted()-signal. However in this test we want to stress
0457     // KFileItemModel to do a lot of insert operation and hence decrease
0458     // the timeout to 1 millisecond.
0459     m_testDir->createFile("1");
0460     m_model->loadDirectory(m_testDir->url());
0461     QVERIFY(itemsInsertedSpy.wait());
0462     QCOMPARE(m_model->count(), 1);
0463 
0464     // Insert 10 items for 20 times. After each insert operation the model consistency
0465     // is checked.
0466     QSet<int> insertedItems;
0467     for (int i = 0; i < 20; ++i) {
0468         itemsInsertedSpy.clear();
0469 
0470         for (int j = 0; j < 10; ++j) {
0471             int itemName = QRandomGenerator::global()->generate();
0472             while (insertedItems.contains(itemName)) {
0473                 itemName = QRandomGenerator::global()->generate();
0474             }
0475             insertedItems.insert(itemName);
0476 
0477             m_testDir->createFile(QString::number(itemName));
0478         }
0479 
0480         m_model->m_dirLister->updateDirectory(m_testDir->url());
0481         if (itemsInsertedSpy.isEmpty()) {
0482             QVERIFY(itemsInsertedSpy.wait());
0483         }
0484 
0485         QVERIFY(m_model->isConsistent());
0486     }
0487 
0488     QCOMPARE(m_model->count(), 201);
0489 }
0490 
0491 void KFileItemModelTest::testItemRangeConsistencyWhenInsertingItems()
0492 {
0493     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
0494 
0495     m_testDir->createFiles({"B", "E", "G"});
0496 
0497     // Due to inserting the 3 items one item-range with index == 0 and
0498     // count == 3 must be given
0499     m_model->loadDirectory(m_testDir->url());
0500     QVERIFY(itemsInsertedSpy.wait());
0501 
0502     QCOMPARE(itemsInsertedSpy.count(), 1);
0503     QList<QVariant> arguments = itemsInsertedSpy.takeFirst();
0504     KItemRangeList itemRangeList = arguments.at(0).value<KItemRangeList>();
0505     QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 3));
0506 
0507     // The indexes of the item-ranges must always be related to the model before
0508     // the items have been inserted. Having:
0509     //   0 1 2
0510     //   B E G
0511     // and inserting A, C, D, F the resulting model will be:
0512     //   0 1 2 3 4 5 6
0513     //   A B C D E F G
0514     // and the item-ranges must be:
0515     //   index: 0, count: 1 for A
0516     //   index: 1, count: 2 for B, C
0517     //   index: 2, count: 1 for G
0518 
0519     m_testDir->createFiles({"A", "C", "D", "F"});
0520 
0521     m_model->m_dirLister->updateDirectory(m_testDir->url());
0522     QVERIFY(itemsInsertedSpy.wait());
0523 
0524     QCOMPARE(itemsInsertedSpy.count(), 1);
0525     arguments = itemsInsertedSpy.takeFirst();
0526     itemRangeList = arguments.at(0).value<KItemRangeList>();
0527     QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 1) << KItemRange(1, 2) << KItemRange(2, 1));
0528 }
0529 
0530 void KFileItemModelTest::testExpandItems()
0531 {
0532     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
0533     QVERIFY(itemsInsertedSpy.isValid());
0534     QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved);
0535     QVERIFY(itemsRemovedSpy.isValid());
0536     QSignalSpy loadingCompletedSpy(m_model, &KFileItemModel::directoryLoadingCompleted);
0537     QVERIFY(loadingCompletedSpy.isValid());
0538 
0539     // Test expanding subfolders in a folder with the items "a/", "a/a/", "a/a/1", "a/a-1/", "a/a-1/1".
0540     // Besides testing the basic item expansion functionality, the test makes sure that
0541     // KFileItemModel::expansionLevelsCompare(const KFileItem& a, const KFileItem& b)
0542     // yields the correct result for "a/a/1" and "a/a-1/", which is non-trivial because they share the
0543     // first three characters.
0544     QSet<QByteArray> originalModelRoles = m_model->roles();
0545     QSet<QByteArray> modelRoles = originalModelRoles;
0546     modelRoles << "isExpanded"
0547                << "isExpandable"
0548                << "expandedParentsCount";
0549     m_model->setRoles(modelRoles);
0550 
0551     m_testDir->createFiles({"a/a/1", "a/a-1/1"});
0552 
0553     // Store the URLs of all folders in a set.
0554     QSet<QUrl> allFolders;
0555     allFolders << QUrl::fromLocalFile(m_testDir->path() + "/a") << QUrl::fromLocalFile(m_testDir->path() + "/a/a")
0556                << QUrl::fromLocalFile(m_testDir->path() + "/a/a-1");
0557 
0558     m_model->loadDirectory(m_testDir->url());
0559     QVERIFY(itemsInsertedSpy.wait());
0560 
0561     // So far, the model contains only "a/"
0562     QCOMPARE(m_model->count(), 1);
0563     QVERIFY(m_model->isExpandable(0));
0564     QVERIFY(!m_model->isExpanded(0));
0565     QVERIFY(m_model->expandedDirectories().empty());
0566 
0567     QCOMPARE(itemsInsertedSpy.count(), 1);
0568     KItemRangeList itemRangeList = itemsInsertedSpy.takeFirst().at(0).value<KItemRangeList>();
0569     QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 1)); // 1 new item "a/" with index 0
0570 
0571     // Expand the folder "a/" -> "a/a/" and "a/a-1/" become visible
0572     m_model->setExpanded(0, true);
0573     QVERIFY(m_model->isExpanded(0));
0574     QVERIFY(itemsInsertedSpy.wait());
0575     QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/a/", "a/a-1/"
0576     QCOMPARE(m_model->expandedDirectories(), QSet<QUrl>() << QUrl::fromLocalFile(m_testDir->path() + "/a"));
0577 
0578     QCOMPARE(itemsInsertedSpy.count(), 1);
0579     itemRangeList = itemsInsertedSpy.takeFirst().at(0).value<KItemRangeList>();
0580     QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 2)); // 2 new items "a/a/" and "a/a-1/" with indices 1 and 2
0581 
0582     QVERIFY(m_model->isExpandable(1));
0583     QVERIFY(!m_model->isExpanded(1));
0584     QVERIFY(m_model->isExpandable(2));
0585     QVERIFY(!m_model->isExpanded(2));
0586 
0587     // Expand the folder "a/a/" -> "a/a/1" becomes visible
0588     m_model->setExpanded(1, true);
0589     QVERIFY(m_model->isExpanded(1));
0590     QVERIFY(itemsInsertedSpy.wait());
0591     QCOMPARE(m_model->count(), 4); // 4 items: "a/", "a/a/", "a/a/1", "a/a-1/"
0592     QCOMPARE(m_model->expandedDirectories(), QSet<QUrl>() << QUrl::fromLocalFile(m_testDir->path() + "/a") << QUrl::fromLocalFile(m_testDir->path() + "/a/a"));
0593 
0594     QCOMPARE(itemsInsertedSpy.count(), 1);
0595     itemRangeList = itemsInsertedSpy.takeFirst().at(0).value<KItemRangeList>();
0596     QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(2, 1)); // 1 new item "a/a/1" with index 2
0597 
0598     QVERIFY(!m_model->isExpandable(2));
0599     QVERIFY(!m_model->isExpanded(2));
0600 
0601     // Expand the folder "a/a-1/" -> "a/a-1/1" becomes visible
0602     m_model->setExpanded(3, true);
0603     QVERIFY(m_model->isExpanded(3));
0604     QVERIFY(itemsInsertedSpy.wait());
0605     QCOMPARE(m_model->count(), 5); // 5 items: "a/", "a/a/", "a/a/1", "a/a-1/", "a/a-1/1"
0606     QCOMPARE(m_model->expandedDirectories(), allFolders);
0607 
0608     QCOMPARE(itemsInsertedSpy.count(), 1);
0609     itemRangeList = itemsInsertedSpy.takeFirst().at(0).value<KItemRangeList>();
0610     QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(4, 1)); // 1 new item "a/a-1/1" with index 4
0611 
0612     QVERIFY(!m_model->isExpandable(4));
0613     QVERIFY(!m_model->isExpanded(4));
0614 
0615     // Collapse the top-level folder -> all other items should disappear
0616     m_model->setExpanded(0, false);
0617     QVERIFY(!m_model->isExpanded(0));
0618     QCOMPARE(m_model->count(), 1);
0619     QVERIFY(!m_model->expandedDirectories().contains(QUrl::fromLocalFile(m_testDir->path() + "/a"))); // TODO: Make sure that child URLs are also removed
0620 
0621     QCOMPARE(itemsRemovedSpy.count(), 1);
0622     itemRangeList = itemsRemovedSpy.takeFirst().at(0).value<KItemRangeList>();
0623     QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 4)); // 4 items removed
0624     QVERIFY(m_model->isConsistent());
0625 
0626     // Clear the model, reload the folder and try to restore the expanded folders.
0627     m_model->clear();
0628     QCOMPARE(m_model->count(), 0);
0629     QVERIFY(m_model->expandedDirectories().empty());
0630 
0631     m_model->loadDirectory(m_testDir->url());
0632     m_model->restoreExpandedDirectories(allFolders);
0633     QVERIFY(loadingCompletedSpy.wait());
0634     QCOMPARE(m_model->count(), 5); // 5 items: "a/", "a/a/", "a/a/1", "a/a-1/", "a/a-1/1"
0635     QVERIFY(m_model->isExpanded(0));
0636     QVERIFY(m_model->isExpanded(1));
0637     QVERIFY(!m_model->isExpanded(2));
0638     QVERIFY(m_model->isExpanded(3));
0639     QVERIFY(!m_model->isExpanded(4));
0640     QCOMPARE(m_model->expandedDirectories(), allFolders);
0641     QVERIFY(m_model->isConsistent());
0642 
0643     // Move to a sub folder, then call restoreExpandedFolders() *before* going back.
0644     // This is how DolphinView restores the expanded folders when navigating in history.
0645     m_model->loadDirectory(QUrl::fromLocalFile(m_testDir->path() + "/a/a/"));
0646     QVERIFY(loadingCompletedSpy.wait());
0647     QCOMPARE(m_model->count(), 1); // 1 item: "1"
0648     m_model->restoreExpandedDirectories(allFolders);
0649     m_model->loadDirectory(m_testDir->url());
0650     QVERIFY(loadingCompletedSpy.wait());
0651     QCOMPARE(m_model->count(), 5); // 5 items: "a/", "a/a/", "a/a/1", "a/a-1/", "a/a-1/1"
0652     QCOMPARE(m_model->expandedDirectories(), allFolders);
0653 
0654     // Remove all expanded items by changing the roles
0655     itemsRemovedSpy.clear();
0656     m_model->setRoles(originalModelRoles);
0657     QVERIFY(!m_model->isExpanded(0));
0658     QCOMPARE(m_model->count(), 1);
0659     QVERIFY(!m_model->expandedDirectories().contains(QUrl::fromLocalFile(m_testDir->path() + "/a")));
0660 
0661     QCOMPARE(itemsRemovedSpy.count(), 1);
0662     itemRangeList = itemsRemovedSpy.takeFirst().at(0).value<KItemRangeList>();
0663     QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 4)); // 4 items removed
0664     QVERIFY(m_model->isConsistent());
0665 }
0666 
0667 void KFileItemModelTest::testExpandParentItems()
0668 {
0669     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
0670     QSignalSpy loadingCompletedSpy(m_model, &KFileItemModel::directoryLoadingCompleted);
0671     QVERIFY(loadingCompletedSpy.isValid());
0672 
0673     // Create a tree structure of folders:
0674     // a 1/
0675     // a 1/b1/
0676     // a 1/b1/c1/
0677     // a2/
0678     // a2/b2/
0679     // a2/b2/c2/
0680     // a2/b2/c2/d2/
0681     QSet<QByteArray> modelRoles = m_model->roles();
0682     modelRoles << "isExpanded"
0683                << "isExpandable"
0684                << "expandedParentsCount";
0685     m_model->setRoles(modelRoles);
0686 
0687     m_testDir->createFiles({"a 1/b1/c1/file.txt", "a2/b2/c2/d2/file.txt"});
0688 
0689     m_model->loadDirectory(m_testDir->url());
0690     QVERIFY(itemsInsertedSpy.wait());
0691 
0692     // So far, the model contains only "a 1/" and "a2/".
0693     QCOMPARE(m_model->count(), 2);
0694     QVERIFY(m_model->expandedDirectories().empty());
0695 
0696     // Expand the parents of "a2/b2/c2".
0697     m_model->expandParentDirectories(QUrl::fromLocalFile(m_testDir->path() + "a2/b2/c2"));
0698     QVERIFY(loadingCompletedSpy.wait());
0699 
0700     // The model should now contain "a 1/", "a2/", "a2/b2/", and "a2/b2/c2/".
0701     // It's important that only the parents of "a1/b1/c1" are expanded.
0702     QCOMPARE(m_model->count(), 4);
0703     QVERIFY(!m_model->isExpanded(0));
0704     QVERIFY(m_model->isExpanded(1));
0705     QVERIFY(m_model->isExpanded(2));
0706     QVERIFY(!m_model->isExpanded(3));
0707 
0708     // Expand the parents of "a 1/b1".
0709     m_model->expandParentDirectories(QUrl::fromLocalFile(m_testDir->path() + "a 1/b1"));
0710     QVERIFY(loadingCompletedSpy.wait());
0711 
0712     // The model should now contain "a 1/", "a 1/b1/", "a2/", "a2/b2", and "a2/b2/c2/".
0713     // It's important that only the parents of "a 1/b1/" and "a2/b2/c2/" are expanded.
0714     QCOMPARE(m_model->count(), 5);
0715     QVERIFY(m_model->isExpanded(0));
0716     QVERIFY(!m_model->isExpanded(1));
0717     QVERIFY(m_model->isExpanded(2));
0718     QVERIFY(m_model->isExpanded(3));
0719     QVERIFY(!m_model->isExpanded(4));
0720     QVERIFY(m_model->isConsistent());
0721 
0722     // Expand "a 1/b1/".
0723     m_model->setExpanded(1, true);
0724     QVERIFY(loadingCompletedSpy.wait());
0725     QCOMPARE(m_model->count(), 6);
0726     QVERIFY(m_model->isExpanded(0));
0727     QVERIFY(m_model->isExpanded(1));
0728     QVERIFY(!m_model->isExpanded(2));
0729     QVERIFY(m_model->isExpanded(3));
0730     QVERIFY(m_model->isExpanded(4));
0731     QVERIFY(!m_model->isExpanded(5));
0732     QVERIFY(m_model->isConsistent());
0733 
0734     // Collapse "a 1/b1/" again, and verify that the previous state is restored.
0735     m_model->setExpanded(1, false);
0736     QCOMPARE(m_model->count(), 5);
0737     QVERIFY(m_model->isExpanded(0));
0738     QVERIFY(!m_model->isExpanded(1));
0739     QVERIFY(m_model->isExpanded(2));
0740     QVERIFY(m_model->isExpanded(3));
0741     QVERIFY(!m_model->isExpanded(4));
0742     QVERIFY(m_model->isConsistent());
0743 }
0744 
0745 /**
0746  * Renaming an expanded folder by prepending its name with a dot makes it
0747  * hidden. Verify that this does not cause an inconsistent model state and
0748  * a crash later on, see https://bugs.kde.org/show_bug.cgi?id=311947
0749  */
0750 void KFileItemModelTest::testMakeExpandedItemHidden()
0751 {
0752     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
0753     QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved);
0754 
0755     QSet<QByteArray> modelRoles = m_model->roles();
0756     modelRoles << "isExpanded"
0757                << "isExpandable"
0758                << "expandedParentsCount";
0759     m_model->setRoles(modelRoles);
0760 
0761     m_testDir->createFiles({"1a/2a/3a", "1a/2a/3b", "1a/2b", "1b"});
0762 
0763     m_model->loadDirectory(m_testDir->url());
0764     QVERIFY(itemsInsertedSpy.wait());
0765 
0766     // So far, the model contains only "1a/" and "1b".
0767     QCOMPARE(m_model->count(), 2);
0768     m_model->setExpanded(0, true);
0769     QVERIFY(itemsInsertedSpy.wait());
0770 
0771     // Now "1a/2a" and "1a/2b" have appeared.
0772     QCOMPARE(m_model->count(), 4);
0773     m_model->setExpanded(1, true);
0774     QVERIFY(itemsInsertedSpy.wait());
0775     QCOMPARE(m_model->count(), 6);
0776 
0777     // Rename "1a/2" and make it hidden.
0778     const QUrl oldUrl = QUrl::fromLocalFile(m_model->fileItem(0).url().path() + "/2a");
0779     const QUrl newUrl = QUrl::fromLocalFile(m_model->fileItem(0).url().path() + "/.2a");
0780 
0781     KIO::SimpleJob *job = KIO::rename(oldUrl, newUrl, KIO::HideProgressInfo);
0782     bool ok = job->exec();
0783     QVERIFY(ok);
0784     QVERIFY(itemsRemovedSpy.wait());
0785 
0786     // "1a/2" and its subfolders have disappeared now.
0787     QVERIFY(m_model->isConsistent());
0788     QCOMPARE(m_model->count(), 3);
0789 
0790     m_model->setExpanded(0, false);
0791     QCOMPARE(m_model->count(), 2);
0792 }
0793 
0794 void KFileItemModelTest::testRemoveFilteredExpandedItems()
0795 {
0796     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
0797 
0798     QSet<QByteArray> originalModelRoles = m_model->roles();
0799     QSet<QByteArray> modelRoles = originalModelRoles;
0800     modelRoles << "isExpanded"
0801                << "isExpandable"
0802                << "expandedParentsCount";
0803     m_model->setRoles(modelRoles);
0804 
0805     m_testDir->createFiles({"folder/child", "file"});
0806 
0807     m_model->loadDirectory(m_testDir->url());
0808     QVERIFY(itemsInsertedSpy.wait());
0809 
0810     // So far, the model contains only "folder/" and "file".
0811     QCOMPARE(m_model->count(), 2);
0812     QVERIFY(m_model->isExpandable(0));
0813     QVERIFY(!m_model->isExpandable(1));
0814     QVERIFY(!m_model->isExpanded(0));
0815     QVERIFY(!m_model->isExpanded(1));
0816     QCOMPARE(itemsInModel(),
0817              QStringList() << "folder"
0818                            << "file");
0819 
0820     // Expand "folder" -> "folder/child" becomes visible.
0821     m_model->setExpanded(0, true);
0822     QVERIFY(m_model->isExpanded(0));
0823     QVERIFY(itemsInsertedSpy.wait());
0824     QCOMPARE(itemsInModel(),
0825              QStringList() << "folder"
0826                            << "child"
0827                            << "file");
0828 
0829     // Add a name filter.
0830     m_model->setNameFilter("f");
0831     QCOMPARE(itemsInModel(),
0832              QStringList() << "folder"
0833                            << "file");
0834 
0835     m_model->setNameFilter("fo");
0836     QCOMPARE(itemsInModel(), QStringList() << "folder");
0837 
0838     // Remove all expanded items by changing the roles
0839     m_model->setRoles(originalModelRoles);
0840     QVERIFY(!m_model->isExpanded(0));
0841     QCOMPARE(itemsInModel(), QStringList() << "folder");
0842 
0843     // Remove the name filter and verify that "folder/child" does not reappear.
0844     m_model->setNameFilter(QString());
0845     QCOMPARE(itemsInModel(),
0846              QStringList() << "folder"
0847                            << "file");
0848 }
0849 
0850 void KFileItemModelTest::testSizeSortingAfterRefresh()
0851 {
0852     // testDir structure is as follows
0853     // ./
0854     // ├─ a
0855     // ├─ b
0856     // ├─ c/
0857     // │  ├─ c-2/
0858     // │  │  ├─ c-3
0859     // │  ├─ c-1
0860     // ├─ d
0861     // ├─ e
0862 
0863     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
0864     QSignalSpy itemsMovedSpy(m_model, &KFileItemModel::itemsMoved);
0865     QVERIFY(itemsMovedSpy.isValid());
0866 
0867     // Create some files with different sizes and modification times to check the different sorting options
0868     QDateTime now = QDateTime::currentDateTime();
0869 
0870     QSet<QByteArray> roles;
0871     roles.insert("text");
0872     roles.insert("isExpanded");
0873     roles.insert("isExpandable");
0874     roles.insert("expandedParentsCount");
0875     m_model->setRoles(roles);
0876 
0877     m_testDir->createDir("c/c-2");
0878     m_testDir->createFile("c/c-2/c-3");
0879     m_testDir->createFile("c/c-1");
0880 
0881     m_testDir->createFile("a", "A file", now.addDays(-3));
0882     m_testDir->createFile("b", "A larger file", now.addDays(0));
0883     m_testDir->createDir("c", now.addDays(-2));
0884     m_testDir->createFile("d", "The largest file in this directory", now.addDays(-1));
0885     m_testDir->createFile("e", "An even larger file", now.addDays(-4));
0886 
0887     m_model->loadDirectory(m_testDir->url());
0888     QVERIFY(itemsInsertedSpy.wait());
0889 
0890     int index = m_model->index(QUrl(m_testDir->url().url() + "/c"));
0891     m_model->setExpanded(index, true);
0892     QVERIFY(itemsInsertedSpy.wait());
0893 
0894     index = m_model->index(QUrl(m_testDir->url().url() + "/c/c-2"));
0895     m_model->setExpanded(index, true);
0896     QVERIFY(itemsInsertedSpy.wait());
0897 
0898     // Default: Sort by Name, ascending
0899     QCOMPARE(m_model->sortRole(), QByteArray("text"));
0900     QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder);
0901     QCOMPARE(itemsInModel(),
0902              QStringList() << "c"
0903                            << "c-2"
0904                            << "c-3"
0905                            << "c-1"
0906                            << "a"
0907                            << "b"
0908                            << "d"
0909                            << "e");
0910 
0911     // Sort by Size, ascending, before refresh
0912     m_model->setSortRole("size");
0913     QCOMPARE(m_model->sortRole(), QByteArray("size"));
0914     QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder);
0915     QCOMPARE(itemsInModel(),
0916              QStringList() << "c"
0917                            << "c-2"
0918                            << "c-3"
0919                            << "c-1"
0920                            << "a"
0921                            << "b"
0922                            << "e"
0923                            << "d");
0924 
0925     // Refresh directory
0926     m_model->refreshDirectory(m_model->directory());
0927     QVERIFY(itemsInsertedSpy.wait());
0928 
0929     // Expand folders again
0930     index = m_model->index(QUrl(m_testDir->url().url() + "/c"));
0931     m_model->setExpanded(index, true);
0932     QVERIFY(itemsInsertedSpy.wait());
0933 
0934     index = m_model->index(QUrl(m_testDir->url().url() + "/c/c-2"));
0935     m_model->setExpanded(index, true);
0936     QVERIFY(itemsInsertedSpy.wait());
0937 
0938     // Sort by Size, ascending, after refresh
0939     QCOMPARE(m_model->sortRole(), QByteArray("size"));
0940     QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder);
0941     QCOMPARE(itemsInModel(),
0942              QStringList() << "c"
0943                            << "c-2"
0944                            << "c-3"
0945                            << "c-1"
0946                            << "a"
0947                            << "b"
0948                            << "e"
0949                            << "d");
0950 }
0951 
0952 void KFileItemModelTest::testSorting()
0953 {
0954     // testDir structure is as follows
0955     // ./
0956     // ├─ .g/
0957     // ├─ a
0958     // ├─ b
0959     // ├─ c/
0960     // │  ├─ c-2/
0961     // │  │  ├─ c-3
0962     // │  ├─ c-1
0963     // ├─ .f
0964     // ├─ d
0965     // ├─ e
0966 
0967     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
0968     QSignalSpy itemsMovedSpy(m_model, &KFileItemModel::itemsMoved);
0969     QVERIFY(itemsMovedSpy.isValid());
0970 
0971     // Create some files with different sizes and modification times to check the different sorting options
0972     QDateTime now = QDateTime::currentDateTime();
0973 
0974     QSet<QByteArray> roles;
0975     roles.insert("text");
0976     roles.insert("isExpanded");
0977     roles.insert("isExpandable");
0978     roles.insert("expandedParentsCount");
0979     m_model->setRoles(roles);
0980 
0981     m_testDir->createDir("c/c-2");
0982     m_testDir->createFile("c/c-2/c-3");
0983     m_testDir->createFile("c/c-1");
0984 
0985     m_testDir->createFile("a", "A file", now.addDays(-3));
0986     m_testDir->createFile("b", "A larger file", now.addDays(0));
0987     m_testDir->createDir("c", now.addDays(-2));
0988     m_testDir->createFile("d", "The largest file in this directory", now.addDays(-1));
0989     m_testDir->createFile("e", "An even larger file", now.addDays(-4));
0990     m_testDir->createFile(".f");
0991     m_testDir->createDir(".g");
0992 
0993     m_model->loadDirectory(m_testDir->url());
0994     QVERIFY(itemsInsertedSpy.wait());
0995     QCOMPARE(itemsInsertedSpy.count(), 1);
0996     KItemRangeList itemRangeList = itemsInsertedSpy.takeFirst().at(0).value<KItemRangeList>();
0997     QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 5));
0998 
0999     int index = m_model->index(QUrl(m_testDir->url().url() + "/c"));
1000     m_model->setExpanded(index, true);
1001     QVERIFY(itemsInsertedSpy.wait());
1002     QCOMPARE(itemsInsertedSpy.count(), 1);
1003     itemRangeList = itemsInsertedSpy.takeFirst().at(0).value<KItemRangeList>();
1004     QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 2));
1005 
1006     index = m_model->index(QUrl(m_testDir->url().url() + "/c/c-2"));
1007     m_model->setExpanded(index, true);
1008     QVERIFY(itemsInsertedSpy.wait());
1009     QCOMPARE(itemsInsertedSpy.count(), 1);
1010     itemRangeList = itemsInsertedSpy.takeFirst().at(0).value<KItemRangeList>();
1011     QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(2, 1));
1012 
1013     // Default: Sort by Name, ascending
1014     QCOMPARE(m_model->sortRole(), QByteArray("text"));
1015     QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder);
1016     QVERIFY(m_model->sortDirectoriesFirst());
1017     QVERIFY(!m_model->showHiddenFiles());
1018     QCOMPARE(itemsInModel(),
1019              QStringList() << "c"
1020                            << "c-2"
1021                            << "c-3"
1022                            << "c-1"
1023                            << "a"
1024                            << "b"
1025                            << "d"
1026                            << "e");
1027 
1028     // Sort by Name, ascending, 'Sort Folders First' disabled
1029     m_model->setSortDirectoriesFirst(false);
1030     QCOMPARE(m_model->sortRole(), QByteArray("text"));
1031     QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder);
1032     QCOMPARE(itemsInModel(),
1033              QStringList() << "a"
1034                            << "b"
1035                            << "c"
1036                            << "c-1"
1037                            << "c-2"
1038                            << "c-3"
1039                            << "d"
1040                            << "e");
1041     QCOMPARE(itemsMovedSpy.count(), 1);
1042     QCOMPARE(itemsMovedSpy.first().at(0).value<KItemRange>(), KItemRange(0, 6));
1043     QCOMPARE(itemsMovedSpy.takeFirst().at(1).value<QList<int>>(), QList<int>() << 2 << 4 << 5 << 3 << 0 << 1);
1044 
1045     // Sort by Name, descending
1046     m_model->setSortDirectoriesFirst(true);
1047     m_model->setSortOrder(Qt::DescendingOrder);
1048     QCOMPARE(m_model->sortRole(), QByteArray("text"));
1049     QCOMPARE(m_model->sortOrder(), Qt::DescendingOrder);
1050     QCOMPARE(itemsInModel(),
1051              QStringList() << "c"
1052                            << "c-2"
1053                            << "c-3"
1054                            << "c-1"
1055                            << "e"
1056                            << "d"
1057                            << "b"
1058                            << "a");
1059     QCOMPARE(itemsMovedSpy.count(), 2);
1060     QCOMPARE(itemsMovedSpy.first().at(0).value<KItemRange>(), KItemRange(0, 6));
1061     QCOMPARE(itemsMovedSpy.takeFirst().at(1).value<QList<int>>(), QList<int>() << 4 << 5 << 0 << 3 << 1 << 2);
1062     QCOMPARE(itemsMovedSpy.first().at(0).value<KItemRange>(), KItemRange(4, 4));
1063     QCOMPARE(itemsMovedSpy.takeFirst().at(1).value<QList<int>>(), QList<int>() << 7 << 6 << 5 << 4);
1064 
1065     // Sort by Date, descending
1066     m_model->setSortDirectoriesFirst(true);
1067     m_model->setSortRole("modificationtime");
1068     QCOMPARE(m_model->sortRole(), QByteArray("modificationtime"));
1069     QCOMPARE(m_model->sortOrder(), Qt::DescendingOrder);
1070     QCOMPARE(itemsInModel(),
1071              QStringList() << "c"
1072                            << "c-2"
1073                            << "c-3"
1074                            << "c-1"
1075                            << "b"
1076                            << "d"
1077                            << "a"
1078                            << "e");
1079     QCOMPARE(itemsMovedSpy.count(), 1);
1080     QCOMPARE(itemsMovedSpy.first().at(0).value<KItemRange>(), KItemRange(4, 4));
1081     QCOMPARE(itemsMovedSpy.takeFirst().at(1).value<QList<int>>(), QList<int>() << 7 << 5 << 4 << 6);
1082 
1083     // Sort by Date, ascending
1084     m_model->setSortOrder(Qt::AscendingOrder);
1085     QCOMPARE(m_model->sortRole(), QByteArray("modificationtime"));
1086     QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder);
1087     QCOMPARE(itemsInModel(),
1088              QStringList() << "c"
1089                            << "c-2"
1090                            << "c-3"
1091                            << "c-1"
1092                            << "e"
1093                            << "a"
1094                            << "d"
1095                            << "b");
1096     QCOMPARE(itemsMovedSpy.count(), 1);
1097     QCOMPARE(itemsMovedSpy.first().at(0).value<KItemRange>(), KItemRange(4, 4));
1098     QCOMPARE(itemsMovedSpy.takeFirst().at(1).value<QList<int>>(), QList<int>() << 7 << 6 << 5 << 4);
1099 
1100     // Sort by Date, ascending, 'Sort Folders First' disabled
1101     m_model->setSortDirectoriesFirst(false);
1102     QCOMPARE(m_model->sortRole(), QByteArray("modificationtime"));
1103     QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder);
1104     QVERIFY(!m_model->sortDirectoriesFirst());
1105     QCOMPARE(itemsInModel(),
1106              QStringList() << "e"
1107                            << "a"
1108                            << "c"
1109                            << "c-1"
1110                            << "c-2"
1111                            << "c-3"
1112                            << "d"
1113                            << "b");
1114     QCOMPARE(itemsMovedSpy.count(), 1);
1115     QCOMPARE(itemsMovedSpy.first().at(0).value<KItemRange>(), KItemRange(0, 6));
1116     QCOMPARE(itemsMovedSpy.takeFirst().at(1).value<QList<int>>(), QList<int>() << 2 << 4 << 5 << 3 << 0 << 1);
1117 
1118     // Sort by Name, ascending, 'Sort Folders First' disabled
1119     m_model->setSortRole("text");
1120     QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder);
1121     QVERIFY(!m_model->sortDirectoriesFirst());
1122     QCOMPARE(itemsInModel(),
1123              QStringList() << "a"
1124                            << "b"
1125                            << "c"
1126                            << "c-1"
1127                            << "c-2"
1128                            << "c-3"
1129                            << "d"
1130                            << "e");
1131     QCOMPARE(itemsMovedSpy.count(), 1);
1132     QCOMPARE(itemsMovedSpy.first().at(0).value<KItemRange>(), KItemRange(0, 8));
1133     QCOMPARE(itemsMovedSpy.takeFirst().at(1).value<QList<int>>(), QList<int>() << 7 << 0 << 2 << 3 << 4 << 5 << 6 << 1);
1134 
1135     // Sort by Size, ascending, 'Sort Folders First' disabled
1136     m_model->setSortRole("size");
1137     QCOMPARE(m_model->sortRole(), QByteArray("size"));
1138     QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder);
1139     QVERIFY(!m_model->sortDirectoriesFirst());
1140     QCOMPARE(itemsInModel(),
1141              QStringList() << "c"
1142                            << "c-2"
1143                            << "c-3"
1144                            << "c-1"
1145                            << "a"
1146                            << "b"
1147                            << "e"
1148                            << "d");
1149     QCOMPARE(itemsMovedSpy.count(), 1);
1150     QCOMPARE(itemsMovedSpy.first().at(0).value<KItemRange>(), KItemRange(0, 8));
1151     QCOMPARE(itemsMovedSpy.takeFirst().at(1).value<QList<int>>(), QList<int>() << 4 << 5 << 0 << 3 << 1 << 2 << 7 << 6);
1152 
1153     // In 'Sort by Size' mode, folders are always first -> changing 'Sort Folders First' does not resort the model
1154     m_model->setSortDirectoriesFirst(true);
1155     QCOMPARE(m_model->sortRole(), QByteArray("size"));
1156     QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder);
1157     QVERIFY(m_model->sortDirectoriesFirst());
1158     QCOMPARE(itemsInModel(),
1159              QStringList() << "c"
1160                            << "c-2"
1161                            << "c-3"
1162                            << "c-1"
1163                            << "a"
1164                            << "b"
1165                            << "e"
1166                            << "d");
1167     QCOMPARE(itemsMovedSpy.count(), 0);
1168 
1169     // Sort by Size, descending, 'Sort Folders First' enabled
1170     m_model->setSortOrder(Qt::DescendingOrder);
1171     QCOMPARE(m_model->sortRole(), QByteArray("size"));
1172     QCOMPARE(m_model->sortOrder(), Qt::DescendingOrder);
1173     QVERIFY(m_model->sortDirectoriesFirst());
1174     QCOMPARE(itemsInModel(),
1175              QStringList() << "c"
1176                            << "c-2"
1177                            << "c-3"
1178                            << "c-1"
1179                            << "d"
1180                            << "e"
1181                            << "b"
1182                            << "a");
1183     QCOMPARE(itemsMovedSpy.count(), 1);
1184     QCOMPARE(itemsMovedSpy.first().at(0).value<KItemRange>(), KItemRange(4, 4));
1185     QCOMPARE(itemsMovedSpy.takeFirst().at(1).value<QList<int>>(), QList<int>() << 7 << 6 << 5 << 4);
1186 
1187     // 'Show Hidden Files' enabled
1188     m_model->setShowHiddenFiles(true);
1189     QVERIFY(m_model->showHiddenFiles());
1190     QVERIFY(!m_model->sortHiddenLast());
1191     QCOMPARE(itemsInModel(),
1192              QStringList() << "c"
1193                            << "c-2"
1194                            << "c-3"
1195                            << "c-1"
1196                            << ".g"
1197                            << "d"
1198                            << "e"
1199                            << "b"
1200                            << "a"
1201                            << ".f");
1202     QCOMPARE(itemsMovedSpy.count(), 0);
1203     QCOMPARE(itemsInsertedSpy.count(), 1);
1204     QCOMPARE(itemsInsertedSpy.takeFirst().at(0).value<KItemRangeList>(), KItemRangeList() << KItemRange(4, 1) << KItemRange(8, 1));
1205 
1206     // 'Sort Hidden Files Last' enabled
1207     m_model->setSortHiddenLast(true);
1208     QVERIFY(m_model->sortHiddenLast());
1209     QCOMPARE(itemsInModel(),
1210              QStringList() << "c"
1211                            << "c-2"
1212                            << "c-3"
1213                            << "c-1"
1214                            << "d"
1215                            << "e"
1216                            << "b"
1217                            << "a"
1218                            << ".g"
1219                            << ".f");
1220     QCOMPARE(itemsMovedSpy.count(), 1);
1221     QCOMPARE(itemsInsertedSpy.count(), 0);
1222     QCOMPARE(itemsMovedSpy.first().at(0).value<KItemRange>(), KItemRange(4, 5));
1223     QCOMPARE(itemsMovedSpy.takeFirst().at(1).value<QList<int>>(), QList<int>() << 8 << 4 << 5 << 6 << 7);
1224 
1225     // Sort by Name
1226     m_model->setSortRole("text");
1227     QCOMPARE(itemsInModel(),
1228              QStringList() << "c"
1229                            << "c-2"
1230                            << "c-3"
1231                            << "c-1"
1232                            << "e"
1233                            << "d"
1234                            << "b"
1235                            << "a"
1236                            << ".g"
1237                            << ".f");
1238     QCOMPARE(itemsMovedSpy.count(), 1);
1239     QCOMPARE(itemsMovedSpy.first().at(0).value<KItemRange>(), KItemRange(4, 2));
1240     QCOMPARE(itemsMovedSpy.takeFirst().at(1).value<QList<int>>(), QList<int>() << 5 << 4);
1241 
1242     // Sort ascending
1243     m_model->setSortOrder(Qt::AscendingOrder);
1244     QCOMPARE(itemsInModel(),
1245              QStringList() << "c"
1246                            << "c-2"
1247                            << "c-3"
1248                            << "c-1"
1249                            << "a"
1250                            << "b"
1251                            << "d"
1252                            << "e"
1253                            << ".g"
1254                            << ".f");
1255     QCOMPARE(itemsMovedSpy.count(), 1);
1256     QCOMPARE(itemsMovedSpy.first().at(0).value<KItemRange>(), KItemRange(4, 4));
1257     QCOMPARE(itemsMovedSpy.takeFirst().at(1).value<QList<int>>(), QList<int>() << 7 << 6 << 5 << 4);
1258 
1259     // 'Sort Folders First' disabled
1260     m_model->setSortDirectoriesFirst(false);
1261     QVERIFY(!m_model->sortDirectoriesFirst());
1262     QCOMPARE(itemsInModel(),
1263              QStringList() << "a"
1264                            << "b"
1265                            << "c"
1266                            << "c-1"
1267                            << "c-2"
1268                            << "c-3"
1269                            << "d"
1270                            << "e"
1271                            << ".f"
1272                            << ".g");
1273     QCOMPARE(itemsMovedSpy.count(), 1);
1274     QCOMPARE(itemsMovedSpy.first().at(0).value<KItemRange>(), KItemRange(0, 10));
1275     QCOMPARE(itemsMovedSpy.takeFirst().at(1).value<QList<int>>(), QList<int>() << 2 << 4 << 5 << 3 << 0 << 1 << 6 << 7 << 9 << 8);
1276 }
1277 
1278 void KFileItemModelTest::testIndexForKeyboardSearch()
1279 {
1280     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
1281 
1282     m_testDir->createFiles({"a", "aa", "Image.jpg", "Image.png", "Text", "Text1", "Text2", "Text11"});
1283 
1284     m_model->loadDirectory(m_testDir->url());
1285     QVERIFY(itemsInsertedSpy.wait());
1286 
1287     // Search from index 0
1288     QCOMPARE(m_model->indexForKeyboardSearch("a", 0), 0);
1289     QCOMPARE(m_model->indexForKeyboardSearch("aa", 0), 1);
1290     QCOMPARE(m_model->indexForKeyboardSearch("i", 0), 2);
1291     QCOMPARE(m_model->indexForKeyboardSearch("image", 0), 2);
1292     QCOMPARE(m_model->indexForKeyboardSearch("image.jpg", 0), 2);
1293     QCOMPARE(m_model->indexForKeyboardSearch("image.png", 0), 3);
1294     QCOMPARE(m_model->indexForKeyboardSearch("t", 0), 4);
1295     QCOMPARE(m_model->indexForKeyboardSearch("text", 0), 4);
1296     QCOMPARE(m_model->indexForKeyboardSearch("text1", 0), 5);
1297     QCOMPARE(m_model->indexForKeyboardSearch("text2", 0), 6);
1298     QCOMPARE(m_model->indexForKeyboardSearch("text11", 0), 7);
1299 
1300     // Start a search somewhere in the middle
1301     QCOMPARE(m_model->indexForKeyboardSearch("a", 1), 1);
1302     QCOMPARE(m_model->indexForKeyboardSearch("i", 3), 3);
1303     QCOMPARE(m_model->indexForKeyboardSearch("t", 5), 5);
1304     QCOMPARE(m_model->indexForKeyboardSearch("text1", 6), 7);
1305 
1306     // Test searches that go past the last item back to index 0
1307     QCOMPARE(m_model->indexForKeyboardSearch("a", 2), 0);
1308     QCOMPARE(m_model->indexForKeyboardSearch("i", 7), 2);
1309     QCOMPARE(m_model->indexForKeyboardSearch("image.jpg", 3), 2);
1310     QCOMPARE(m_model->indexForKeyboardSearch("text2", 7), 6);
1311 
1312     // Test searches that yield no result
1313     QCOMPARE(m_model->indexForKeyboardSearch("aaa", 0), -1);
1314     QCOMPARE(m_model->indexForKeyboardSearch("b", 0), -1);
1315     QCOMPARE(m_model->indexForKeyboardSearch("image.svg", 0), -1);
1316     QCOMPARE(m_model->indexForKeyboardSearch("text3", 0), -1);
1317     QCOMPARE(m_model->indexForKeyboardSearch("text3", 5), -1);
1318 
1319     // Test upper case searches (note that search is case insensitive)
1320     QCOMPARE(m_model->indexForKeyboardSearch("A", 0), 0);
1321     QCOMPARE(m_model->indexForKeyboardSearch("aA", 0), 1);
1322     QCOMPARE(m_model->indexForKeyboardSearch("TexT", 5), 5);
1323     QCOMPARE(m_model->indexForKeyboardSearch("IMAGE", 4), 2);
1324 
1325     // TODO: Maybe we should also test keyboard searches in directories which are not sorted by Name?
1326 }
1327 
1328 void KFileItemModelTest::testNameFilter()
1329 {
1330     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
1331 
1332     m_testDir->createFiles({"A1", "A2", "Abc", "Bcd", "Cde"});
1333 
1334     m_model->loadDirectory(m_testDir->url());
1335     QVERIFY(itemsInsertedSpy.wait());
1336 
1337     m_model->setNameFilter("A"); // Shows A1, A2 and Abc
1338     QCOMPARE(m_model->count(), 3);
1339 
1340     m_model->setNameFilter("A2"); // Shows only A2
1341     QCOMPARE(m_model->count(), 1);
1342 
1343     m_model->setNameFilter("A2"); // Shows only A1
1344     QCOMPARE(m_model->count(), 1);
1345 
1346     m_model->setNameFilter("Bc"); // Shows "Abc" and "Bcd"
1347     QCOMPARE(m_model->count(), 2);
1348 
1349     m_model->setNameFilter("bC"); // Shows "Abc" and "Bcd"
1350     QCOMPARE(m_model->count(), 2);
1351 
1352     m_model->setNameFilter(QString()); // Shows again all items
1353     QCOMPARE(m_model->count(), 5);
1354 }
1355 
1356 /**
1357  * Verifies that we do not crash when adding a KFileItem with an empty path.
1358  * Before this issue was fixed, KFileItemModel::expandedParentsCountCompare()
1359  * tried to always read the first character of the path, even if the path is empty.
1360  */
1361 void KFileItemModelTest::testEmptyPath()
1362 {
1363     QSet<QByteArray> roles;
1364     roles.insert("text");
1365     roles.insert("isExpanded");
1366     roles.insert("isExpandable");
1367     roles.insert("expandedParentsCount");
1368     m_model->setRoles(roles);
1369 
1370     const QUrl emptyUrl;
1371     QVERIFY(emptyUrl.path().isEmpty());
1372 
1373     const QUrl url("file:///test/");
1374 
1375     KFileItemList items;
1376     items << KFileItem(emptyUrl, QString(), KFileItem::Unknown) << KFileItem(url, QString(), KFileItem::Unknown);
1377     m_model->slotItemsAdded(emptyUrl, items);
1378     m_model->slotCompleted();
1379 }
1380 
1381 /**
1382  * Verifies that the 'isExpanded' state of folders does not change when the
1383  * 'refreshItems' signal is received, see https://bugs.kde.org/show_bug.cgi?id=299675.
1384  */
1385 void KFileItemModelTest::testRefreshExpandedItem()
1386 {
1387     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
1388     QSignalSpy itemsChangedSpy(m_model, &KFileItemModel::itemsChanged);
1389     QVERIFY(itemsChangedSpy.isValid());
1390 
1391     QSet<QByteArray> modelRoles = m_model->roles();
1392     modelRoles << "isExpanded"
1393                << "isExpandable"
1394                << "expandedParentsCount";
1395     m_model->setRoles(modelRoles);
1396 
1397     m_testDir->createFiles({"a/1", "a/2", "3", "4"});
1398 
1399     m_model->loadDirectory(m_testDir->url());
1400     QVERIFY(itemsInsertedSpy.wait());
1401     QCOMPARE(m_model->count(), 3); // "a/", "3", "4"
1402 
1403     m_model->setExpanded(0, true);
1404     QVERIFY(itemsInsertedSpy.wait());
1405     QCOMPARE(m_model->count(), 5); // "a/", "a/1", "a/2", "3", "4"
1406     QVERIFY(m_model->isExpanded(0));
1407 
1408     const KFileItem item = m_model->fileItem(0);
1409     m_model->slotRefreshItems({qMakePair(item, item)});
1410     QVERIFY(!itemsChangedSpy.isEmpty());
1411 
1412     QCOMPARE(m_model->count(), 5); // "a/", "a/1", "a/2", "3", "4"
1413     QVERIFY(m_model->isExpanded(0));
1414 }
1415 
1416 /**
1417  * Verifies that adding an item to an expanded folder that's filtered makes the parental chain visible.
1418  */
1419 void KFileItemModelTest::testAddItemToFilteredExpandedFolder()
1420 {
1421     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
1422     QSignalSpy fileItemsChangedSpy(m_model, &KFileItemModel::fileItemsChanged);
1423 
1424     QSet<QByteArray> modelRoles = m_model->roles();
1425     modelRoles << "isExpanded"
1426                << "isExpandable"
1427                << "expandedParentsCount";
1428     m_model->setRoles(modelRoles);
1429 
1430     m_testDir->createFile("a/b/file");
1431 
1432     m_model->loadDirectory(m_testDir->url());
1433     QVERIFY(itemsInsertedSpy.wait());
1434     QCOMPARE(m_model->count(), 1); // "a
1435 
1436     // Expand "a/".
1437     m_model->setExpanded(0, true);
1438     QVERIFY(itemsInsertedSpy.wait());
1439 
1440     // Expand "a/b/".
1441     m_model->setExpanded(1, true);
1442     QVERIFY(itemsInsertedSpy.wait());
1443 
1444     QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/b/", "a/b/file"
1445 
1446     const QUrl urlB = m_model->fileItem(1).url();
1447 
1448     // Set a filter that matches ".txt" extension
1449     m_model->setNameFilter("*.txt");
1450     QCOMPARE(m_model->count(), 0); // Everything got hidden since we don't have a .txt file yet
1451 
1452     m_model->slotItemsAdded(urlB, KFileItemList() << KFileItem(QUrl("a/b/newItem.txt")));
1453     m_model->slotCompleted();
1454 
1455     // Entire parental chain should now be shown
1456     QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/b/", "a/b/newItem.txt"
1457     QCOMPARE(itemsInModel(),
1458              QStringList() << "a"
1459                            << "b"
1460                            << "newItem.txt");
1461 
1462     // Items should be indented in hierarchy
1463     QCOMPARE(m_model->expandedParentsCount(0), 0);
1464     QCOMPARE(m_model->expandedParentsCount(1), 1);
1465     QCOMPARE(m_model->expandedParentsCount(2), 2);
1466 }
1467 
1468 /**
1469  * Verifies that deleting the last filter-passing child from expanded folders
1470  * makes the parental chain hidden.
1471  */
1472 void KFileItemModelTest::testDeleteItemsWithExpandedFolderWithFilter()
1473 {
1474     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
1475     QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved);
1476 
1477     QSet<QByteArray> modelRoles = m_model->roles();
1478     modelRoles << "isExpanded"
1479                << "isExpandable"
1480                << "expandedParentsCount";
1481     m_model->setRoles(modelRoles);
1482 
1483     m_testDir->createFile("a/b/file");
1484 
1485     m_model->loadDirectory(m_testDir->url());
1486     QVERIFY(itemsInsertedSpy.wait());
1487     QCOMPARE(m_model->count(), 1); // "a
1488 
1489     // Expand "a/".
1490     m_model->setExpanded(0, true);
1491     QVERIFY(itemsInsertedSpy.wait());
1492 
1493     // Expand "a/b/".
1494     m_model->setExpanded(1, true);
1495     QVERIFY(itemsInsertedSpy.wait());
1496 
1497     QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/b/", "a/b/file"
1498 
1499     // Set a filter that matches "file" extension
1500     m_model->setNameFilter("file");
1501     QCOMPARE(m_model->count(), 3); // Everything is still shown
1502 
1503     // Delete "file"
1504     QCOMPARE(itemsRemovedSpy.count(), 0);
1505     m_model->slotItemsDeleted(KFileItemList() << m_model->fileItem(2));
1506     QCOMPARE(itemsRemovedSpy.count(), 1);
1507 
1508     // Entire parental chain should now be filtered
1509     QCOMPARE(m_model->count(), 0);
1510     QCOMPARE(m_model->m_filteredItems.size(), 2);
1511 }
1512 
1513 /**
1514  * Verifies that the fileItemsChanged signal is raised with the correct index after renaming files with filter set.
1515  * The rename operation will cause one item to be filtered out and another item to be reordered.
1516  */
1517 void KFileItemModelTest::testRefreshItemsWithFilter()
1518 {
1519     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
1520     QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved);
1521     QSignalSpy itemsChangedSpy(m_model, &KFileItemModel::itemsChanged);
1522     QSignalSpy itemsMovedSpy(m_model, &KFileItemModel::itemsMoved);
1523 
1524     // Creates three .txt files
1525     m_testDir->createFiles({"b.txt", "c.txt", "d.txt"});
1526 
1527     m_model->loadDirectory(m_testDir->url());
1528     QVERIFY(itemsInsertedSpy.wait());
1529 
1530     QCOMPARE(m_model->count(), 3); // "b.txt", "c.txt", "d.txt"
1531 
1532     // Set a filter that matches ".txt" extension
1533     m_model->setNameFilter("*.txt");
1534     QCOMPARE(m_model->count(), 3); // Still all items are shown
1535     QCOMPARE(itemsInModel(),
1536              QStringList() << "b.txt"
1537                            << "c.txt"
1538                            << "d.txt");
1539 
1540     // Objects used to rename
1541     const KFileItem fileItemC_txt = m_model->fileItem(1);
1542     KFileItem fileItemC_cfg = fileItemC_txt;
1543     fileItemC_cfg.setUrl(QUrl("c.cfg"));
1544 
1545     const KFileItem fileItemD_txt = m_model->fileItem(2);
1546     KFileItem fileItemA_txt = fileItemD_txt;
1547     fileItemA_txt.setUrl(QUrl("a.txt"));
1548 
1549     // Rename "c.txt" to "c.cfg"; and rename "d.txt" to "a.txt"
1550     QCOMPARE(itemsRemovedSpy.count(), 0);
1551     QCOMPARE(itemsChangedSpy.count(), 0);
1552     m_model->slotRefreshItems({qMakePair(fileItemC_txt, fileItemC_cfg), qMakePair(fileItemD_txt, fileItemA_txt)});
1553     QCOMPARE(itemsRemovedSpy.count(), 1);
1554     QCOMPARE(itemsChangedSpy.count(), 1);
1555     QCOMPARE(m_model->count(), 2); // Only "a.txt" and "b.txt". "c.cfg" got filtered out
1556 
1557     QList<QVariant> arguments = itemsChangedSpy.takeLast();
1558     KItemRangeList itemRangeList = arguments.at(0).value<KItemRangeList>();
1559 
1560     // We started with the order "b.txt", "c.txt", "d.txt"
1561     // "d.txt" started with index "2"
1562     // "c.txt" got renamed and got filtered out
1563     // "d.txt" index shifted from index "2" to "1"
1564     // So we expect index "1" in this argument, meaning "d.txt" was renamed
1565     QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 1));
1566 
1567     // Re-sorting is done asynchronously:
1568     QCOMPARE(itemsInModel(),
1569              QStringList() << "b.txt"
1570                            << "a.txt"); // Files should still be in the incorrect order
1571     QVERIFY(itemsMovedSpy.wait());
1572     QCOMPARE(itemsInModel(),
1573              QStringList() << "a.txt"
1574                            << "b.txt"); // Files were re-sorted and should now be in the correct order
1575 }
1576 
1577 /**
1578  * Verifies that parental chains are hidden and shown as needed while their children get filtered/unfiltered due to renaming.
1579  * Also verifies that the "isExpanded" and "expandedParentsCount" values are kept for expanded folders that get refreshed.
1580  */
1581 void KFileItemModelTest::testRefreshExpandedFolderWithFilter()
1582 {
1583     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
1584     QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved);
1585 
1586     QSet<QByteArray> modelRoles = m_model->roles();
1587     modelRoles << "isExpanded"
1588                << "isExpandable"
1589                << "expandedParentsCount";
1590     m_model->setRoles(modelRoles);
1591 
1592     m_testDir->createFile("a/b/someFolder/someFile");
1593 
1594     m_model->loadDirectory(m_testDir->url());
1595     QVERIFY(itemsInsertedSpy.wait());
1596 
1597     QCOMPARE(m_model->count(), 1); // Only "a/"
1598 
1599     // Expand "a/".
1600     m_model->setExpanded(0, true);
1601     QVERIFY(itemsInsertedSpy.wait());
1602 
1603     // Expand "a/b/".
1604     m_model->setExpanded(1, true);
1605     QVERIFY(itemsInsertedSpy.wait());
1606 
1607     // Expand "a/b/someFolder/".
1608     m_model->setExpanded(2, true);
1609     QVERIFY(itemsInsertedSpy.wait());
1610     QCOMPARE(m_model->count(), 4); // 4 items: "a/", "a/b/", "a/b/someFolder", "a/b/someFolder/someFile"
1611 
1612     // Set a filter that matches the expanded folder "someFolder"
1613     m_model->setNameFilter("someFolder");
1614     QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/b/", "a/b/someFolder"
1615 
1616     // Objects used to rename
1617     const KFileItem fileItemA = m_model->fileItem(0);
1618     KFileItem fileItemARenamed = fileItemA;
1619     fileItemARenamed.setUrl(QUrl("a_renamed"));
1620 
1621     const KFileItem fileItemSomeFolder = m_model->fileItem(2);
1622     KFileItem fileItemRenamedFolder = fileItemSomeFolder;
1623     fileItemRenamedFolder.setUrl(QUrl("/a_renamed/b/renamedFolder"));
1624 
1625     // Rename "a" to "a_renamed"
1626     // This way we test if the algorithm is sane as to NOT hide "a_renamed" since it will have visible children
1627     m_model->slotRefreshItems({qMakePair(fileItemA, fileItemARenamed)});
1628     QCOMPARE(m_model->count(), 3); // Entire parental chain must still be shown
1629     QCOMPARE(itemsInModel(),
1630              QStringList() << "a_renamed"
1631                            << "b"
1632                            << "someFolder");
1633 
1634     // Rename "a_renamed" back to "a"; and "someFolder" to "renamedFolder"
1635     m_model->slotRefreshItems({qMakePair(fileItemARenamed, fileItemA), qMakePair(fileItemSomeFolder, fileItemRenamedFolder)});
1636     QCOMPARE(m_model->count(), 0); // Entire parental chain became hidden
1637 
1638     // Rename "renamedFolder" back to "someFolder". Filter is passing again
1639     m_model->slotRefreshItems({qMakePair(fileItemRenamedFolder, fileItemSomeFolder)});
1640     QCOMPARE(m_model->count(), 3); // Entire parental chain is shown again
1641     QCOMPARE(itemsInModel(),
1642              QStringList() << "a"
1643                            << "b"
1644                            << "someFolder");
1645 
1646     // slotRefreshItems() should preserve "isExpanded" and "expandedParentsCount" values explicitly in this case
1647     QCOMPARE(m_model->m_itemData.at(2)->values.value("isExpanded").toBool(), true);
1648     QCOMPARE(m_model->m_itemData.at(2)->values.value("expandedParentsCount"), 2);
1649 }
1650 
1651 /**
1652  * Verify that removing hidden files and folders from the model does not
1653  * result in a crash, see https://bugs.kde.org/show_bug.cgi?id=314046
1654  */
1655 void KFileItemModelTest::testRemoveHiddenItems()
1656 {
1657     m_testDir->createDir(".a");
1658     m_testDir->createDir(".b");
1659     m_testDir->createDir("c");
1660     m_testDir->createDir("d");
1661     m_testDir->createFiles({".f", ".g", "h", "i"});
1662 
1663     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
1664     QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved);
1665 
1666     m_model->setShowHiddenFiles(true);
1667     m_model->loadDirectory(m_testDir->url());
1668     QVERIFY(itemsInsertedSpy.wait());
1669     QCOMPARE(itemsInModel(),
1670              QStringList() << ".a"
1671                            << ".b"
1672                            << "c"
1673                            << "d"
1674                            << ".f"
1675                            << ".g"
1676                            << "h"
1677                            << "i");
1678     QCOMPARE(itemsInsertedSpy.count(), 1);
1679     QCOMPARE(itemsRemovedSpy.count(), 0);
1680     KItemRangeList itemRangeList = itemsInsertedSpy.takeFirst().at(0).value<KItemRangeList>();
1681     QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 8));
1682 
1683     m_model->setShowHiddenFiles(false);
1684     QCOMPARE(itemsInModel(),
1685              QStringList() << "c"
1686                            << "d"
1687                            << "h"
1688                            << "i");
1689     QCOMPARE(itemsInsertedSpy.count(), 0);
1690     QCOMPARE(itemsRemovedSpy.count(), 1);
1691     itemRangeList = itemsRemovedSpy.takeFirst().at(0).value<KItemRangeList>();
1692     QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 2) << KItemRange(4, 2));
1693 
1694     m_model->setShowHiddenFiles(true);
1695     QCOMPARE(itemsInModel(),
1696              QStringList() << ".a"
1697                            << ".b"
1698                            << "c"
1699                            << "d"
1700                            << ".f"
1701                            << ".g"
1702                            << "h"
1703                            << "i");
1704     QCOMPARE(itemsInsertedSpy.count(), 1);
1705     QCOMPARE(itemsRemovedSpy.count(), 0);
1706     itemRangeList = itemsInsertedSpy.takeFirst().at(0).value<KItemRangeList>();
1707     QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 2) << KItemRange(2, 2));
1708 
1709     m_model->clear();
1710     QCOMPARE(itemsInModel(), QStringList());
1711     QCOMPARE(itemsInsertedSpy.count(), 0);
1712     QCOMPARE(itemsRemovedSpy.count(), 1);
1713     itemRangeList = itemsRemovedSpy.takeFirst().at(0).value<KItemRangeList>();
1714     QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 8));
1715 
1716     // Hiding hidden files makes the dir lister emit its itemsDeleted signal.
1717     // Verify that this does not make the model crash.
1718     m_model->setShowHiddenFiles(false);
1719 }
1720 
1721 /**
1722  * Verify that filtered items are removed when their parent is collapsed.
1723  */
1724 void KFileItemModelTest::collapseParentOfHiddenItems()
1725 {
1726     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
1727     QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved);
1728 
1729     QSet<QByteArray> modelRoles = m_model->roles();
1730     modelRoles << "isExpanded"
1731                << "isExpandable"
1732                << "expandedParentsCount";
1733     m_model->setRoles(modelRoles);
1734 
1735     m_testDir->createFiles({"a/1", "a/b/1", "a/b/c/1", "a/b/c/d/1"});
1736 
1737     m_model->loadDirectory(m_testDir->url());
1738     QVERIFY(itemsInsertedSpy.wait());
1739     QCOMPARE(m_model->count(), 1); // Only "a/"
1740 
1741     // Expand "a/".
1742     m_model->setExpanded(0, true);
1743     QVERIFY(itemsInsertedSpy.wait());
1744     QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/b/", "a/1"
1745 
1746     // Expand "a/b/".
1747     m_model->setExpanded(1, true);
1748     QVERIFY(itemsInsertedSpy.wait());
1749     QCOMPARE(m_model->count(), 5); // 5 items: "a/", "a/b/", "a/b/c", "a/b/1", "a/1"
1750 
1751     // Expand "a/b/c/".
1752     m_model->setExpanded(2, true);
1753     QVERIFY(itemsInsertedSpy.wait());
1754     QCOMPARE(m_model->count(), 7); // 7 items: "a/", "a/b/", "a/b/c", "a/b/c/d/", "a/b/c/1", "a/b/1", "a/1"
1755 
1756     // Set a name filter that matches nothing -> nothing should remain.
1757     m_model->setNameFilter("xyz");
1758     QCOMPARE(itemsRemovedSpy.count(), 1);
1759     QCOMPARE(m_model->count(), 0); // Everything is hidden
1760     QCOMPARE(itemsInModel(), QStringList());
1761 
1762     // Filter by the file names. Folder "d" will be hidden since it was collapsed
1763     m_model->setNameFilter("1");
1764     QCOMPARE(itemsRemovedSpy.count(), 1); // nothing was removed, itemsRemovedSpy count will remain the same:
1765     QCOMPARE(m_model->count(), 6); // 6 items: "a/", "a/b/", "a/b/c", "a/b/c/1", "a/b/1", "a/1"
1766 
1767     // Collapse the folder "a/".
1768     m_model->setExpanded(0, false);
1769     QCOMPARE(itemsRemovedSpy.count(), 2);
1770     QCOMPARE(m_model->count(), 1);
1771     QCOMPARE(itemsInModel(), QStringList() << "a");
1772 
1773     // Remove the filter -> "a" should still appear (and we should not get a crash).
1774     m_model->setNameFilter(QString());
1775     QCOMPARE(itemsRemovedSpy.count(), 2); // nothing was removed, itemsRemovedSpy count will remain the same:
1776     QCOMPARE(m_model->count(), 1);
1777     QCOMPARE(itemsInModel(), QStringList() << "a");
1778 }
1779 
1780 /**
1781  * Verify that filtered items are removed when their parent is deleted.
1782  */
1783 void KFileItemModelTest::removeParentOfHiddenItems()
1784 {
1785     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
1786     QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved);
1787 
1788     QSet<QByteArray> modelRoles = m_model->roles();
1789     modelRoles << "isExpanded"
1790                << "isExpandable"
1791                << "expandedParentsCount";
1792     m_model->setRoles(modelRoles);
1793 
1794     m_testDir->createFiles({"a/1", "a/b/1", "a/b/c/1", "a/b/c/d/1"});
1795 
1796     m_model->loadDirectory(m_testDir->url());
1797     QVERIFY(itemsInsertedSpy.wait());
1798     QCOMPARE(m_model->count(), 1); // Only "a/"
1799 
1800     // Expand "a/".
1801     m_model->setExpanded(0, true);
1802     QVERIFY(itemsInsertedSpy.wait());
1803     QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/b/", "a/1"
1804 
1805     // Expand "a/b/".
1806     m_model->setExpanded(1, true);
1807     QVERIFY(itemsInsertedSpy.wait());
1808     QCOMPARE(m_model->count(), 5); // 5 items: "a/", "a/b/", "a/b/c", "a/b/1", "a/1"
1809 
1810     // Expand "a/b/c/".
1811     m_model->setExpanded(2, true);
1812     QVERIFY(itemsInsertedSpy.wait());
1813     QCOMPARE(m_model->count(), 7); // 7 items: "a/", "a/b/", "a/b/c", "a/b/c/d/", "a/b/c/1", "a/b/1", "a/1"
1814 
1815     // Set a name filter that matches nothing -> nothing should remain.
1816     m_model->setNameFilter("xyz");
1817     QCOMPARE(itemsRemovedSpy.count(), 1);
1818     QCOMPARE(m_model->count(), 0);
1819     QCOMPARE(itemsInModel(), QStringList());
1820 
1821     // Filter by "c". Folder "b" will also be shown because it is its parent.
1822     m_model->setNameFilter("c");
1823     QCOMPARE(itemsRemovedSpy.count(), 1); // nothing was removed, itemsRemovedSpy count will remain the same:
1824     QCOMPARE(m_model->count(), 3);
1825     QCOMPARE(itemsInModel(),
1826              QStringList() << "a"
1827                            << "b"
1828                            << "c");
1829 
1830     // Simulate the deletion of the directory "a/b/".
1831     m_model->slotItemsDeleted(KFileItemList() << m_model->fileItem(1));
1832     QCOMPARE(itemsRemovedSpy.count(), 2);
1833     QCOMPARE(m_model->count(), 0); // "a" will be filtered out since it doesn't pass the filter and doesn't have visible children
1834 
1835     // Remove the filter -> only the file "a/1" should appear.
1836     m_model->setNameFilter(QString());
1837     QCOMPARE(m_model->count(), 2);
1838     QCOMPARE(itemsInModel(),
1839              QStringList() << "a"
1840                            << "1");
1841 }
1842 
1843 /**
1844  * Create a tree structure where parent-child relationships can not be
1845  * determined by parsing the URLs, and verify that KFileItemModel
1846  * handles them correctly.
1847  */
1848 void KFileItemModelTest::testGeneralParentChildRelationships()
1849 {
1850     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
1851     QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved);
1852 
1853     QSet<QByteArray> modelRoles = m_model->roles();
1854     modelRoles << "isExpanded"
1855                << "isExpandable"
1856                << "expandedParentsCount";
1857     m_model->setRoles(modelRoles);
1858 
1859     m_testDir->createFiles({"parent1/realChild1/realGrandChild1", "parent2/realChild2/realGrandChild2"});
1860 
1861     m_model->loadDirectory(m_testDir->url());
1862     QVERIFY(itemsInsertedSpy.wait());
1863     QCOMPARE(itemsInModel(),
1864              QStringList() << "parent1"
1865                            << "parent2");
1866 
1867     // Expand all folders.
1868     m_model->setExpanded(0, true);
1869     QVERIFY(itemsInsertedSpy.wait());
1870     QCOMPARE(itemsInModel(),
1871              QStringList() << "parent1"
1872                            << "realChild1"
1873                            << "parent2");
1874 
1875     m_model->setExpanded(1, true);
1876     QVERIFY(itemsInsertedSpy.wait());
1877     QCOMPARE(itemsInModel(),
1878              QStringList() << "parent1"
1879                            << "realChild1"
1880                            << "realGrandChild1"
1881                            << "parent2");
1882 
1883     m_model->setExpanded(3, true);
1884     QVERIFY(itemsInsertedSpy.wait());
1885     QCOMPARE(itemsInModel(),
1886              QStringList() << "parent1"
1887                            << "realChild1"
1888                            << "realGrandChild1"
1889                            << "parent2"
1890                            << "realChild2");
1891 
1892     m_model->setExpanded(4, true);
1893     QVERIFY(itemsInsertedSpy.wait());
1894     QCOMPARE(itemsInModel(),
1895              QStringList() << "parent1"
1896                            << "realChild1"
1897                            << "realGrandChild1"
1898                            << "parent2"
1899                            << "realChild2"
1900                            << "realGrandChild2");
1901 
1902     // Add some more children and grand-children.
1903     const QUrl parent1 = m_model->fileItem(0).url();
1904     const QUrl parent2 = m_model->fileItem(3).url();
1905     const QUrl realChild1 = m_model->fileItem(1).url();
1906     const QUrl realChild2 = m_model->fileItem(4).url();
1907 
1908     m_model->slotItemsAdded(parent1, KFileItemList() << KFileItem(QUrl("child1"), QString(), KFileItem::Unknown));
1909     m_model->slotCompleted();
1910     QCOMPARE(itemsInModel(),
1911              QStringList() << "parent1"
1912                            << "realChild1"
1913                            << "realGrandChild1"
1914                            << "child1"
1915                            << "parent2"
1916                            << "realChild2"
1917                            << "realGrandChild2");
1918 
1919     m_model->slotItemsAdded(parent2, KFileItemList() << KFileItem(QUrl("child2"), QString(), KFileItem::Unknown));
1920     m_model->slotCompleted();
1921     QCOMPARE(itemsInModel(),
1922              QStringList() << "parent1"
1923                            << "realChild1"
1924                            << "realGrandChild1"
1925                            << "child1"
1926                            << "parent2"
1927                            << "realChild2"
1928                            << "realGrandChild2"
1929                            << "child2");
1930 
1931     m_model->slotItemsAdded(realChild1, KFileItemList() << KFileItem(QUrl("grandChild1"), QString(), KFileItem::Unknown));
1932     m_model->slotCompleted();
1933     QCOMPARE(itemsInModel(),
1934              QStringList() << "parent1"
1935                            << "realChild1"
1936                            << "grandChild1"
1937                            << "realGrandChild1"
1938                            << "child1"
1939                            << "parent2"
1940                            << "realChild2"
1941                            << "realGrandChild2"
1942                            << "child2");
1943 
1944     m_model->slotItemsAdded(realChild2, KFileItemList() << KFileItem(QUrl("grandChild2"), QString(), KFileItem::Unknown));
1945     m_model->slotCompleted();
1946     QCOMPARE(itemsInModel(),
1947              QStringList() << "parent1"
1948                            << "realChild1"
1949                            << "grandChild1"
1950                            << "realGrandChild1"
1951                            << "child1"
1952                            << "parent2"
1953                            << "realChild2"
1954                            << "grandChild2"
1955                            << "realGrandChild2"
1956                            << "child2");
1957 
1958     // Set a name filter that matches nothing -> nothing will remain.
1959     m_model->setNameFilter("xyz");
1960     QCOMPARE(itemsInModel(), QStringList());
1961     QCOMPARE(itemsRemovedSpy.count(), 1);
1962     QList<QVariant> arguments = itemsRemovedSpy.takeFirst();
1963     KItemRangeList itemRangeList = arguments.at(0).value<KItemRangeList>();
1964     QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 10));
1965 
1966     // Set a name filter that matches only "realChild". Their prarents should still show.
1967     m_model->setNameFilter("realChild");
1968     QCOMPARE(itemsInModel(),
1969              QStringList() << "parent1"
1970                            << "realChild1"
1971                            << "parent2"
1972                            << "realChild2");
1973     QCOMPARE(itemsRemovedSpy.count(), 0); // nothing was removed, itemsRemovedSpy will not be called this time
1974 
1975     // Collapse "parent1".
1976     m_model->setExpanded(0, false);
1977     QCOMPARE(itemsInModel(),
1978              QStringList() << "parent1"
1979                            << "parent2"
1980                            << "realChild2");
1981     QCOMPARE(itemsRemovedSpy.count(), 1);
1982     arguments = itemsRemovedSpy.takeFirst();
1983     itemRangeList = arguments.at(0).value<KItemRangeList>();
1984     QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 1));
1985 
1986     // Remove "parent2".
1987     m_model->slotItemsDeleted(KFileItemList() << m_model->fileItem(1));
1988     QCOMPARE(itemsInModel(), QStringList() << "parent1");
1989     QCOMPARE(itemsRemovedSpy.count(), 1);
1990     arguments = itemsRemovedSpy.takeFirst();
1991     itemRangeList = arguments.at(0).value<KItemRangeList>();
1992     QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 2));
1993 
1994     // Clear filter, verify that no items reappear.
1995     m_model->setNameFilter(QString());
1996     QCOMPARE(itemsInModel(), QStringList() << "parent1");
1997 }
1998 
1999 void KFileItemModelTest::testNameRoleGroups()
2000 {
2001     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
2002     QSignalSpy itemsMovedSpy(m_model, &KFileItemModel::itemsMoved);
2003     QVERIFY(itemsMovedSpy.isValid());
2004     QSignalSpy groupsChangedSpy(m_model, &KFileItemModel::groupsChanged);
2005     QVERIFY(groupsChangedSpy.isValid());
2006 
2007     m_testDir->createFiles({"b.txt", "c.txt", "d.txt", "e.txt"});
2008 
2009     m_model->setGroupedSorting(true);
2010     m_model->loadDirectory(m_testDir->url());
2011     QVERIFY(itemsInsertedSpy.wait());
2012     QCOMPARE(itemsInModel(),
2013              QStringList() << "b.txt"
2014                            << "c.txt"
2015                            << "d.txt"
2016                            << "e.txt");
2017 
2018     QList<QPair<int, QVariant>> expectedGroups;
2019     expectedGroups << QPair<int, QVariant>(0, QLatin1String("B"));
2020     expectedGroups << QPair<int, QVariant>(1, QLatin1String("C"));
2021     expectedGroups << QPair<int, QVariant>(2, QLatin1String("D"));
2022     expectedGroups << QPair<int, QVariant>(3, QLatin1String("E"));
2023     QCOMPARE(m_model->groups(), expectedGroups);
2024 
2025     // Rename d.txt to a.txt.
2026     QHash<QByteArray, QVariant> data;
2027     data.insert("text", "a.txt");
2028     m_model->setData(2, data);
2029     QVERIFY(itemsMovedSpy.wait());
2030     QCOMPARE(itemsInModel(),
2031              QStringList() << "a.txt"
2032                            << "b.txt"
2033                            << "c.txt"
2034                            << "e.txt");
2035 
2036     expectedGroups.clear();
2037     expectedGroups << QPair<int, QVariant>(0, QLatin1String("A"));
2038     expectedGroups << QPair<int, QVariant>(1, QLatin1String("B"));
2039     expectedGroups << QPair<int, QVariant>(2, QLatin1String("C"));
2040     expectedGroups << QPair<int, QVariant>(3, QLatin1String("E"));
2041     QCOMPARE(m_model->groups(), expectedGroups);
2042 
2043     // Rename c.txt to d.txt.
2044     data.insert("text", "d.txt");
2045     m_model->setData(2, data);
2046     QVERIFY(groupsChangedSpy.wait());
2047     QCOMPARE(itemsInModel(),
2048              QStringList() << "a.txt"
2049                            << "b.txt"
2050                            << "d.txt"
2051                            << "e.txt");
2052 
2053     expectedGroups.clear();
2054     expectedGroups << QPair<int, QVariant>(0, QLatin1String("A"));
2055     expectedGroups << QPair<int, QVariant>(1, QLatin1String("B"));
2056     expectedGroups << QPair<int, QVariant>(2, QLatin1String("D"));
2057     expectedGroups << QPair<int, QVariant>(3, QLatin1String("E"));
2058     QCOMPARE(m_model->groups(), expectedGroups);
2059 
2060     // Change d.txt back to c.txt, but this time using the dir lister's refreshItems() signal.
2061     const KFileItem fileItemD = m_model->fileItem(2);
2062     KFileItem fileItemC = fileItemD;
2063     QUrl urlC = fileItemC.url().adjusted(QUrl::RemoveFilename);
2064     urlC.setPath(urlC.path() + "c.txt");
2065     fileItemC.setUrl(urlC);
2066 
2067     m_model->slotRefreshItems({qMakePair(fileItemD, fileItemC)});
2068     QVERIFY(groupsChangedSpy.wait());
2069     QCOMPARE(itemsInModel(),
2070              QStringList() << "a.txt"
2071                            << "b.txt"
2072                            << "c.txt"
2073                            << "e.txt");
2074 
2075     expectedGroups.clear();
2076     expectedGroups << QPair<int, QVariant>(0, QLatin1String("A"));
2077     expectedGroups << QPair<int, QVariant>(1, QLatin1String("B"));
2078     expectedGroups << QPair<int, QVariant>(2, QLatin1String("C"));
2079     expectedGroups << QPair<int, QVariant>(3, QLatin1String("E"));
2080     QCOMPARE(m_model->groups(), expectedGroups);
2081 }
2082 
2083 void KFileItemModelTest::testNameRoleGroupsWithExpandedItems()
2084 {
2085     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
2086 
2087     QSet<QByteArray> modelRoles = m_model->roles();
2088     modelRoles << "isExpanded"
2089                << "isExpandable"
2090                << "expandedParentsCount";
2091     m_model->setRoles(modelRoles);
2092 
2093     m_testDir->createFiles({"a/b.txt", "a/c.txt", "d/e.txt", "d/f.txt"});
2094 
2095     m_model->setGroupedSorting(true);
2096     m_model->loadDirectory(m_testDir->url());
2097     QVERIFY(itemsInsertedSpy.wait());
2098     QCOMPARE(itemsInModel(),
2099              QStringList() << "a"
2100                            << "d");
2101 
2102     QList<QPair<int, QVariant>> expectedGroups;
2103     expectedGroups << QPair<int, QVariant>(0, QLatin1String("A"));
2104     expectedGroups << QPair<int, QVariant>(1, QLatin1String("D"));
2105     QCOMPARE(m_model->groups(), expectedGroups);
2106 
2107     // Verify that expanding "a" and "d" will not change the groups (except for the index of "D").
2108     expectedGroups.clear();
2109     expectedGroups << QPair<int, QVariant>(0, QLatin1String("A"));
2110     expectedGroups << QPair<int, QVariant>(3, QLatin1String("D"));
2111 
2112     m_model->setExpanded(0, true);
2113     QVERIFY(m_model->isExpanded(0));
2114     QVERIFY(itemsInsertedSpy.wait());
2115     QCOMPARE(itemsInModel(),
2116              QStringList() << "a"
2117                            << "b.txt"
2118                            << "c.txt"
2119                            << "d");
2120     QCOMPARE(m_model->groups(), expectedGroups);
2121 
2122     m_model->setExpanded(3, true);
2123     QVERIFY(m_model->isExpanded(3));
2124     QVERIFY(itemsInsertedSpy.wait());
2125     QCOMPARE(itemsInModel(),
2126              QStringList() << "a"
2127                            << "b.txt"
2128                            << "c.txt"
2129                            << "d"
2130                            << "e.txt"
2131                            << "f.txt");
2132     QCOMPARE(m_model->groups(), expectedGroups);
2133 }
2134 
2135 void KFileItemModelTest::testInconsistentModel()
2136 {
2137     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
2138 
2139     QSet<QByteArray> modelRoles = m_model->roles();
2140     modelRoles << "isExpanded"
2141                << "isExpandable"
2142                << "expandedParentsCount";
2143     m_model->setRoles(modelRoles);
2144 
2145     m_testDir->createFiles({"a/b/c1.txt", "a/b/c2.txt"});
2146 
2147     m_model->loadDirectory(m_testDir->url());
2148     QVERIFY(itemsInsertedSpy.wait());
2149     QCOMPARE(itemsInModel(), QStringList() << "a");
2150 
2151     // Expand "a/" and "a/b/".
2152     m_model->setExpanded(0, true);
2153     QVERIFY(m_model->isExpanded(0));
2154     QVERIFY(itemsInsertedSpy.wait());
2155     QCOMPARE(itemsInModel(),
2156              QStringList() << "a"
2157                            << "b");
2158 
2159     m_model->setExpanded(1, true);
2160     QVERIFY(m_model->isExpanded(1));
2161     QVERIFY(itemsInsertedSpy.wait());
2162     QCOMPARE(itemsInModel(),
2163              QStringList() << "a"
2164                            << "b"
2165                            << "c1.txt"
2166                            << "c2.txt");
2167 
2168     // Add the files "c1.txt" and "c2.txt" to the model also as top-level items.
2169     // Such a thing can in principle happen when performing a search, and there
2170     // are files which
2171     // (a) match the search string, and
2172     // (b) are children of a folder that matches the search string and is expanded.
2173     //
2174     // Note that the first item in the list of added items must be new (i.e., not
2175     // in the model yet). Otherwise, KFileItemModel::slotItemsAdded() will see that
2176     // it receives items that are in the model already and ignore them.
2177     QUrl url(m_model->directory().url() + "/a2");
2178     KFileItem newItem(url);
2179 
2180     KFileItemList items;
2181     items << newItem << m_model->fileItem(2) << m_model->fileItem(3);
2182     m_model->slotItemsAdded(m_model->directory(), items);
2183     m_model->slotCompleted();
2184     QCOMPARE(itemsInModel(),
2185              QStringList() << "a"
2186                            << "b"
2187                            << "c1.txt"
2188                            << "c2.txt"
2189                            << "a2"
2190                            << "c1.txt"
2191                            << "c2.txt");
2192 
2193     m_model->setExpanded(0, false);
2194 
2195     // Test that the right items have been removed, see
2196     // https://bugs.kde.org/show_bug.cgi?id=324371
2197     QCOMPARE(itemsInModel(),
2198              QStringList() << "a"
2199                            << "a2"
2200                            << "c1.txt"
2201                            << "c2.txt");
2202 
2203     // Test that resorting does not cause a crash, see
2204     // https://bugs.kde.org/show_bug.cgi?id=325359
2205     // The crash is not 100% reproducible, but Valgrind will report an invalid memory access.
2206     m_model->resortAllItems();
2207 }
2208 
2209 void KFileItemModelTest::testChangeRolesForFilteredItems()
2210 {
2211     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
2212 
2213     QSet<QByteArray> modelRoles = m_model->roles();
2214     modelRoles << "owner";
2215     m_model->setRoles(modelRoles);
2216 
2217     m_testDir->createFiles({"a.txt", "aa.txt", "aaa.txt"});
2218 
2219     m_model->loadDirectory(m_testDir->url());
2220     QVERIFY(itemsInsertedSpy.wait());
2221     QCOMPARE(itemsInModel(),
2222              QStringList() << "a.txt"
2223                            << "aa.txt"
2224                            << "aaa.txt");
2225 
2226     for (int index = 0; index < m_model->count(); ++index) {
2227         // All items should have the "text" and "owner" roles, but not "group".
2228         QVERIFY(m_model->data(index).contains("text"));
2229         QVERIFY(m_model->data(index).contains("owner"));
2230         QVERIFY(!m_model->data(index).contains("group"));
2231     }
2232 
2233     // Add a filter, such that only "aaa.txt" remains in the model.
2234     m_model->setNameFilter("aaa");
2235     QCOMPARE(itemsInModel(), QStringList() << "aaa.txt");
2236 
2237     // Add the "group" role.
2238     modelRoles << "group";
2239     m_model->setRoles(modelRoles);
2240 
2241     // Modify the filter, such that "aa.txt" reappears, and verify that all items have the expected roles.
2242     m_model->setNameFilter("aa");
2243     QCOMPARE(itemsInModel(),
2244              QStringList() << "aa.txt"
2245                            << "aaa.txt");
2246 
2247     for (int index = 0; index < m_model->count(); ++index) {
2248         // All items should have the "text", "owner", and "group" roles.
2249         QVERIFY(m_model->data(index).contains("text"));
2250         QVERIFY(m_model->data(index).contains("owner"));
2251         QVERIFY(m_model->data(index).contains("group"));
2252     }
2253 
2254     // Remove the "owner" role.
2255     modelRoles.remove("owner");
2256     m_model->setRoles(modelRoles);
2257 
2258     // Clear the filter, and verify that all items have the expected roles
2259     m_model->setNameFilter(QString());
2260     QCOMPARE(itemsInModel(),
2261              QStringList() << "a.txt"
2262                            << "aa.txt"
2263                            << "aaa.txt");
2264 
2265     for (int index = 0; index < m_model->count(); ++index) {
2266         // All items should have the "text" and "group" roles, but now "owner".
2267         QVERIFY(m_model->data(index).contains("text"));
2268         QVERIFY(!m_model->data(index).contains("owner"));
2269         QVERIFY(m_model->data(index).contains("group"));
2270     }
2271 }
2272 
2273 void KFileItemModelTest::testChangeSortRoleWhileFiltering()
2274 {
2275     KFileItemList items;
2276 
2277     KIO::UDSEntry entry[3];
2278 
2279     entry[0].fastInsert(KIO::UDSEntry::UDS_NAME, "a.txt");
2280     entry[0].fastInsert(KIO::UDSEntry::UDS_USER, "user-b");
2281 
2282     entry[1].fastInsert(KIO::UDSEntry::UDS_NAME, "b.txt");
2283     entry[1].fastInsert(KIO::UDSEntry::UDS_USER, "user-c");
2284 
2285     entry[2].fastInsert(KIO::UDSEntry::UDS_NAME, "c.txt");
2286     entry[2].fastInsert(KIO::UDSEntry::UDS_USER, "user-a");
2287 
2288     for (int i = 0; i < 3; ++i) {
2289         entry[i].fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, 0100000); // S_IFREG might not be defined on non-Unix platforms.
2290         entry[i].fastInsert(KIO::UDSEntry::UDS_ACCESS, 07777);
2291         entry[i].fastInsert(KIO::UDSEntry::UDS_SIZE, 0);
2292         entry[i].fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, 0);
2293         entry[i].fastInsert(KIO::UDSEntry::UDS_GROUP, "group");
2294         entry[i].fastInsert(KIO::UDSEntry::UDS_ACCESS_TIME, 0);
2295         items.append(KFileItem(entry[i], m_testDir->url(), false, true));
2296     }
2297 
2298     m_model->slotItemsAdded(m_testDir->url(), items);
2299     m_model->slotCompleted();
2300 
2301     QCOMPARE(itemsInModel(),
2302              QStringList() << "a.txt"
2303                            << "b.txt"
2304                            << "c.txt");
2305 
2306     // Add a filter.
2307     m_model->setNameFilter("a");
2308     QCOMPARE(itemsInModel(), QStringList() << "a.txt");
2309 
2310     // Sort by "owner".
2311     m_model->setSortRole("owner");
2312 
2313     // Clear the filter, and verify that the items are sorted correctly.
2314     m_model->setNameFilter(QString());
2315     QCOMPARE(itemsInModel(),
2316              QStringList() << "c.txt"
2317                            << "a.txt"
2318                            << "b.txt");
2319 }
2320 
2321 void KFileItemModelTest::testRefreshFilteredItems()
2322 {
2323     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
2324 
2325     m_testDir->createFiles({"a.txt", "b.txt", "c.jpg", "d.jpg"});
2326 
2327     m_model->loadDirectory(m_testDir->url());
2328     QVERIFY(itemsInsertedSpy.wait());
2329     QCOMPARE(itemsInModel(),
2330              QStringList() << "a.txt"
2331                            << "b.txt"
2332                            << "c.jpg"
2333                            << "d.jpg");
2334 
2335     const KFileItem fileItemC = m_model->fileItem(2);
2336 
2337     // Show only the .txt files.
2338     m_model->setNameFilter(".txt");
2339     QCOMPARE(itemsInModel(),
2340              QStringList() << "a.txt"
2341                            << "b.txt");
2342 
2343     // Rename one of the .jpg files.
2344     KFileItem fileItemE = fileItemC;
2345     QUrl urlE = fileItemE.url().adjusted(QUrl::RemoveFilename);
2346     urlE.setPath(urlE.path() + "/e.jpg");
2347     fileItemE.setUrl(urlE);
2348 
2349     m_model->slotRefreshItems({qMakePair(fileItemC, fileItemE)});
2350 
2351     // Show all files again, and verify that the model has updated the file name.
2352     m_model->setNameFilter(QString());
2353     QCOMPARE(itemsInModel(),
2354              QStringList() << "a.txt"
2355                            << "b.txt"
2356                            << "d.jpg"
2357                            << "e.jpg");
2358 }
2359 
2360 void KFileItemModelTest::testCreateMimeData()
2361 {
2362     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
2363 
2364     QSet<QByteArray> modelRoles = m_model->roles();
2365     modelRoles << "isExpanded"
2366                << "isExpandable"
2367                << "expandedParentsCount";
2368     m_model->setRoles(modelRoles);
2369 
2370     m_testDir->createFile("a/1");
2371 
2372     m_model->loadDirectory(m_testDir->url());
2373     QVERIFY(itemsInsertedSpy.wait());
2374     QCOMPARE(itemsInModel(), QStringList() << "a");
2375 
2376     // Expand "a/".
2377     m_model->setExpanded(0, true);
2378     QVERIFY(itemsInsertedSpy.wait());
2379     QCOMPARE(itemsInModel(),
2380              QStringList() << "a"
2381                            << "1");
2382 
2383     // Verify that creating the MIME data for a child of an expanded folder does
2384     // not cause a crash, see https://bugs.kde.org/show_bug.cgi?id=329119
2385     KItemSet selection;
2386     selection.insert(1);
2387     QMimeData *mimeData = m_model->createMimeData(selection);
2388     delete mimeData;
2389 }
2390 
2391 void KFileItemModelTest::testCollapseFolderWhileLoading()
2392 {
2393     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
2394 
2395     QSet<QByteArray> modelRoles = m_model->roles();
2396     modelRoles << "isExpanded"
2397                << "isExpandable"
2398                << "expandedParentsCount";
2399     m_model->setRoles(modelRoles);
2400 
2401     m_testDir->createFile("a2/b/c1.txt");
2402 
2403     m_model->loadDirectory(m_testDir->url());
2404     QVERIFY(itemsInsertedSpy.wait());
2405     QCOMPARE(itemsInModel(), QStringList() << "a2");
2406 
2407     // Expand "a2/".
2408     m_model->setExpanded(0, true);
2409     QVERIFY(m_model->isExpanded(0));
2410     QVERIFY(itemsInsertedSpy.wait());
2411     QCOMPARE(itemsInModel(),
2412              QStringList() << "a2"
2413                            << "b");
2414 
2415     // Expand "a2/b/".
2416     m_model->setExpanded(1, true);
2417     QVERIFY(m_model->isExpanded(1));
2418     QVERIFY(itemsInsertedSpy.wait());
2419     QCOMPARE(itemsInModel(),
2420              QStringList() << "a2"
2421                            << "b"
2422                            << "c1.txt");
2423 
2424     // Simulate that a new item "c2.txt" appears, but that the dir lister's completed()
2425     // signal is not emitted yet.
2426     const KFileItem fileItemC1 = m_model->fileItem(2);
2427     KFileItem fileItemC2 = fileItemC1;
2428     QUrl urlC2 = fileItemC2.url();
2429     urlC2 = urlC2.adjusted(QUrl::RemoveFilename);
2430     urlC2.setPath(urlC2.path() + "c2.txt");
2431     fileItemC2.setUrl(urlC2);
2432 
2433     const QUrl urlB = m_model->fileItem(1).url();
2434     m_model->slotItemsAdded(urlB, KFileItemList() << fileItemC2);
2435     QCOMPARE(itemsInModel(),
2436              QStringList() << "a2"
2437                            << "b"
2438                            << "c1.txt");
2439 
2440     // Collapse "a2/". This should also remove all its (indirect) children from
2441     // the model and from the model's m_pendingItemsToInsert member.
2442     m_model->setExpanded(0, false);
2443     QCOMPARE(itemsInModel(), QStringList() << "a2");
2444 
2445     // Simulate that the dir lister's completed() signal is emitted. If "c2.txt"
2446     // is still in m_pendingItemsToInsert, then we might get a crash, see
2447     // https://bugs.kde.org/show_bug.cgi?id=332102. Even if the crash is not
2448     // reproducible here, Valgrind will complain, and the item "c2.txt" will appear
2449     // without parent in the model.
2450     m_model->slotCompleted();
2451     QCOMPARE(itemsInModel(), QStringList() << "a2");
2452 
2453     // Expand "a2/" again.
2454     m_model->setExpanded(0, true);
2455     QVERIFY(m_model->isExpanded(0));
2456     QVERIFY(itemsInsertedSpy.wait());
2457     QCOMPARE(itemsInModel(),
2458              QStringList() << "a2"
2459                            << "b");
2460 
2461     // Now simulate that a new folder "a1/" is appears, but that the dir lister's
2462     // completed() signal is not emitted yet.
2463     const KFileItem fileItemA2 = m_model->fileItem(0);
2464     KFileItem fileItemA1 = fileItemA2;
2465     QUrl urlA1 = fileItemA1.url().adjusted(QUrl::RemoveFilename);
2466     urlA1.setPath(urlA1.path() + "a1");
2467     fileItemA1.setUrl(urlA1);
2468 
2469     m_model->slotItemsAdded(m_model->directory(), KFileItemList() << fileItemA1);
2470     QCOMPARE(itemsInModel(),
2471              QStringList() << "a2"
2472                            << "b");
2473 
2474     // Collapse "a2/". Note that this will cause "a1/" to be added to the model,
2475     // i.e., the index of "a2/" will change from 0 to 1. Check that this does not
2476     // confuse the code which collapses the folder.
2477     m_model->setExpanded(0, false);
2478     QCOMPARE(itemsInModel(),
2479              QStringList() << "a1"
2480                            << "a2");
2481     QVERIFY(!m_model->isExpanded(0));
2482     QVERIFY(!m_model->isExpanded(1));
2483 }
2484 
2485 void KFileItemModelTest::testDeleteFileMoreThanOnce()
2486 {
2487     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
2488 
2489     m_testDir->createFiles({"a.txt", "b.txt", "c.txt", "d.txt"});
2490 
2491     m_model->loadDirectory(m_testDir->url());
2492     QVERIFY(itemsInsertedSpy.wait());
2493     QCOMPARE(itemsInModel(),
2494              QStringList() << "a.txt"
2495                            << "b.txt"
2496                            << "c.txt"
2497                            << "d.txt");
2498 
2499     const KFileItem fileItemB = m_model->fileItem(1);
2500 
2501     // Tell the model that a list of items has been deleted, where "b.txt" appears twice in the list.
2502     KFileItemList list;
2503     list << fileItemB << fileItemB;
2504     m_model->slotItemsDeleted(list);
2505 
2506     QVERIFY(m_model->isConsistent());
2507     QCOMPARE(itemsInModel(),
2508              QStringList() << "a.txt"
2509                            << "c.txt"
2510                            << "d.txt");
2511 }
2512 
2513 void KFileItemModelTest::testInsertAfterExpand()
2514 {
2515     m_model->m_dirLister->setAutoUpdate(true);
2516 
2517     QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
2518     QVERIFY(itemsInsertedSpy.isValid());
2519     QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved);
2520     QVERIFY(itemsRemovedSpy.isValid());
2521     QSignalSpy loadingCompletedSpy(m_model, &KFileItemModel::directoryLoadingCompleted);
2522     QVERIFY(loadingCompletedSpy.isValid());
2523 
2524     // Test expanding subfolders in a folder with the items "a/", "a/a/"
2525     QSet<QByteArray> originalModelRoles = m_model->roles();
2526     QSet<QByteArray> modelRoles = originalModelRoles;
2527     modelRoles << "isExpanded"
2528                << "isExpandable"
2529                << "expandedParentsCount";
2530     m_model->setRoles(modelRoles);
2531 
2532     m_testDir->createFile("a/b/1");
2533 
2534     m_model->loadDirectory(m_testDir->url());
2535     QVERIFY(itemsInsertedSpy.wait());
2536 
2537     // So far, the model contains only "a/"
2538     QCOMPARE(m_model->count(), 1);
2539     QVERIFY(m_model->isExpandable(0));
2540     QVERIFY(!m_model->isExpanded(0));
2541     QVERIFY(m_model->expandedDirectories().empty());
2542 
2543     QCOMPARE(itemsInsertedSpy.count(), 1);
2544     {
2545         KItemRangeList itemRangeList = itemsInsertedSpy.takeFirst().at(0).value<KItemRangeList>();
2546         QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 1)); // 1 new item "a/" with index 0
2547         QCOMPARE(m_model->expandedParentsCount(0), 0);
2548     }
2549     itemsInsertedSpy.clear();
2550 
2551     // Expand the folder "a/" -> "a/b" become visible
2552     m_model->setExpanded(0, true);
2553     QVERIFY(m_model->isExpanded(0));
2554     QVERIFY(itemsInsertedSpy.wait());
2555     QCOMPARE(m_model->count(), 2); // 3 items: "a/", "a/a/"
2556     QCOMPARE(m_model->expandedDirectories(), QSet<QUrl>({QUrl::fromLocalFile(m_testDir->path() + "/a")}));
2557 
2558     QCOMPARE(itemsInsertedSpy.count(), 1);
2559     {
2560         KItemRangeList itemRangeList = itemsInsertedSpy.takeFirst().at(0).value<KItemRangeList>();
2561         QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 1)); // 1 new item "a/b" with index 1
2562         QCOMPARE(m_model->expandedParentsCount(1), 1);
2563     }
2564     itemsInsertedSpy.clear();
2565 
2566     // Expand "a/b" -> "a/b/1" becomes visible
2567     m_model->setExpanded(1, true);
2568     QVERIFY(itemsInsertedSpy.wait());
2569     QCOMPARE(m_model->expandedDirectories(), QSet({QUrl::fromLocalFile(m_testDir->path() + "/a"), QUrl::fromLocalFile(m_testDir->path() + "/a/b")}));
2570 
2571     QCOMPARE(itemsInsertedSpy.count(), 1);
2572     {
2573         KItemRangeList itemRangeList = itemsInsertedSpy.takeFirst().at(0).value<KItemRangeList>();
2574         QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(2, 1)); // 1 new item "a/b/1" with index 2
2575         QCOMPARE(m_model->expandedParentsCount(2), 2);
2576     }
2577     itemsInsertedSpy.clear();
2578 
2579     // Collapse "a" whilst leaving "b" expanded
2580     m_model->setExpanded(0, false);
2581 
2582     // Insert additional files into "a/b/"
2583     m_testDir->createFile("a/b/2");
2584 
2585     QVERIFY(!itemsInsertedSpy.wait(5000));
2586 
2587     QCOMPARE(itemsInModel(), {"a"});
2588 
2589     m_model->setExpanded(0, true);
2590     ;
2591     QTRY_COMPARE(itemsInModel(), QStringList({"a", "b", "1", "2"}));
2592 
2593     QCOMPARE(m_model->expandedParentsCount(0), 0); // a
2594     QCOMPARE(m_model->expandedParentsCount(1), 1); // a/b
2595     QCOMPARE(m_model->expandedParentsCount(2), 2); // a/b/1
2596     QCOMPARE(m_model->expandedParentsCount(3), 2); // a/b/2
2597 }
2598 
2599 void KFileItemModelTest::testCurrentDirRemoved()
2600 {
2601     m_model->m_dirLister->setAutoUpdate(true);
2602     QSignalSpy currentDirectoryRemovedSpy(m_model, &KFileItemModel::currentDirectoryRemoved);
2603     QVERIFY(currentDirectoryRemovedSpy.isValid());
2604     QSignalSpy loadingCompletedSpy(m_model, &KFileItemModel::directoryLoadingCompleted);
2605     QVERIFY(loadingCompletedSpy.isValid());
2606     QSignalSpy dirListerClearSpy(m_model->m_dirLister, &KCoreDirLister::clear);
2607     QVERIFY(dirListerClearSpy.isValid());
2608 
2609     m_testDir->createFiles({"dir/a.txt", "dir/b.txt"});
2610     m_model->loadDirectory(QUrl::fromLocalFile(m_testDir->path() + "/dir/"));
2611     QVERIFY(loadingCompletedSpy.wait());
2612     QCOMPARE(m_model->count(), 2);
2613     QVERIFY(m_model->isConsistent());
2614 
2615     m_testDir->removeDir("dir");
2616     QVERIFY(currentDirectoryRemovedSpy.wait());
2617 
2618     // dirLister calls clear
2619     QCOMPARE(dirListerClearSpy.count(), 2);
2620     QVERIFY(m_model->isConsistent());
2621     QVERIFY(m_model->m_itemData.isEmpty());
2622     QCOMPARE(m_model->count(), 0);
2623 }
2624 
2625 QStringList KFileItemModelTest::itemsInModel() const
2626 {
2627     QStringList items;
2628     for (int i = 0; i < m_model->count(); i++) {
2629         items << m_model->fileItem(i).text();
2630     }
2631     return items;
2632 }
2633 
2634 QTEST_MAIN(KFileItemModelTest)
2635 
2636 #include "kfileitemmodeltest.moc"