X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/545d872b817a0a91aeb7867f04cc0d912d226da0..refs/heads/master:/src/tests/kfileitemmodeltest.cpp diff --git a/src/tests/kfileitemmodeltest.cpp b/src/tests/kfileitemmodeltest.cpp index 99ee3368e..34ae2ba87 100644 --- a/src/tests/kfileitemmodeltest.cpp +++ b/src/tests/kfileitemmodeltest.cpp @@ -1,54 +1,43 @@ -/*************************************************************************** - * Copyright (C) 2011 by Peter Penz * - * Copyright (C) 2011 by Frank Reininghaus * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * - ***************************************************************************/ - -#include +/* + * SPDX-FileCopyrightText: 2011 Peter Penz + * SPDX-FileCopyrightText: 2011 Frank Reininghaus + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include +#include +#include +#include +#include +#include #include -#include +#include #include "kitemviews/kfileitemmodel.h" -#include "kitemviews/private/kfileitemmodeldirlister.h" #include "testdir.h" -void myMessageOutput(QtMsgType type, const char* msg) +void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) { + Q_UNUSED(context) + switch (type) { case QtDebugMsg: break; case QtWarningMsg: break; case QtCriticalMsg: - fprintf(stderr, "Critical: %s\n", msg); + fprintf(stderr, "Critical: %s\n", msg.toLocal8Bit().data()); break; case QtFatalMsg: - fprintf(stderr, "Fatal: %s\n", msg); + fprintf(stderr, "Fatal: %s\n", msg.toLocal8Bit().data()); abort(); default: - break; + break; } } -namespace { - const int DefaultTimeout = 5000; -}; - Q_DECLARE_METATYPE(KItemRange) Q_DECLARE_METATYPE(KItemRangeList) Q_DECLARE_METATYPE(QList) @@ -57,8 +46,9 @@ class KFileItemModelTest : public QObject { Q_OBJECT -private slots: +private Q_SLOTS: void init(); + void initTestCase(); void cleanup(); void testDefaultRoles(); @@ -79,10 +69,15 @@ private slots: void testMakeExpandedItemHidden(); void testRemoveFilteredExpandedItems(); void testSorting(); + void testNaturalSorting(); void testIndexForKeyboardSearch(); void testNameFilter(); void testEmptyPath(); void testRefreshExpandedItem(); + void testAddItemToFilteredExpandedFolder(); + void testDeleteItemsWithExpandedFolderWithFilter(); + void testRefreshItemsWithFilter(); + void testRefreshExpandedFolderWithFilter(); void testRemoveHiddenItems(); void collapseParentOfHiddenItems(); void removeParentOfHiddenItems(); @@ -93,21 +88,31 @@ private slots: void testChangeRolesForFilteredItems(); void testChangeSortRoleWhileFiltering(); void testRefreshFilteredItems(); + void testCollapseFolderWhileLoading(); void testCreateMimeData(); + void testDeleteFileMoreThanOnce(); + void testInsertAfterExpand(); + void testCurrentDirRemoved(); + void testSizeSortingAfterRefresh(); private: QStringList itemsInModel() const; private: - KFileItemModel* m_model; - TestDir* m_testDir; + KFileItemModel *m_model; + TestDir *m_testDir; }; +void KFileItemModelTest::initTestCase() +{ + QStandardPaths::setTestModeEnabled(true); +} + void KFileItemModelTest::init() { // The item-model tests result in a huge number of debugging // output from kdelibs. Only show critical and fatal messages. - qInstallMsgHandler(myMessageOutput); + qInstallMessageHandler(myMessageOutput); qRegisterMetaType("KItemRange"); qRegisterMetaType("KItemRangeList"); @@ -124,52 +129,53 @@ void KFileItemModelTest::init() void KFileItemModelTest::cleanup() { delete m_model; - m_model = 0; + m_model = nullptr; delete m_testDir; - m_testDir = 0; + m_testDir = nullptr; } void KFileItemModelTest::testDefaultRoles() { const QSet roles = m_model->roles(); - QCOMPARE(roles.count(), 3); + QCOMPARE(roles.count(), 4); QVERIFY(roles.contains("text")); QVERIFY(roles.contains("isDir")); QVERIFY(roles.contains("isLink")); + QVERIFY(roles.contains("isHidden")); } void KFileItemModelTest::testDefaultSortRole() { - QCOMPARE(m_model->sortRole(), QByteArray("text")); + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QVERIFY(itemsInsertedSpy.isValid()); - QStringList files; - files << "c.txt" << "a.txt" << "b.txt"; + QCOMPARE(m_model->sortRole(), QByteArray("text")); - m_testDir->createFiles(files); + m_testDir->createFiles({"c.txt", "a.txt", "b.txt"}); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); QCOMPARE(m_model->count(), 3); - QCOMPARE(m_model->data(0)["text"].toString(), QString("a.txt")); - QCOMPARE(m_model->data(1)["text"].toString(), QString("b.txt")); - QCOMPARE(m_model->data(2)["text"].toString(), QString("c.txt")); + QCOMPARE(m_model->data(0).value("text").toString(), QStringLiteral("a.txt")); + QCOMPARE(m_model->data(1).value("text").toString(), QStringLiteral("b.txt")); + QCOMPARE(m_model->data(2).value("text").toString(), QStringLiteral("c.txt")); } void KFileItemModelTest::testDefaultGroupedSorting() { - QCOMPARE(m_model->groupedSorting(), false); + QCOMPARE(m_model->groupedSorting(), true); } void KFileItemModelTest::testNewItems() { - QStringList files; - files << "a.txt" << "b.txt" << "c.txt"; - m_testDir->createFiles(files); + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + + m_testDir->createFiles({"a.txt", "b.txt", "c.txt"}); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); QCOMPARE(m_model->count(), 3); @@ -178,38 +184,40 @@ void KFileItemModelTest::testNewItems() void KFileItemModelTest::testRemoveItems() { - m_testDir->createFile("a.txt"); - m_testDir->createFile("b.txt"); + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved); + + m_testDir->createFiles({"a.txt", "b.txt"}); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); QCOMPARE(m_model->count(), 2); QVERIFY(m_model->isConsistent()); m_testDir->removeFile("a.txt"); m_model->m_dirLister->updateDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsRemoved(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsRemovedSpy.wait()); QCOMPARE(m_model->count(), 1); QVERIFY(m_model->isConsistent()); } void KFileItemModelTest::testDirLoadingCompleted() { - QSignalSpy loadingCompletedSpy(m_model, SIGNAL(directoryLoadingCompleted())); - QSignalSpy itemsInsertedSpy(m_model, SIGNAL(itemsInserted(KItemRangeList))); - QSignalSpy itemsRemovedSpy(m_model, SIGNAL(itemsRemoved(KItemRangeList))); + QSignalSpy loadingCompletedSpy(m_model, &KFileItemModel::directoryLoadingCompleted); + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved); - m_testDir->createFiles(QStringList() << "a.txt" << "b.txt" << "c.txt"); + m_testDir->createFiles({"a.txt", "b.txt", "c.txt"}); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(directoryLoadingCompleted()), DefaultTimeout)); + QVERIFY(loadingCompletedSpy.wait()); QCOMPARE(loadingCompletedSpy.count(), 1); QCOMPARE(itemsInsertedSpy.count(), 1); QCOMPARE(itemsRemovedSpy.count(), 0); QCOMPARE(m_model->count(), 3); - m_testDir->createFiles(QStringList() << "d.txt" << "e.txt"); + m_testDir->createFiles({"d.txt", "e.txt"}); m_model->m_dirLister->updateDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(directoryLoadingCompleted()), DefaultTimeout)); + QVERIFY(loadingCompletedSpy.wait()); QCOMPARE(loadingCompletedSpy.count(), 2); QCOMPARE(itemsInsertedSpy.count(), 2); QCOMPARE(itemsRemovedSpy.count(), 0); @@ -218,7 +226,7 @@ void KFileItemModelTest::testDirLoadingCompleted() m_testDir->removeFile("a.txt"); m_testDir->createFile("f.txt"); m_model->m_dirLister->updateDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(directoryLoadingCompleted()), DefaultTimeout)); + QVERIFY(loadingCompletedSpy.wait()); QCOMPARE(loadingCompletedSpy.count(), 3); QCOMPARE(itemsInsertedSpy.count(), 3); QCOMPARE(itemsRemovedSpy.count(), 1); @@ -226,7 +234,7 @@ void KFileItemModelTest::testDirLoadingCompleted() m_testDir->removeFile("b.txt"); m_model->m_dirLister->updateDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsRemoved(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsRemovedSpy.wait()); QCOMPARE(loadingCompletedSpy.count(), 4); QCOMPARE(itemsInsertedSpy.count(), 3); QCOMPARE(itemsRemovedSpy.count(), 2); @@ -237,22 +245,26 @@ void KFileItemModelTest::testDirLoadingCompleted() void KFileItemModelTest::testSetData() { + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QVERIFY(itemsInsertedSpy.isValid()); + QSignalSpy itemsChangedSpy(m_model, &KFileItemModel::itemsChanged); + QVERIFY(itemsChangedSpy.isValid()); + m_testDir->createFile("a.txt"); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); QHash values; values.insert("customRole1", "Test1"); values.insert("customRole2", "Test2"); - QSignalSpy itemsChangedSpy(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet))); m_model->setData(0, values); QCOMPARE(itemsChangedSpy.count(), 1); values = m_model->data(0); - QCOMPARE(values.value("customRole1").toString(), QString("Test1")); - QCOMPARE(values.value("customRole2").toString(), QString("Test2")); + QCOMPARE(values.value("customRole1").toString(), QStringLiteral("Test1")); + QCOMPARE(values.value("customRole2").toString(), QStringLiteral("Test2")); QVERIFY(m_model->isConsistent()); } @@ -271,11 +283,11 @@ void KFileItemModelTest::testSetDataWithModifiedSortRole_data() // Index 2 = rating 6 QTest::newRow("Index 0: Rating 3") << 0 << 3 << false << 3 << 4 << 6; - QTest::newRow("Index 0: Rating 5") << 0 << 5 << true << 4 << 5 << 6; - QTest::newRow("Index 0: Rating 8") << 0 << 8 << true << 4 << 6 << 8; + QTest::newRow("Index 0: Rating 5") << 0 << 5 << true << 4 << 5 << 6; + QTest::newRow("Index 0: Rating 8") << 0 << 8 << true << 4 << 6 << 8; - QTest::newRow("Index 2: Rating 1") << 2 << 1 << true << 1 << 2 << 4; - QTest::newRow("Index 2: Rating 3") << 2 << 3 << true << 2 << 3 << 4; + QTest::newRow("Index 2: Rating 1") << 2 << 1 << true << 1 << 2 << 4; + QTest::newRow("Index 2: Rating 3") << 2 << 3 << true << 2 << 3 << 4; QTest::newRow("Index 2: Rating 5") << 2 << 5 << false << 2 << 4 << 5; } @@ -288,18 +300,21 @@ void KFileItemModelTest::testSetDataWithModifiedSortRole() QFETCH(int, ratingIndex1); QFETCH(int, ratingIndex2); + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QVERIFY(itemsInsertedSpy.isValid()); + QSignalSpy itemsMovedSpy(m_model, &KFileItemModel::itemsMoved); + QVERIFY(itemsMovedSpy.isValid()); + // Changing the value of a sort-role must result in // a reordering of the items. QCOMPARE(m_model->sortRole(), QByteArray("text")); m_model->setSortRole("rating"); QCOMPARE(m_model->sortRole(), QByteArray("rating")); - QStringList files; - files << "a.txt" << "b.txt" << "c.txt"; - m_testDir->createFiles(files); + m_testDir->createFiles({"a.txt", "b.txt", "c.txt"}); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); // Fill the "rating" role of each file: // a.txt -> 2 @@ -329,7 +344,7 @@ void KFileItemModelTest::testSetDataWithModifiedSortRole() m_model->setData(changedIndex, rating); if (expectMoveSignal) { - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsMoved(KItemRange,QList)), DefaultTimeout)); + QVERIFY(itemsMovedSpy.wait()); } QCOMPARE(m_model->data(0).value("rating").toInt(), ratingIndex0); @@ -340,15 +355,20 @@ void KFileItemModelTest::testSetDataWithModifiedSortRole() void KFileItemModelTest::testChangeSortRole() { + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSignalSpy itemsMovedSpy(m_model, &KFileItemModel::itemsMoved); + QVERIFY(itemsMovedSpy.isValid()); + QCOMPARE(m_model->sortRole(), QByteArray("text")); - QStringList files; - files << "a.txt" << "b.jpg" << "c.txt"; - m_testDir->createFiles(files); + m_testDir->createFiles({"a.txt", "b.jpg", "c.txt"}); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); - QCOMPARE(itemsInModel(), QStringList() << "a.txt" << "b.jpg" << "c.txt"); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << "a.txt" + << "b.jpg" + << "c.txt"); // Simulate that KFileItemModelRolesUpdater determines the mime type. // Resorting the files by 'type' will only work immediately if their @@ -358,18 +378,21 @@ void KFileItemModelTest::testChangeSortRole() } // Now: sort by type. - QSignalSpy spyItemsMoved(m_model, SIGNAL(itemsMoved(KItemRange,QList))); m_model->setSortRole("type"); QCOMPARE(m_model->sortRole(), QByteArray("type")); - QVERIFY(!spyItemsMoved.isEmpty()); + QVERIFY(!itemsMovedSpy.isEmpty()); // The actual order of the files might depend on the translation of the // result of KFileItem::mimeComment() in the user's language. QStringList version1; - version1 << "b.jpg" << "a.txt" << "c.txt"; + version1 << "b.jpg" + << "a.txt" + << "c.txt"; QStringList version2; - version2 << "a.txt" << "c.txt" << "b.jpg"; + version2 << "a.txt" + << "c.txt" + << "b.jpg"; const bool ok1 = (itemsInModel() == version1); const bool ok2 = (itemsInModel() == version2); @@ -379,17 +402,22 @@ void KFileItemModelTest::testChangeSortRole() void KFileItemModelTest::testResortAfterChangingName() { + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSignalSpy itemsMovedSpy(m_model, &KFileItemModel::itemsMoved); + QVERIFY(itemsMovedSpy.isValid()); + // We sort by size in a directory where all files have the same size. // Therefore, the files are sorted by their names. m_model->setSortRole("size"); - QStringList files; - files << "a.txt" << "b.txt" << "c.txt"; - m_testDir->createFiles(files); + m_testDir->createFiles({"a.txt", "b.txt", "c.txt"}); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); - QCOMPARE(itemsInModel(), QStringList() << "a.txt" << "b.txt" << "c.txt"); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << "a.txt" + << "b.txt" + << "c.txt"); // We rename a.txt to d.txt. Even though the size has not changed at all, // the model must re-sort the items. @@ -397,25 +425,31 @@ void KFileItemModelTest::testResortAfterChangingName() data.insert("text", "d.txt"); m_model->setData(0, data); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsMoved(KItemRange,QList)), DefaultTimeout)); - QCOMPARE(itemsInModel(), QStringList() << "b.txt" << "c.txt" << "d.txt"); + QVERIFY(itemsMovedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << "b.txt" + << "c.txt" + << "d.txt"); // We rename d.txt back to a.txt using the dir lister's refreshItems() signal. const KFileItem fileItemD = m_model->fileItem(2); KFileItem fileItemA = fileItemD; - KUrl urlA = fileItemA.url(); - urlA.setFileName("a.txt"); + QUrl urlA = fileItemA.url().adjusted(QUrl::RemoveFilename); + urlA.setPath(urlA.path() + "a.txt"); fileItemA.setUrl(urlA); - m_model->slotRefreshItems(QList >() << qMakePair(fileItemD, fileItemA)); + m_model->slotRefreshItems({qMakePair(fileItemD, fileItemA)}); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsMoved(KItemRange,QList)), DefaultTimeout)); - QCOMPARE(itemsInModel(), QStringList() << "a.txt" << "b.txt" << "c.txt"); + QVERIFY(itemsMovedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << "a.txt" + << "b.txt" + << "c.txt"); } void KFileItemModelTest::testModelConsistencyWhenInsertingItems() { - //QSKIP("Temporary disabled", SkipSingle); + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); // KFileItemModel prevents that inserting a punch of items sequentially // results in an itemsInserted()-signal for each item. Instead internally @@ -425,19 +459,19 @@ void KFileItemModelTest::testModelConsistencyWhenInsertingItems() // the timeout to 1 millisecond. m_testDir->createFile("1"); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); QCOMPARE(m_model->count(), 1); // Insert 10 items for 20 times. After each insert operation the model consistency // is checked. QSet insertedItems; for (int i = 0; i < 20; ++i) { - QSignalSpy spy(m_model, SIGNAL(itemsInserted(KItemRangeList))); + itemsInsertedSpy.clear(); for (int j = 0; j < 10; ++j) { - int itemName = qrand(); + int itemName = QRandomGenerator::global()->generate(); while (insertedItems.contains(itemName)) { - itemName = qrand(); + itemName = QRandomGenerator::global()->generate(); } insertedItems.insert(itemName); @@ -445,8 +479,8 @@ void KFileItemModelTest::testModelConsistencyWhenInsertingItems() } m_model->m_dirLister->updateDirectory(m_testDir->url()); - if (spy.count() == 0) { - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + if (itemsInsertedSpy.isEmpty()) { + QVERIFY(itemsInsertedSpy.wait()); } QVERIFY(m_model->isConsistent()); @@ -457,18 +491,17 @@ void KFileItemModelTest::testModelConsistencyWhenInsertingItems() void KFileItemModelTest::testItemRangeConsistencyWhenInsertingItems() { - QStringList files; - files << "B" << "E" << "G"; - m_testDir->createFiles(files); + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + + m_testDir->createFiles({"B", "E", "G"}); // Due to inserting the 3 items one item-range with index == 0 and // count == 3 must be given - QSignalSpy spy1(m_model, SIGNAL(itemsInserted(KItemRangeList))); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); - QCOMPARE(spy1.count(), 1); - QList arguments = spy1.takeFirst(); + QCOMPARE(itemsInsertedSpy.count(), 1); + QList arguments = itemsInsertedSpy.takeFirst(); KItemRangeList itemRangeList = arguments.at(0).value(); QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 3)); @@ -484,42 +517,47 @@ void KFileItemModelTest::testItemRangeConsistencyWhenInsertingItems() // index: 1, count: 2 for B, C // index: 2, count: 1 for G - files.clear(); - files << "A" << "C" << "D" << "F"; - m_testDir->createFiles(files); + m_testDir->createFiles({"A", "C", "D", "F"}); - QSignalSpy spy2(m_model, SIGNAL(itemsInserted(KItemRangeList))); m_model->m_dirLister->updateDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); - QCOMPARE(spy2.count(), 1); - arguments = spy2.takeFirst(); + QCOMPARE(itemsInsertedSpy.count(), 1); + arguments = itemsInsertedSpy.takeFirst(); itemRangeList = arguments.at(0).value(); QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 1) << KItemRange(1, 2) << KItemRange(2, 1)); } void KFileItemModelTest::testExpandItems() { + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QVERIFY(itemsInsertedSpy.isValid()); + QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved); + QVERIFY(itemsRemovedSpy.isValid()); + QSignalSpy loadingCompletedSpy(m_model, &KFileItemModel::directoryLoadingCompleted); + QVERIFY(loadingCompletedSpy.isValid()); + // Test expanding subfolders in a folder with the items "a/", "a/a/", "a/a/1", "a/a-1/", "a/a-1/1". // Besides testing the basic item expansion functionality, the test makes sure that // KFileItemModel::expansionLevelsCompare(const KFileItem& a, const KFileItem& b) - // yields the correct result for "a/a/1" and "a/a-1/", whis is non-trivial because they share the + // yields the correct result for "a/a/1" and "a/a-1/", which is non-trivial because they share the // first three characters. QSet originalModelRoles = m_model->roles(); QSet modelRoles = originalModelRoles; - modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount"; + modelRoles << "isExpanded" + << "isExpandable" + << "expandedParentsCount"; m_model->setRoles(modelRoles); - QStringList files; - files << "a/a/1" << "a/a-1/1"; // missing folders are created automatically - m_testDir->createFiles(files); + m_testDir->createFiles({"a/a/1", "a/a-1/1"}); // Store the URLs of all folders in a set. - QSet allFolders; - allFolders << KUrl(m_testDir->name() + 'a') << KUrl(m_testDir->name() + "a/a") << KUrl(m_testDir->name() + "a/a-1"); + QSet allFolders; + allFolders << QUrl::fromLocalFile(m_testDir->path() + "/a") << QUrl::fromLocalFile(m_testDir->path() + "/a/a") + << QUrl::fromLocalFile(m_testDir->path() + "/a/a-1"); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); // So far, the model contains only "a/" QCOMPARE(m_model->count(), 1); @@ -527,17 +565,19 @@ void KFileItemModelTest::testExpandItems() QVERIFY(!m_model->isExpanded(0)); QVERIFY(m_model->expandedDirectories().empty()); - QSignalSpy spyInserted(m_model, SIGNAL(itemsInserted(KItemRangeList))); + QCOMPARE(itemsInsertedSpy.count(), 1); + KItemRangeList itemRangeList = itemsInsertedSpy.takeFirst().at(0).value(); + QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 1)); // 1 new item "a/" with index 0 // Expand the folder "a/" -> "a/a/" and "a/a-1/" become visible m_model->setExpanded(0, true); QVERIFY(m_model->isExpanded(0)); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/a/", "a/a-1/" - QCOMPARE(m_model->expandedDirectories(), QSet() << KUrl(m_testDir->name() + 'a')); + QCOMPARE(m_model->expandedDirectories(), QSet() << QUrl::fromLocalFile(m_testDir->path() + "/a")); - QCOMPARE(spyInserted.count(), 1); - KItemRangeList itemRangeList = spyInserted.takeFirst().at(0).value(); + QCOMPARE(itemsInsertedSpy.count(), 1); + itemRangeList = itemsInsertedSpy.takeFirst().at(0).value(); QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 2)); // 2 new items "a/a/" and "a/a-1/" with indices 1 and 2 QVERIFY(m_model->isExpandable(1)); @@ -548,12 +588,12 @@ void KFileItemModelTest::testExpandItems() // Expand the folder "a/a/" -> "a/a/1" becomes visible m_model->setExpanded(1, true); QVERIFY(m_model->isExpanded(1)); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); - QCOMPARE(m_model->count(), 4); // 4 items: "a/", "a/a/", "a/a/1", "a/a-1/" - QCOMPARE(m_model->expandedDirectories(), QSet() << KUrl(m_testDir->name() + 'a') << KUrl(m_testDir->name() + "a/a")); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(m_model->count(), 4); // 4 items: "a/", "a/a/", "a/a/1", "a/a-1/" + QCOMPARE(m_model->expandedDirectories(), QSet() << QUrl::fromLocalFile(m_testDir->path() + "/a") << QUrl::fromLocalFile(m_testDir->path() + "/a/a")); - QCOMPARE(spyInserted.count(), 1); - itemRangeList = spyInserted.takeFirst().at(0).value(); + QCOMPARE(itemsInsertedSpy.count(), 1); + itemRangeList = itemsInsertedSpy.takeFirst().at(0).value(); QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(2, 1)); // 1 new item "a/a/1" with index 2 QVERIFY(!m_model->isExpandable(2)); @@ -562,27 +602,25 @@ void KFileItemModelTest::testExpandItems() // Expand the folder "a/a-1/" -> "a/a-1/1" becomes visible m_model->setExpanded(3, true); QVERIFY(m_model->isExpanded(3)); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); - QCOMPARE(m_model->count(), 5); // 5 items: "a/", "a/a/", "a/a/1", "a/a-1/", "a/a-1/1" + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(m_model->count(), 5); // 5 items: "a/", "a/a/", "a/a/1", "a/a-1/", "a/a-1/1" QCOMPARE(m_model->expandedDirectories(), allFolders); - QCOMPARE(spyInserted.count(), 1); - itemRangeList = spyInserted.takeFirst().at(0).value(); + QCOMPARE(itemsInsertedSpy.count(), 1); + itemRangeList = itemsInsertedSpy.takeFirst().at(0).value(); QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(4, 1)); // 1 new item "a/a-1/1" with index 4 QVERIFY(!m_model->isExpandable(4)); QVERIFY(!m_model->isExpanded(4)); - QSignalSpy spyRemoved(m_model, SIGNAL(itemsRemoved(KItemRangeList))); - // Collapse the top-level folder -> all other items should disappear m_model->setExpanded(0, false); QVERIFY(!m_model->isExpanded(0)); QCOMPARE(m_model->count(), 1); - QVERIFY(!m_model->expandedDirectories().contains(KUrl(m_testDir->name() + 'a'))); // TODO: Make sure that child URLs are also removed + QVERIFY(!m_model->expandedDirectories().contains(QUrl::fromLocalFile(m_testDir->path() + "/a"))); // TODO: Make sure that child URLs are also removed - QCOMPARE(spyRemoved.count(), 1); - itemRangeList = spyRemoved.takeFirst().at(0).value(); + QCOMPARE(itemsRemovedSpy.count(), 1); + itemRangeList = itemsRemovedSpy.takeFirst().at(0).value(); QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 4)); // 4 items removed QVERIFY(m_model->isConsistent()); @@ -593,8 +631,8 @@ void KFileItemModelTest::testExpandItems() m_model->loadDirectory(m_testDir->url()); m_model->restoreExpandedDirectories(allFolders); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(directoryLoadingCompleted()), DefaultTimeout)); - QCOMPARE(m_model->count(), 5); // 5 items: "a/", "a/a/", "a/a/1", "a/a-1/", "a/a-1/1" + QVERIFY(loadingCompletedSpy.wait()); + QCOMPARE(m_model->count(), 5); // 5 items: "a/", "a/a/", "a/a/1", "a/a-1/", "a/a-1/1" QVERIFY(m_model->isExpanded(0)); QVERIFY(m_model->isExpanded(1)); QVERIFY(!m_model->isExpanded(2)); @@ -605,30 +643,34 @@ void KFileItemModelTest::testExpandItems() // Move to a sub folder, then call restoreExpandedFolders() *before* going back. // This is how DolphinView restores the expanded folders when navigating in history. - m_model->loadDirectory(KUrl(m_testDir->name() + "a/a/")); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(directoryLoadingCompleted()), DefaultTimeout)); - QCOMPARE(m_model->count(), 1); // 1 item: "1" + m_model->loadDirectory(QUrl::fromLocalFile(m_testDir->path() + "/a/a/")); + QVERIFY(loadingCompletedSpy.wait()); + QCOMPARE(m_model->count(), 1); // 1 item: "1" m_model->restoreExpandedDirectories(allFolders); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(directoryLoadingCompleted()), DefaultTimeout)); - QCOMPARE(m_model->count(), 5); // 5 items: "a/", "a/a/", "a/a/1", "a/a-1/", "a/a-1/1" + QVERIFY(loadingCompletedSpy.wait()); + QCOMPARE(m_model->count(), 5); // 5 items: "a/", "a/a/", "a/a/1", "a/a-1/", "a/a-1/1" QCOMPARE(m_model->expandedDirectories(), allFolders); // Remove all expanded items by changing the roles - spyRemoved.clear(); + itemsRemovedSpy.clear(); m_model->setRoles(originalModelRoles); QVERIFY(!m_model->isExpanded(0)); QCOMPARE(m_model->count(), 1); - QVERIFY(!m_model->expandedDirectories().contains(KUrl(m_testDir->name() + 'a'))); + QVERIFY(!m_model->expandedDirectories().contains(QUrl::fromLocalFile(m_testDir->path() + "/a"))); - QCOMPARE(spyRemoved.count(), 1); - itemRangeList = spyRemoved.takeFirst().at(0).value(); + QCOMPARE(itemsRemovedSpy.count(), 1); + itemRangeList = itemsRemovedSpy.takeFirst().at(0).value(); QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 4)); // 4 items removed QVERIFY(m_model->isConsistent()); } void KFileItemModelTest::testExpandParentItems() { + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSignalSpy loadingCompletedSpy(m_model, &KFileItemModel::directoryLoadingCompleted); + QVERIFY(loadingCompletedSpy.isValid()); + // Create a tree structure of folders: // a 1/ // a 1/b1/ @@ -638,23 +680,23 @@ void KFileItemModelTest::testExpandParentItems() // a2/b2/c2/ // a2/b2/c2/d2/ QSet modelRoles = m_model->roles(); - modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount"; + modelRoles << "isExpanded" + << "isExpandable" + << "expandedParentsCount"; m_model->setRoles(modelRoles); - QStringList files; - files << "a 1/b1/c1/file.txt" << "a2/b2/c2/d2/file.txt"; // missing folders are created automatically - m_testDir->createFiles(files); + m_testDir->createFiles({"a 1/b1/c1/file.txt", "a2/b2/c2/d2/file.txt"}); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); // So far, the model contains only "a 1/" and "a2/". QCOMPARE(m_model->count(), 2); QVERIFY(m_model->expandedDirectories().empty()); // Expand the parents of "a2/b2/c2". - m_model->expandParentDirectories(KUrl(m_testDir->name() + "a2/b2/c2")); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(directoryLoadingCompleted()), DefaultTimeout)); + m_model->expandParentDirectories(QUrl::fromLocalFile(m_testDir->path() + "a2/b2/c2")); + QVERIFY(loadingCompletedSpy.wait()); // The model should now contain "a 1/", "a2/", "a2/b2/", and "a2/b2/c2/". // It's important that only the parents of "a1/b1/c1" are expanded. @@ -665,8 +707,8 @@ void KFileItemModelTest::testExpandParentItems() QVERIFY(!m_model->isExpanded(3)); // Expand the parents of "a 1/b1". - m_model->expandParentDirectories(KUrl(m_testDir->name() + "a 1/b1")); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(directoryLoadingCompleted()), DefaultTimeout)); + m_model->expandParentDirectories(QUrl::fromLocalFile(m_testDir->path() + "a 1/b1")); + QVERIFY(loadingCompletedSpy.wait()); // The model should now contain "a 1/", "a 1/b1/", "a2/", "a2/b2", and "a2/b2/c2/". // It's important that only the parents of "a 1/b1/" and "a2/b2/c2/" are expanded. @@ -680,7 +722,7 @@ void KFileItemModelTest::testExpandParentItems() // Expand "a 1/b1/". m_model->setExpanded(1, true); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(directoryLoadingCompleted()), DefaultTimeout)); + QVERIFY(loadingCompletedSpy.wait()); QCOMPARE(m_model->count(), 6); QVERIFY(m_model->isExpanded(0)); QVERIFY(m_model->isExpanded(1)); @@ -708,38 +750,39 @@ void KFileItemModelTest::testExpandParentItems() */ void KFileItemModelTest::testMakeExpandedItemHidden() { + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved); + QSet modelRoles = m_model->roles(); - modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount"; + modelRoles << "isExpanded" + << "isExpandable" + << "expandedParentsCount"; m_model->setRoles(modelRoles); - QStringList files; - m_testDir->createFile("1a/2a/3a"); - m_testDir->createFile("1a/2a/3b"); - m_testDir->createFile("1a/2b"); - m_testDir->createFile("1b"); + m_testDir->createFiles({"1a/2a/3a", "1a/2a/3b", "1a/2b", "1b"}); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); // So far, the model contains only "1a/" and "1b". QCOMPARE(m_model->count(), 2); m_model->setExpanded(0, true); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); // Now "1a/2a" and "1a/2b" have appeared. QCOMPARE(m_model->count(), 4); m_model->setExpanded(1, true); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); QCOMPARE(m_model->count(), 6); // Rename "1a/2" and make it hidden. - const QString oldPath = m_model->fileItem(0).url().path() + "/2a"; - const QString newPath = m_model->fileItem(0).url().path() + "/.2a"; + const QUrl oldUrl = QUrl::fromLocalFile(m_model->fileItem(0).url().path() + "/2a"); + const QUrl newUrl = QUrl::fromLocalFile(m_model->fileItem(0).url().path() + "/.2a"); - KIO::SimpleJob* job = KIO::rename(oldPath, newPath, KIO::HideProgressInfo); + KIO::SimpleJob *job = KIO::rename(oldUrl, newUrl, KIO::HideProgressInfo); bool ok = job->exec(); QVERIFY(ok); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsRemoved(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsRemovedSpy.wait()); // "1a/2" and its subfolders have disappeared now. QVERIFY(m_model->isConsistent()); @@ -747,22 +790,23 @@ void KFileItemModelTest::testMakeExpandedItemHidden() m_model->setExpanded(0, false); QCOMPARE(m_model->count(), 2); - } void KFileItemModelTest::testRemoveFilteredExpandedItems() { + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSet originalModelRoles = m_model->roles(); QSet modelRoles = originalModelRoles; - modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount"; + modelRoles << "isExpanded" + << "isExpandable" + << "expandedParentsCount"; m_model->setRoles(modelRoles); - QStringList files; - files << "folder/child" << "file"; // missing folders are created automatically - m_testDir->createFiles(files); + m_testDir->createFiles({"folder/child", "file"}); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); // So far, the model contains only "folder/" and "file". QCOMPARE(m_model->count(), 2); @@ -770,17 +814,24 @@ void KFileItemModelTest::testRemoveFilteredExpandedItems() QVERIFY(!m_model->isExpandable(1)); QVERIFY(!m_model->isExpanded(0)); QVERIFY(!m_model->isExpanded(1)); - QCOMPARE(itemsInModel(), QStringList() << "folder" << "file"); + QCOMPARE(itemsInModel(), + QStringList() << "folder" + << "file"); // Expand "folder" -> "folder/child" becomes visible. m_model->setExpanded(0, true); QVERIFY(m_model->isExpanded(0)); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); - QCOMPARE(itemsInModel(), QStringList() << "folder" << "child" << "file"); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << "folder" + << "child" + << "file"); // Add a name filter. m_model->setNameFilter("f"); - QCOMPARE(itemsInModel(), QStringList() << "folder" << "file"); + QCOMPARE(itemsInModel(), + QStringList() << "folder" + << "file"); m_model->setNameFilter("fo"); QCOMPARE(itemsInModel(), QStringList() << "folder"); @@ -792,11 +843,132 @@ void KFileItemModelTest::testRemoveFilteredExpandedItems() // Remove the name filter and verify that "folder/child" does not reappear. m_model->setNameFilter(QString()); - QCOMPARE(itemsInModel(), QStringList() << "folder" << "file"); + QCOMPARE(itemsInModel(), + QStringList() << "folder" + << "file"); +} + +void KFileItemModelTest::testSizeSortingAfterRefresh() +{ + // testDir structure is as follows + // ./ + // ├─ a + // ├─ b + // ├─ c/ + // │ ├─ c-2/ + // │ │ ├─ c-3 + // │ ├─ c-1 + // ├─ d + // ├─ e + + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSignalSpy itemsMovedSpy(m_model, &KFileItemModel::itemsMoved); + QVERIFY(itemsMovedSpy.isValid()); + + // Create some files with different sizes and modification times to check the different sorting options + QDateTime now = QDateTime::currentDateTime(); + + QSet roles; + roles.insert("text"); + roles.insert("isExpanded"); + roles.insert("isExpandable"); + roles.insert("expandedParentsCount"); + m_model->setRoles(roles); + + m_testDir->createDir("c/c-2"); + m_testDir->createFile("c/c-2/c-3"); + m_testDir->createFile("c/c-1"); + + m_testDir->createFile("a", "A file", now.addDays(-3)); + m_testDir->createFile("b", "A larger file", now.addDays(0)); + m_testDir->createDir("c", now.addDays(-2)); + m_testDir->createFile("d", "The largest file in this directory", now.addDays(-1)); + m_testDir->createFile("e", "An even larger file", now.addDays(-4)); + + m_model->loadDirectory(m_testDir->url()); + QVERIFY(itemsInsertedSpy.wait()); + + int index = m_model->index(QUrl(m_testDir->url().url() + "/c")); + m_model->setExpanded(index, true); + QVERIFY(itemsInsertedSpy.wait()); + + index = m_model->index(QUrl(m_testDir->url().url() + "/c/c-2")); + m_model->setExpanded(index, true); + QVERIFY(itemsInsertedSpy.wait()); + + // Default: Sort by Name, ascending + QCOMPARE(m_model->sortRole(), QByteArray("text")); + QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder); + QCOMPARE(itemsInModel(), + QStringList() << "c" + << "c-2" + << "c-3" + << "c-1" + << "a" + << "b" + << "d" + << "e"); + + // Sort by Size, ascending, before refresh + m_model->setSortRole("size"); + QCOMPARE(m_model->sortRole(), QByteArray("size")); + QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder); + QCOMPARE(itemsInModel(), + QStringList() << "c" + << "c-2" + << "c-3" + << "c-1" + << "a" + << "b" + << "e" + << "d"); + + // Refresh directory + m_model->refreshDirectory(m_model->directory()); + QVERIFY(itemsInsertedSpy.wait()); + + // Expand folders again + index = m_model->index(QUrl(m_testDir->url().url() + "/c")); + m_model->setExpanded(index, true); + QVERIFY(itemsInsertedSpy.wait()); + + index = m_model->index(QUrl(m_testDir->url().url() + "/c/c-2")); + m_model->setExpanded(index, true); + QVERIFY(itemsInsertedSpy.wait()); + + // Sort by Size, ascending, after refresh + QCOMPARE(m_model->sortRole(), QByteArray("size")); + QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder); + QCOMPARE(itemsInModel(), + QStringList() << "c" + << "c-2" + << "c-3" + << "c-1" + << "a" + << "b" + << "e" + << "d"); } void KFileItemModelTest::testSorting() { + // testDir structure is as follows + // ./ + // ├─ .g/ + // ├─ a + // ├─ b + // ├─ c/ + // │ ├─ c-2/ + // │ │ ├─ c-3 + // │ ├─ c-1 + // ├─ .f + // ├─ d + // ├─ e + + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSignalSpy itemsMovedSpy(m_model, &KFileItemModel::itemsMoved); + QVERIFY(itemsMovedSpy.isValid()); + // Create some files with different sizes and modification times to check the different sorting options QDateTime now = QDateTime::currentDateTime(); @@ -817,125 +989,378 @@ void KFileItemModelTest::testSorting() m_testDir->createFile("d", "The largest file in this directory", now.addDays(-1)); m_testDir->createFile("e", "An even larger file", now.addDays(-4)); m_testDir->createFile(".f"); + m_testDir->createDir(".g"); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(itemsInsertedSpy.count(), 1); + KItemRangeList itemRangeList = itemsInsertedSpy.takeFirst().at(0).value(); + QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 5)); - int index = m_model->index(KUrl(m_testDir->url().url() + 'c')); + int index = m_model->index(QUrl(m_testDir->url().url() + "/c")); m_model->setExpanded(index, true); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(itemsInsertedSpy.count(), 1); + itemRangeList = itemsInsertedSpy.takeFirst().at(0).value(); + QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 2)); - index = m_model->index(KUrl(m_testDir->url().url() + "c/c-2")); + index = m_model->index(QUrl(m_testDir->url().url() + "/c/c-2")); m_model->setExpanded(index, true); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(itemsInsertedSpy.count(), 1); + itemRangeList = itemsInsertedSpy.takeFirst().at(0).value(); + QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(2, 1)); // Default: Sort by Name, ascending QCOMPARE(m_model->sortRole(), QByteArray("text")); QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder); QVERIFY(m_model->sortDirectoriesFirst()); QVERIFY(!m_model->showHiddenFiles()); - QCOMPARE(itemsInModel(), QStringList() << "c" << "c-2" << "c-3" << "c-1" << "a" << "b" << "d" << "e"); - - QSignalSpy spyItemsMoved(m_model, SIGNAL(itemsMoved(KItemRange,QList))); + QCOMPARE(itemsInModel(), + QStringList() << "c" + << "c-2" + << "c-3" + << "c-1" + << "a" + << "b" + << "d" + << "e"); // Sort by Name, ascending, 'Sort Folders First' disabled m_model->setSortDirectoriesFirst(false); QCOMPARE(m_model->sortRole(), QByteArray("text")); QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder); - QCOMPARE(itemsInModel(), QStringList() << "a" << "b" << "c" << "c-1" << "c-2" << "c-3" << "d" << "e"); - QCOMPARE(spyItemsMoved.count(), 1); - QCOMPARE(spyItemsMoved.first().at(0).value(), KItemRange(0, 6)); - QCOMPARE(spyItemsMoved.takeFirst().at(1).value >(), QList() << 2 << 4 << 5 << 3 << 0 << 1); + QCOMPARE(itemsInModel(), + QStringList() << "a" + << "b" + << "c" + << "c-1" + << "c-2" + << "c-3" + << "d" + << "e"); + QCOMPARE(itemsMovedSpy.count(), 1); + QCOMPARE(itemsMovedSpy.first().at(0).value(), KItemRange(0, 6)); + QCOMPARE(itemsMovedSpy.takeFirst().at(1).value>(), QList() << 2 << 4 << 5 << 3 << 0 << 1); // Sort by Name, descending m_model->setSortDirectoriesFirst(true); m_model->setSortOrder(Qt::DescendingOrder); QCOMPARE(m_model->sortRole(), QByteArray("text")); QCOMPARE(m_model->sortOrder(), Qt::DescendingOrder); - QCOMPARE(itemsInModel(), QStringList() << "c" << "c-2" << "c-3" << "c-1" << "e" << "d" << "b" << "a"); - QCOMPARE(spyItemsMoved.count(), 2); - QCOMPARE(spyItemsMoved.first().at(0).value(), KItemRange(0, 6)); - QCOMPARE(spyItemsMoved.takeFirst().at(1).value >(), QList() << 4 << 5 << 0 << 3 << 1 << 2); - QCOMPARE(spyItemsMoved.first().at(0).value(), KItemRange(4, 4)); - QCOMPARE(spyItemsMoved.takeFirst().at(1).value >(), QList() << 7 << 6 << 5 << 4); + QCOMPARE(itemsInModel(), + QStringList() << "c" + << "c-2" + << "c-3" + << "c-1" + << "e" + << "d" + << "b" + << "a"); + QCOMPARE(itemsMovedSpy.count(), 2); + QCOMPARE(itemsMovedSpy.first().at(0).value(), KItemRange(0, 6)); + QCOMPARE(itemsMovedSpy.takeFirst().at(1).value>(), QList() << 4 << 5 << 0 << 3 << 1 << 2); + QCOMPARE(itemsMovedSpy.first().at(0).value(), KItemRange(4, 4)); + QCOMPARE(itemsMovedSpy.takeFirst().at(1).value>(), QList() << 7 << 6 << 5 << 4); // Sort by Date, descending m_model->setSortDirectoriesFirst(true); - m_model->setSortRole("date"); - QCOMPARE(m_model->sortRole(), QByteArray("date")); + m_model->setSortRole("modificationtime"); + QCOMPARE(m_model->sortRole(), QByteArray("modificationtime")); QCOMPARE(m_model->sortOrder(), Qt::DescendingOrder); - QCOMPARE(itemsInModel(), QStringList() << "c" << "c-2" << "c-3" << "c-1" << "b" << "d" << "a" << "e"); - QCOMPARE(spyItemsMoved.count(), 1); - QCOMPARE(spyItemsMoved.first().at(0).value(), KItemRange(4, 4)); - QCOMPARE(spyItemsMoved.takeFirst().at(1).value >(), QList() << 7 << 5 << 4 << 6); + QCOMPARE(itemsInModel(), + QStringList() << "c" + << "c-2" + << "c-3" + << "c-1" + << "b" + << "d" + << "a" + << "e"); + QCOMPARE(itemsMovedSpy.count(), 1); + QCOMPARE(itemsMovedSpy.first().at(0).value(), KItemRange(4, 4)); + QCOMPARE(itemsMovedSpy.takeFirst().at(1).value>(), QList() << 7 << 5 << 4 << 6); // Sort by Date, ascending m_model->setSortOrder(Qt::AscendingOrder); - QCOMPARE(m_model->sortRole(), QByteArray("date")); + QCOMPARE(m_model->sortRole(), QByteArray("modificationtime")); QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder); - QCOMPARE(itemsInModel(), QStringList() << "c" << "c-2" << "c-3" << "c-1" << "e" << "a" << "d" << "b"); - QCOMPARE(spyItemsMoved.count(), 1); - QCOMPARE(spyItemsMoved.first().at(0).value(), KItemRange(4, 4)); - QCOMPARE(spyItemsMoved.takeFirst().at(1).value >(), QList() << 7 << 6 << 5 << 4); + QCOMPARE(itemsInModel(), + QStringList() << "c" + << "c-2" + << "c-3" + << "c-1" + << "e" + << "a" + << "d" + << "b"); + QCOMPARE(itemsMovedSpy.count(), 1); + QCOMPARE(itemsMovedSpy.first().at(0).value(), KItemRange(4, 4)); + QCOMPARE(itemsMovedSpy.takeFirst().at(1).value>(), QList() << 7 << 6 << 5 << 4); // Sort by Date, ascending, 'Sort Folders First' disabled m_model->setSortDirectoriesFirst(false); - QCOMPARE(m_model->sortRole(), QByteArray("date")); + QCOMPARE(m_model->sortRole(), QByteArray("modificationtime")); QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder); QVERIFY(!m_model->sortDirectoriesFirst()); - QCOMPARE(itemsInModel(), QStringList() << "e" << "a" << "c" << "c-1" << "c-2" << "c-3" << "d" << "b"); - QCOMPARE(spyItemsMoved.count(), 1); - QCOMPARE(spyItemsMoved.first().at(0).value(), KItemRange(0, 6)); - QCOMPARE(spyItemsMoved.takeFirst().at(1).value >(), QList() << 2 << 4 << 5 << 3 << 0 << 1); + QCOMPARE(itemsInModel(), + QStringList() << "e" + << "a" + << "c" + << "c-1" + << "c-2" + << "c-3" + << "d" + << "b"); + QCOMPARE(itemsMovedSpy.count(), 1); + QCOMPARE(itemsMovedSpy.first().at(0).value(), KItemRange(0, 6)); + QCOMPARE(itemsMovedSpy.takeFirst().at(1).value>(), QList() << 2 << 4 << 5 << 3 << 0 << 1); // Sort by Name, ascending, 'Sort Folders First' disabled m_model->setSortRole("text"); QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder); QVERIFY(!m_model->sortDirectoriesFirst()); - QCOMPARE(itemsInModel(), QStringList() << "a" << "b" << "c" << "c-1" << "c-2" << "c-3" << "d" << "e"); - QCOMPARE(spyItemsMoved.count(), 1); - QCOMPARE(spyItemsMoved.first().at(0).value(), KItemRange(0, 8)); - QCOMPARE(spyItemsMoved.takeFirst().at(1).value >(), QList() << 7 << 0 << 2 << 3 << 4 << 5 << 6 << 1); + QCOMPARE(itemsInModel(), + QStringList() << "a" + << "b" + << "c" + << "c-1" + << "c-2" + << "c-3" + << "d" + << "e"); + QCOMPARE(itemsMovedSpy.count(), 1); + QCOMPARE(itemsMovedSpy.first().at(0).value(), KItemRange(0, 8)); + QCOMPARE(itemsMovedSpy.takeFirst().at(1).value>(), QList() << 7 << 0 << 2 << 3 << 4 << 5 << 6 << 1); // Sort by Size, ascending, 'Sort Folders First' disabled m_model->setSortRole("size"); QCOMPARE(m_model->sortRole(), QByteArray("size")); QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder); QVERIFY(!m_model->sortDirectoriesFirst()); - QCOMPARE(itemsInModel(), QStringList() << "c" << "c-2" << "c-3" << "c-1" << "a" << "b" << "e" << "d"); - QCOMPARE(spyItemsMoved.count(), 1); - QCOMPARE(spyItemsMoved.first().at(0).value(), KItemRange(0, 8)); - QCOMPARE(spyItemsMoved.takeFirst().at(1).value >(), QList() << 4 << 5 << 0 << 3 << 1 << 2 << 7 << 6); + QCOMPARE(itemsInModel(), + QStringList() << "c" + << "c-2" + << "c-3" + << "c-1" + << "a" + << "b" + << "e" + << "d"); + QCOMPARE(itemsMovedSpy.count(), 1); + QCOMPARE(itemsMovedSpy.first().at(0).value(), KItemRange(0, 8)); + QCOMPARE(itemsMovedSpy.takeFirst().at(1).value>(), QList() << 4 << 5 << 0 << 3 << 1 << 2 << 7 << 6); // In 'Sort by Size' mode, folders are always first -> changing 'Sort Folders First' does not resort the model m_model->setSortDirectoriesFirst(true); QCOMPARE(m_model->sortRole(), QByteArray("size")); QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder); QVERIFY(m_model->sortDirectoriesFirst()); - QCOMPARE(itemsInModel(), QStringList() << "c" << "c-2" << "c-3" << "c-1" << "a" << "b" << "e" << "d"); - QCOMPARE(spyItemsMoved.count(), 0); + QCOMPARE(itemsInModel(), + QStringList() << "c" + << "c-2" + << "c-3" + << "c-1" + << "a" + << "b" + << "e" + << "d"); + QCOMPARE(itemsMovedSpy.count(), 0); // Sort by Size, descending, 'Sort Folders First' enabled m_model->setSortOrder(Qt::DescendingOrder); QCOMPARE(m_model->sortRole(), QByteArray("size")); QCOMPARE(m_model->sortOrder(), Qt::DescendingOrder); QVERIFY(m_model->sortDirectoriesFirst()); - QCOMPARE(itemsInModel(), QStringList() << "c" << "c-2" << "c-3" << "c-1" << "d" << "e" << "b" << "a"); - QCOMPARE(spyItemsMoved.count(), 1); - QCOMPARE(spyItemsMoved.first().at(0).value(), KItemRange(4, 4)); - QCOMPARE(spyItemsMoved.takeFirst().at(1).value >(), QList() << 7 << 6 << 5 << 4); + QCOMPARE(itemsInModel(), + QStringList() << "c" + << "c-2" + << "c-3" + << "c-1" + << "d" + << "e" + << "b" + << "a"); + QCOMPARE(itemsMovedSpy.count(), 1); + QCOMPARE(itemsMovedSpy.first().at(0).value(), KItemRange(4, 4)); + QCOMPARE(itemsMovedSpy.takeFirst().at(1).value>(), QList() << 7 << 6 << 5 << 4); + + // 'Show Hidden Files' enabled + m_model->setShowHiddenFiles(true); + QVERIFY(m_model->showHiddenFiles()); + QVERIFY(!m_model->sortHiddenLast()); + QCOMPARE(itemsInModel(), + QStringList() << "c" + << "c-2" + << "c-3" + << "c-1" + << ".g" + << "d" + << "e" + << "b" + << "a" + << ".f"); + QCOMPARE(itemsMovedSpy.count(), 0); + QCOMPARE(itemsInsertedSpy.count(), 1); + QCOMPARE(itemsInsertedSpy.takeFirst().at(0).value(), KItemRangeList() << KItemRange(4, 1) << KItemRange(8, 1)); + + // 'Sort Hidden Files Last' enabled + m_model->setSortHiddenLast(true); + QVERIFY(m_model->sortHiddenLast()); + QCOMPARE(itemsInModel(), + QStringList() << "c" + << "c-2" + << "c-3" + << "c-1" + << "d" + << "e" + << "b" + << "a" + << ".g" + << ".f"); + QCOMPARE(itemsMovedSpy.count(), 1); + QCOMPARE(itemsInsertedSpy.count(), 0); + QCOMPARE(itemsMovedSpy.first().at(0).value(), KItemRange(4, 5)); + QCOMPARE(itemsMovedSpy.takeFirst().at(1).value>(), QList() << 8 << 4 << 5 << 6 << 7); + + // Sort by Name + m_model->setSortRole("text"); + QCOMPARE(itemsInModel(), + QStringList() << "c" + << "c-2" + << "c-3" + << "c-1" + << "e" + << "d" + << "b" + << "a" + << ".g" + << ".f"); + QCOMPARE(itemsMovedSpy.count(), 1); + QCOMPARE(itemsMovedSpy.first().at(0).value(), KItemRange(4, 2)); + QCOMPARE(itemsMovedSpy.takeFirst().at(1).value>(), QList() << 5 << 4); + + // Sort ascending + m_model->setSortOrder(Qt::AscendingOrder); + QCOMPARE(itemsInModel(), + QStringList() << "c" + << "c-2" + << "c-3" + << "c-1" + << "a" + << "b" + << "d" + << "e" + << ".g" + << ".f"); + QCOMPARE(itemsMovedSpy.count(), 1); + QCOMPARE(itemsMovedSpy.first().at(0).value(), KItemRange(4, 4)); + QCOMPARE(itemsMovedSpy.takeFirst().at(1).value>(), QList() << 7 << 6 << 5 << 4); + + // 'Sort Folders First' disabled + m_model->setSortDirectoriesFirst(false); + QVERIFY(!m_model->sortDirectoriesFirst()); + QCOMPARE(itemsInModel(), + QStringList() << "a" + << "b" + << "c" + << "c-1" + << "c-2" + << "c-3" + << "d" + << "e" + << ".f" + << ".g"); + QCOMPARE(itemsMovedSpy.count(), 1); + QCOMPARE(itemsMovedSpy.first().at(0).value(), KItemRange(0, 10)); + QCOMPARE(itemsMovedSpy.takeFirst().at(1).value>(), QList() << 2 << 4 << 5 << 3 << 0 << 1 << 6 << 7 << 9 << 8); +} + +void KFileItemModelTest::testNaturalSorting() +{ + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSignalSpy itemsMovedSpy(m_model, &KFileItemModel::itemsMoved); + QVERIFY(itemsMovedSpy.isValid()); - // TODO: Sort by other roles; show/hide hidden files + m_model->setSortRole("text"); + m_model->setShowHiddenFiles(true); + + m_testDir->createFiles({".a", "a.txt", "b.txt", "a 1.txt", "a 2.txt", "a 10.txt", "a.tar", "b.tar"}); + + m_model->loadDirectory(m_testDir->url()); + QVERIFY(itemsInsertedSpy.wait()); + + // Sort by Name, ascending, natural sorting enabled + QCOMPARE(m_model->sortRole(), QByteArray("text")); + QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder); + QCOMPARE(itemsInModel(), + QStringList() << ".a" + << "a.tar" + << "a.txt" + << "a 1.txt" + << "a 2.txt" + << "a 10.txt" + << "b.tar" + << "b.txt"); + + // Sort by Extension + m_model->setSortRole("extension"); + QCOMPARE(m_model->sortRole(), QByteArray("extension")); + QCOMPARE(itemsInModel(), + QStringList() << ".a" + << "a.tar" + << "b.tar" + << "a.txt" + << "a 1.txt" + << "a 2.txt" + << "a 10.txt" + << "b.txt"); + QCOMPARE(itemsMovedSpy.count(), 1); + QCOMPARE(itemsMovedSpy.first().at(0).value(), KItemRange(2, 5)); + QCOMPARE(itemsMovedSpy.takeFirst().at(1).value>(), QList() << 3 << 4 << 5 << 6 << 2); + + // Disable natural sorting, refresh directory for the change to take effect + m_model->m_naturalSorting = false; + m_model->refreshDirectory(m_model->directory()); + QVERIFY(itemsInsertedSpy.wait()); + + // Sort by Extension, ascending, natural sorting disabled + QCOMPARE(m_model->sortRole(), QByteArray("extension")); + QCOMPARE(itemsInModel(), + QStringList() << ".a" + << "a.tar" + << "b.tar" + << "a 1.txt" + << "a 10.txt" + << "a 2.txt" + << "a.txt" + << "b.txt"); + + // Sort by Name + m_model->setSortRole("text"); + QCOMPARE(m_model->sortRole(), QByteArray("text")); + QCOMPARE(itemsInModel(), + QStringList() << ".a" + << "a 1.txt" + << "a 10.txt" + << "a 2.txt" + << "a.tar" + << "a.txt" + << "b.tar" + << "b.txt"); + QCOMPARE(itemsMovedSpy.count(), 1); + QCOMPARE(itemsMovedSpy.first().at(0).value(), KItemRange(1, 6)); + QCOMPARE(itemsMovedSpy.takeFirst().at(1).value>(), QList() << 4 << 6 << 1 << 2 << 3 << 5); } void KFileItemModelTest::testIndexForKeyboardSearch() { - QStringList files; - files << "a" << "aa" << "Image.jpg" << "Image.png" << "Text" << "Text1" << "Text2" << "Text11"; - m_testDir->createFiles(files); + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + + m_testDir->createFiles({"a", "aa", "Image.jpg", "Image.png", "Text", "Text1", "Text2", "Text11", "U", "Ü", "Üu", "Ž"}); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); // Search from index 0 QCOMPARE(m_model->indexForKeyboardSearch("a", 0), 0); @@ -949,6 +1374,9 @@ void KFileItemModelTest::testIndexForKeyboardSearch() QCOMPARE(m_model->indexForKeyboardSearch("text1", 0), 5); QCOMPARE(m_model->indexForKeyboardSearch("text2", 0), 6); QCOMPARE(m_model->indexForKeyboardSearch("text11", 0), 7); + QCOMPARE(m_model->indexForKeyboardSearch("u", 0), 8); + QCOMPARE(m_model->indexForKeyboardSearch("üu", 0), 10); + QCOMPARE(m_model->indexForKeyboardSearch("ž", 0), 11); // Start a search somewhere in the middle QCOMPARE(m_model->indexForKeyboardSearch("a", 1), 1); @@ -975,17 +1403,23 @@ void KFileItemModelTest::testIndexForKeyboardSearch() QCOMPARE(m_model->indexForKeyboardSearch("TexT", 5), 5); QCOMPARE(m_model->indexForKeyboardSearch("IMAGE", 4), 2); + // Test searches that match items with marks + QCOMPARE(m_model->indexForKeyboardSearch("u", 9), 9); + QCOMPARE(m_model->indexForKeyboardSearch("u", 10), 10); + QCOMPARE(m_model->indexForKeyboardSearch("uu", 0), 10); + QCOMPARE(m_model->indexForKeyboardSearch("z", 0), 11); + // TODO: Maybe we should also test keyboard searches in directories which are not sorted by Name? } void KFileItemModelTest::testNameFilter() { - QStringList files; - files << "A1" << "A2" << "Abc" << "Bcd" << "Cde"; - m_testDir->createFiles(files); + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + + m_testDir->createFiles({"A1", "A2", "Abc", "Bcd", "Cde"}); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); m_model->setNameFilter("A"); // Shows A1, A2 and Abc QCOMPARE(m_model->count(), 3); @@ -1020,10 +1454,10 @@ void KFileItemModelTest::testEmptyPath() roles.insert("expandedParentsCount"); m_model->setRoles(roles); - const KUrl emptyUrl; + const QUrl emptyUrl; QVERIFY(emptyUrl.path().isEmpty()); - const KUrl url("file:///test/"); + const QUrl url("file:///test/"); KFileItemList items; items << KFileItem(emptyUrl, QString(), KFileItem::Unknown) << KFileItem(url, QString(), KFileItem::Unknown); @@ -1037,33 +1471,270 @@ void KFileItemModelTest::testEmptyPath() */ void KFileItemModelTest::testRefreshExpandedItem() { + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSignalSpy itemsChangedSpy(m_model, &KFileItemModel::itemsChanged); + QVERIFY(itemsChangedSpy.isValid()); + QSet modelRoles = m_model->roles(); - modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount"; + modelRoles << "isExpanded" + << "isExpandable" + << "expandedParentsCount"; m_model->setRoles(modelRoles); - QStringList files; - files << "a/1" << "a/2" << "3" << "4"; - m_testDir->createFiles(files); + m_testDir->createFiles({"a/1", "a/2", "3", "4"}); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); QCOMPARE(m_model->count(), 3); // "a/", "3", "4" m_model->setExpanded(0, true); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); QCOMPARE(m_model->count(), 5); // "a/", "a/1", "a/2", "3", "4" QVERIFY(m_model->isExpanded(0)); - QSignalSpy spyItemsChanged(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet))); - const KFileItem item = m_model->fileItem(0); - m_model->slotRefreshItems(QList >() << qMakePair(item, item)); - QVERIFY(!spyItemsChanged.isEmpty()); + m_model->slotRefreshItems({qMakePair(item, item)}); + QVERIFY(!itemsChangedSpy.isEmpty()); QCOMPARE(m_model->count(), 5); // "a/", "a/1", "a/2", "3", "4" QVERIFY(m_model->isExpanded(0)); } +/** + * Verifies that adding an item to an expanded folder that's filtered makes the parental chain visible. + */ +void KFileItemModelTest::testAddItemToFilteredExpandedFolder() +{ + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSignalSpy fileItemsChangedSpy(m_model, &KFileItemModel::fileItemsChanged); + + QSet modelRoles = m_model->roles(); + modelRoles << "isExpanded" + << "isExpandable" + << "expandedParentsCount"; + m_model->setRoles(modelRoles); + + m_testDir->createFile("a/b/file"); + + m_model->loadDirectory(m_testDir->url()); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(m_model->count(), 1); // "a + + // Expand "a/". + m_model->setExpanded(0, true); + QVERIFY(itemsInsertedSpy.wait()); + + // Expand "a/b/". + m_model->setExpanded(1, true); + QVERIFY(itemsInsertedSpy.wait()); + + QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/b/", "a/b/file" + + const QUrl urlB = m_model->fileItem(1).url(); + + // Set a filter that matches ".txt" extension + m_model->setNameFilter("*.txt"); + QCOMPARE(m_model->count(), 0); // Everything got hidden since we don't have a .txt file yet + + m_model->slotItemsAdded(urlB, KFileItemList() << KFileItem(QUrl("a/b/newItem.txt"))); + m_model->slotCompleted(); + + // Entire parental chain should now be shown + QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/b/", "a/b/newItem.txt" + QCOMPARE(itemsInModel(), + QStringList() << "a" + << "b" + << "newItem.txt"); + + // Items should be indented in hierarchy + QCOMPARE(m_model->expandedParentsCount(0), 0); + QCOMPARE(m_model->expandedParentsCount(1), 1); + QCOMPARE(m_model->expandedParentsCount(2), 2); +} + +/** + * Verifies that deleting the last filter-passing child from expanded folders + * makes the parental chain hidden. + */ +void KFileItemModelTest::testDeleteItemsWithExpandedFolderWithFilter() +{ + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved); + + QSet modelRoles = m_model->roles(); + modelRoles << "isExpanded" + << "isExpandable" + << "expandedParentsCount"; + m_model->setRoles(modelRoles); + + m_testDir->createFile("a/b/file"); + + m_model->loadDirectory(m_testDir->url()); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(m_model->count(), 1); // "a + + // Expand "a/". + m_model->setExpanded(0, true); + QVERIFY(itemsInsertedSpy.wait()); + + // Expand "a/b/". + m_model->setExpanded(1, true); + QVERIFY(itemsInsertedSpy.wait()); + + QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/b/", "a/b/file" + + // Set a filter that matches "file" extension + m_model->setNameFilter("file"); + QCOMPARE(m_model->count(), 3); // Everything is still shown + + // Delete "file" + QCOMPARE(itemsRemovedSpy.count(), 0); + m_model->slotItemsDeleted(KFileItemList() << m_model->fileItem(2)); + QCOMPARE(itemsRemovedSpy.count(), 1); + + // Entire parental chain should now be filtered + QCOMPARE(m_model->count(), 0); + QCOMPARE(m_model->m_filteredItems.size(), 2); +} + +/** + * Verifies that the fileItemsChanged signal is raised with the correct index after renaming files with filter set. + * The rename operation will cause one item to be filtered out and another item to be reordered. + */ +void KFileItemModelTest::testRefreshItemsWithFilter() +{ + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved); + QSignalSpy itemsChangedSpy(m_model, &KFileItemModel::itemsChanged); + QSignalSpy itemsMovedSpy(m_model, &KFileItemModel::itemsMoved); + + // Creates three .txt files + m_testDir->createFiles({"b.txt", "c.txt", "d.txt"}); + + m_model->loadDirectory(m_testDir->url()); + QVERIFY(itemsInsertedSpy.wait()); + + QCOMPARE(m_model->count(), 3); // "b.txt", "c.txt", "d.txt" + + // Set a filter that matches ".txt" extension + m_model->setNameFilter("*.txt"); + QCOMPARE(m_model->count(), 3); // Still all items are shown + QCOMPARE(itemsInModel(), + QStringList() << "b.txt" + << "c.txt" + << "d.txt"); + + // Objects used to rename + const KFileItem fileItemC_txt = m_model->fileItem(1); + KFileItem fileItemC_cfg = fileItemC_txt; + fileItemC_cfg.setUrl(QUrl("c.cfg")); + + const KFileItem fileItemD_txt = m_model->fileItem(2); + KFileItem fileItemA_txt = fileItemD_txt; + fileItemA_txt.setUrl(QUrl("a.txt")); + + // Rename "c.txt" to "c.cfg"; and rename "d.txt" to "a.txt" + QCOMPARE(itemsRemovedSpy.count(), 0); + QCOMPARE(itemsChangedSpy.count(), 0); + m_model->slotRefreshItems({qMakePair(fileItemC_txt, fileItemC_cfg), qMakePair(fileItemD_txt, fileItemA_txt)}); + QCOMPARE(itemsRemovedSpy.count(), 1); + QCOMPARE(itemsChangedSpy.count(), 1); + QCOMPARE(m_model->count(), 2); // Only "a.txt" and "b.txt". "c.cfg" got filtered out + + QList arguments = itemsChangedSpy.takeLast(); + KItemRangeList itemRangeList = arguments.at(0).value(); + + // We started with the order "b.txt", "c.txt", "d.txt" + // "d.txt" started with index "2" + // "c.txt" got renamed and got filtered out + // "d.txt" index shifted from index "2" to "1" + // So we expect index "1" in this argument, meaning "d.txt" was renamed + QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 1)); + + // Re-sorting is done asynchronously: + QCOMPARE(itemsInModel(), + QStringList() << "b.txt" + << "a.txt"); // Files should still be in the incorrect order + QVERIFY(itemsMovedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << "a.txt" + << "b.txt"); // Files were re-sorted and should now be in the correct order +} + +/** + * Verifies that parental chains are hidden and shown as needed while their children get filtered/unfiltered due to renaming. + * Also verifies that the "isExpanded" and "expandedParentsCount" values are kept for expanded folders that get refreshed. + */ +void KFileItemModelTest::testRefreshExpandedFolderWithFilter() +{ + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved); + + QSet modelRoles = m_model->roles(); + modelRoles << "isExpanded" + << "isExpandable" + << "expandedParentsCount"; + m_model->setRoles(modelRoles); + + m_testDir->createFile("a/b/someFolder/someFile"); + + m_model->loadDirectory(m_testDir->url()); + QVERIFY(itemsInsertedSpy.wait()); + + QCOMPARE(m_model->count(), 1); // Only "a/" + + // Expand "a/". + m_model->setExpanded(0, true); + QVERIFY(itemsInsertedSpy.wait()); + + // Expand "a/b/". + m_model->setExpanded(1, true); + QVERIFY(itemsInsertedSpy.wait()); + + // Expand "a/b/someFolder/". + m_model->setExpanded(2, true); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(m_model->count(), 4); // 4 items: "a/", "a/b/", "a/b/someFolder", "a/b/someFolder/someFile" + + // Set a filter that matches the expanded folder "someFolder" + m_model->setNameFilter("someFolder"); + QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/b/", "a/b/someFolder" + + // Objects used to rename + const KFileItem fileItemA = m_model->fileItem(0); + KFileItem fileItemARenamed = fileItemA; + fileItemARenamed.setUrl(QUrl("a_renamed")); + + const KFileItem fileItemSomeFolder = m_model->fileItem(2); + KFileItem fileItemRenamedFolder = fileItemSomeFolder; + fileItemRenamedFolder.setUrl(QUrl("/a_renamed/b/renamedFolder")); + + // Rename "a" to "a_renamed" + // This way we test if the algorithm is sane as to NOT hide "a_renamed" since it will have visible children + m_model->slotRefreshItems({qMakePair(fileItemA, fileItemARenamed)}); + QCOMPARE(m_model->count(), 3); // Entire parental chain must still be shown + QCOMPARE(itemsInModel(), + QStringList() << "a_renamed" + << "b" + << "someFolder"); + + // Rename "a_renamed" back to "a"; and "someFolder" to "renamedFolder" + m_model->slotRefreshItems({qMakePair(fileItemARenamed, fileItemA), qMakePair(fileItemSomeFolder, fileItemRenamedFolder)}); + QCOMPARE(m_model->count(), 0); // Entire parental chain became hidden + + // Rename "renamedFolder" back to "someFolder". Filter is passing again + m_model->slotRefreshItems({qMakePair(fileItemRenamedFolder, fileItemSomeFolder)}); + QCOMPARE(m_model->count(), 3); // Entire parental chain is shown again + QCOMPARE(itemsInModel(), + QStringList() << "a" + << "b" + << "someFolder"); + + // slotRefreshItems() should preserve "isExpanded" and "expandedParentsCount" values explicitly in this case + QCOMPARE(m_model->m_itemData.at(2)->values.value("isExpanded").toBool(), true); + QCOMPARE(m_model->m_itemData.at(2)->values.value("expandedParentsCount"), 2); +} + /** * Verify that removing hidden files and folders from the model does not * result in a crash, see https://bugs.kde.org/show_bug.cgi?id=314046 @@ -1074,39 +1745,59 @@ void KFileItemModelTest::testRemoveHiddenItems() m_testDir->createDir(".b"); m_testDir->createDir("c"); m_testDir->createDir("d"); - m_testDir->createFiles(QStringList() << ".f" << ".g" << "h" << "i"); + m_testDir->createFiles({".f", ".g", "h", "i"}); - QSignalSpy spyItemsInserted(m_model, SIGNAL(itemsInserted(KItemRangeList))); - QSignalSpy spyItemsRemoved(m_model, SIGNAL(itemsRemoved(KItemRangeList))); + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved); m_model->setShowHiddenFiles(true); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); - QCOMPARE(itemsInModel(), QStringList() << ".a" << ".b" << "c" << "d" <<".f" << ".g" << "h" << "i"); - QCOMPARE(spyItemsInserted.count(), 1); - QCOMPARE(spyItemsRemoved.count(), 0); - KItemRangeList itemRangeList = spyItemsInserted.takeFirst().at(0).value(); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << ".a" + << ".b" + << "c" + << "d" + << ".f" + << ".g" + << "h" + << "i"); + QCOMPARE(itemsInsertedSpy.count(), 1); + QCOMPARE(itemsRemovedSpy.count(), 0); + KItemRangeList itemRangeList = itemsInsertedSpy.takeFirst().at(0).value(); QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 8)); m_model->setShowHiddenFiles(false); - QCOMPARE(itemsInModel(), QStringList() << "c" << "d" << "h" << "i"); - QCOMPARE(spyItemsInserted.count(), 0); - QCOMPARE(spyItemsRemoved.count(), 1); - itemRangeList = spyItemsRemoved.takeFirst().at(0).value(); + QCOMPARE(itemsInModel(), + QStringList() << "c" + << "d" + << "h" + << "i"); + QCOMPARE(itemsInsertedSpy.count(), 0); + QCOMPARE(itemsRemovedSpy.count(), 1); + itemRangeList = itemsRemovedSpy.takeFirst().at(0).value(); QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 2) << KItemRange(4, 2)); m_model->setShowHiddenFiles(true); - QCOMPARE(itemsInModel(), QStringList() << ".a" << ".b" << "c" << "d" <<".f" << ".g" << "h" << "i"); - QCOMPARE(spyItemsInserted.count(), 1); - QCOMPARE(spyItemsRemoved.count(), 0); - itemRangeList = spyItemsInserted.takeFirst().at(0).value(); + QCOMPARE(itemsInModel(), + QStringList() << ".a" + << ".b" + << "c" + << "d" + << ".f" + << ".g" + << "h" + << "i"); + QCOMPARE(itemsInsertedSpy.count(), 1); + QCOMPARE(itemsRemovedSpy.count(), 0); + itemRangeList = itemsInsertedSpy.takeFirst().at(0).value(); QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 2) << KItemRange(2, 2)); m_model->clear(); QCOMPARE(itemsInModel(), QStringList()); - QCOMPARE(spyItemsInserted.count(), 0); - QCOMPARE(spyItemsRemoved.count(), 1); - itemRangeList = spyItemsRemoved.takeFirst().at(0).value(); + QCOMPARE(itemsInsertedSpy.count(), 0); + QCOMPARE(itemsRemovedSpy.count(), 1); + itemRangeList = itemsRemovedSpy.takeFirst().at(0).value(); QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 8)); // Hiding hidden files makes the dir lister emit its itemsDeleted signal. @@ -1119,48 +1810,58 @@ void KFileItemModelTest::testRemoveHiddenItems() */ void KFileItemModelTest::collapseParentOfHiddenItems() { + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved); + QSet modelRoles = m_model->roles(); - modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount"; + modelRoles << "isExpanded" + << "isExpandable" + << "expandedParentsCount"; m_model->setRoles(modelRoles); - QStringList files; - files << "a/1" << "a/b/1" << "a/b/c/1" << "a/b/c/d/1"; - m_testDir->createFiles(files); + m_testDir->createFiles({"a/1", "a/b/1", "a/b/c/1", "a/b/c/d/1"}); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); QCOMPARE(m_model->count(), 1); // Only "a/" // Expand "a/". m_model->setExpanded(0, true); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/b/", "a/1" // Expand "a/b/". m_model->setExpanded(1, true); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); QCOMPARE(m_model->count(), 5); // 5 items: "a/", "a/b/", "a/b/c", "a/b/1", "a/1" // Expand "a/b/c/". m_model->setExpanded(2, true); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); 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" - // Set a name filter that matches nothing -> only the expanded folders remain. + // Set a name filter that matches nothing -> nothing should remain. m_model->setNameFilter("xyz"); - QCOMPARE(m_model->count(), 3); - QCOMPARE(itemsInModel(), QStringList() << "a" << "b" << "c"); + QCOMPARE(itemsRemovedSpy.count(), 1); + QCOMPARE(m_model->count(), 0); // Everything is hidden + QCOMPARE(itemsInModel(), QStringList()); + + // Filter by the file names. Folder "d" will be hidden since it was collapsed + m_model->setNameFilter("1"); + QCOMPARE(itemsRemovedSpy.count(), 1); // nothing was removed, itemsRemovedSpy count will remain the same: + QCOMPARE(m_model->count(), 6); // 6 items: "a/", "a/b/", "a/b/c", "a/b/c/1", "a/b/1", "a/1" // Collapse the folder "a/". - QSignalSpy spyItemsRemoved(m_model, SIGNAL(itemsRemoved(KItemRangeList))); m_model->setExpanded(0, false); - QCOMPARE(spyItemsRemoved.count(), 1); + QCOMPARE(itemsRemovedSpy.count(), 2); QCOMPARE(m_model->count(), 1); QCOMPARE(itemsInModel(), QStringList() << "a"); - // Remove the filter -> no files should appear (and we should not get a crash). + // Remove the filter -> "a" should still appear (and we should not get a crash). m_model->setNameFilter(QString()); + QCOMPARE(itemsRemovedSpy.count(), 2); // nothing was removed, itemsRemovedSpy count will remain the same: QCOMPARE(m_model->count(), 1); + QCOMPARE(itemsInModel(), QStringList() << "a"); } /** @@ -1168,49 +1869,62 @@ void KFileItemModelTest::collapseParentOfHiddenItems() */ void KFileItemModelTest::removeParentOfHiddenItems() { + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved); + QSet modelRoles = m_model->roles(); - modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount"; + modelRoles << "isExpanded" + << "isExpandable" + << "expandedParentsCount"; m_model->setRoles(modelRoles); - QStringList files; - files << "a/1" << "a/b/1" << "a/b/c/1" << "a/b/c/d/1"; - m_testDir->createFiles(files); + m_testDir->createFiles({"a/1", "a/b/1", "a/b/c/1", "a/b/c/d/1"}); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); QCOMPARE(m_model->count(), 1); // Only "a/" // Expand "a/". m_model->setExpanded(0, true); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/b/", "a/1" // Expand "a/b/". m_model->setExpanded(1, true); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); QCOMPARE(m_model->count(), 5); // 5 items: "a/", "a/b/", "a/b/c", "a/b/1", "a/1" // Expand "a/b/c/". m_model->setExpanded(2, true); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); 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" - // Set a name filter that matches nothing -> only the expanded folders remain. + // Set a name filter that matches nothing -> nothing should remain. m_model->setNameFilter("xyz"); + QCOMPARE(itemsRemovedSpy.count(), 1); + QCOMPARE(m_model->count(), 0); + QCOMPARE(itemsInModel(), QStringList()); + + // Filter by "c". Folder "b" will also be shown because it is its parent. + m_model->setNameFilter("c"); + QCOMPARE(itemsRemovedSpy.count(), 1); // nothing was removed, itemsRemovedSpy count will remain the same: QCOMPARE(m_model->count(), 3); - QCOMPARE(itemsInModel(), QStringList() << "a" << "b" << "c"); + QCOMPARE(itemsInModel(), + QStringList() << "a" + << "b" + << "c"); // Simulate the deletion of the directory "a/b/". - QSignalSpy spyItemsRemoved(m_model, SIGNAL(itemsRemoved(KItemRangeList))); m_model->slotItemsDeleted(KFileItemList() << m_model->fileItem(1)); - QCOMPARE(spyItemsRemoved.count(), 1); - QCOMPARE(m_model->count(), 1); - QCOMPARE(itemsInModel(), QStringList() << "a"); + QCOMPARE(itemsRemovedSpy.count(), 2); + QCOMPARE(m_model->count(), 0); // "a" will be filtered out since it doesn't pass the filter and doesn't have visible children // Remove the filter -> only the file "a/1" should appear. m_model->setNameFilter(QString()); QCOMPARE(m_model->count(), 2); - QCOMPARE(itemsInModel(), QStringList() << "a" << "1"); + QCOMPARE(itemsInModel(), + QStringList() << "a" + << "1"); } /** @@ -1220,73 +1934,137 @@ void KFileItemModelTest::removeParentOfHiddenItems() */ void KFileItemModelTest::testGeneralParentChildRelationships() { + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved); + QSet modelRoles = m_model->roles(); - modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount"; + modelRoles << "isExpanded" + << "isExpandable" + << "expandedParentsCount"; m_model->setRoles(modelRoles); - QStringList files; - files << "parent1/realChild1/realGrandChild1" << "parent2/realChild2/realGrandChild2"; - m_testDir->createFiles(files); + m_testDir->createFiles({"parent1/realChild1/realGrandChild1", "parent2/realChild2/realGrandChild2"}); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); - QCOMPARE(itemsInModel(), QStringList() << "parent1" << "parent2"); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << "parent1" + << "parent2"); // Expand all folders. m_model->setExpanded(0, true); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); - QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "parent2"); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << "parent1" + << "realChild1" + << "parent2"); m_model->setExpanded(1, true); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); - QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "realGrandChild1" << "parent2"); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << "parent1" + << "realChild1" + << "realGrandChild1" + << "parent2"); m_model->setExpanded(3, true); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); - QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "realGrandChild1" << "parent2" << "realChild2"); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << "parent1" + << "realChild1" + << "realGrandChild1" + << "parent2" + << "realChild2"); m_model->setExpanded(4, true); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); - QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "realGrandChild1" << "parent2" << "realChild2" << "realGrandChild2"); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << "parent1" + << "realChild1" + << "realGrandChild1" + << "parent2" + << "realChild2" + << "realGrandChild2"); // Add some more children and grand-children. - const KUrl parent1 = m_model->fileItem(0).url(); - const KUrl parent2 = m_model->fileItem(3).url(); - const KUrl realChild1 = m_model->fileItem(1).url(); - const KUrl realChild2 = m_model->fileItem(4).url(); + const QUrl parent1 = m_model->fileItem(0).url(); + const QUrl parent2 = m_model->fileItem(3).url(); + const QUrl realChild1 = m_model->fileItem(1).url(); + const QUrl realChild2 = m_model->fileItem(4).url(); - m_model->slotItemsAdded(parent1, KFileItemList() << KFileItem(KUrl("child1"), QString(), KFileItem::Unknown)); + m_model->slotItemsAdded(parent1, KFileItemList() << KFileItem(QUrl("child1"), QString(), KFileItem::Unknown)); m_model->slotCompleted(); - QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "realGrandChild1" << "child1" << "parent2" << "realChild2" << "realGrandChild2"); - - m_model->slotItemsAdded(parent2, KFileItemList() << KFileItem(KUrl("child2"), QString(), KFileItem::Unknown)); + QCOMPARE(itemsInModel(), + QStringList() << "parent1" + << "realChild1" + << "realGrandChild1" + << "child1" + << "parent2" + << "realChild2" + << "realGrandChild2"); + + m_model->slotItemsAdded(parent2, KFileItemList() << KFileItem(QUrl("child2"), QString(), KFileItem::Unknown)); m_model->slotCompleted(); - QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "realGrandChild1" << "child1" << "parent2" << "realChild2" << "realGrandChild2" << "child2"); - - m_model->slotItemsAdded(realChild1, KFileItemList() << KFileItem(KUrl("grandChild1"), QString(), KFileItem::Unknown)); - m_model->slotCompleted(); - QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "grandChild1" << "realGrandChild1" << "child1" << "parent2" << "realChild2" << "realGrandChild2" << "child2"); - - m_model->slotItemsAdded(realChild1, KFileItemList() << KFileItem(KUrl("grandChild1"), QString(), KFileItem::Unknown)); + QCOMPARE(itemsInModel(), + QStringList() << "parent1" + << "realChild1" + << "realGrandChild1" + << "child1" + << "parent2" + << "realChild2" + << "realGrandChild2" + << "child2"); + + m_model->slotItemsAdded(realChild1, KFileItemList() << KFileItem(QUrl("grandChild1"), QString(), KFileItem::Unknown)); m_model->slotCompleted(); - QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "grandChild1" << "realGrandChild1" << "child1" << "parent2" << "realChild2" << "realGrandChild2" << "child2"); - - m_model->slotItemsAdded(realChild2, KFileItemList() << KFileItem(KUrl("grandChild2"), QString(), KFileItem::Unknown)); + QCOMPARE(itemsInModel(), + QStringList() << "parent1" + << "realChild1" + << "grandChild1" + << "realGrandChild1" + << "child1" + << "parent2" + << "realChild2" + << "realGrandChild2" + << "child2"); + + m_model->slotItemsAdded(realChild2, KFileItemList() << KFileItem(QUrl("grandChild2"), QString(), KFileItem::Unknown)); m_model->slotCompleted(); - QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "grandChild1" << "realGrandChild1" << "child1" << "parent2" << "realChild2" << "grandChild2" << "realGrandChild2" << "child2"); - - // Set a name filter that matches nothing -> only expanded folders remain. - QSignalSpy itemsRemovedSpy(m_model, SIGNAL(itemsRemoved(KItemRangeList))); + QCOMPARE(itemsInModel(), + QStringList() << "parent1" + << "realChild1" + << "grandChild1" + << "realGrandChild1" + << "child1" + << "parent2" + << "realChild2" + << "grandChild2" + << "realGrandChild2" + << "child2"); + + // Set a name filter that matches nothing -> nothing will remain. m_model->setNameFilter("xyz"); - QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "parent2" << "realChild2"); + QCOMPARE(itemsInModel(), QStringList()); QCOMPARE(itemsRemovedSpy.count(), 1); QList arguments = itemsRemovedSpy.takeFirst(); KItemRangeList itemRangeList = arguments.at(0).value(); - QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(2, 3) << KItemRange(7, 3)); + QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 10)); + + // Set a name filter that matches only "realChild". Their prarents should still show. + m_model->setNameFilter("realChild"); + QCOMPARE(itemsInModel(), + QStringList() << "parent1" + << "realChild1" + << "parent2" + << "realChild2"); + QCOMPARE(itemsRemovedSpy.count(), 0); // nothing was removed, itemsRemovedSpy will not be called this time // Collapse "parent1". m_model->setExpanded(0, false); - QCOMPARE(itemsInModel(), QStringList() << "parent1" << "parent2" << "realChild2"); + QCOMPARE(itemsInModel(), + QStringList() << "parent1" + << "parent2" + << "realChild2"); QCOMPARE(itemsRemovedSpy.count(), 1); arguments = itemsRemovedSpy.takeFirst(); itemRangeList = arguments.at(0).value(); @@ -1307,17 +2085,25 @@ void KFileItemModelTest::testGeneralParentChildRelationships() void KFileItemModelTest::testNameRoleGroups() { - QStringList files; - files << "b.txt" << "c.txt" << "d.txt" << "e.txt"; + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSignalSpy itemsMovedSpy(m_model, &KFileItemModel::itemsMoved); + QVERIFY(itemsMovedSpy.isValid()); + QSignalSpy groupsChangedSpy(m_model, &KFileItemModel::groupsChanged); + QVERIFY(groupsChangedSpy.isValid()); - m_testDir->createFiles(files); + m_testDir->createFiles({"b.txt", "c.txt", "d.txt", "e.txt"}); m_model->setGroupedSorting(true); + m_model->setGroupRole("text"); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); - QCOMPARE(itemsInModel(), QStringList() << "b.txt" << "c.txt" << "d.txt" << "e.txt"); - - QList > expectedGroups; + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << "b.txt" + << "c.txt" + << "d.txt" + << "e.txt"); + + QList> expectedGroups; expectedGroups << QPair(0, QLatin1String("B")); expectedGroups << QPair(1, QLatin1String("C")); expectedGroups << QPair(2, QLatin1String("D")); @@ -1328,8 +2114,12 @@ void KFileItemModelTest::testNameRoleGroups() QHash data; data.insert("text", "a.txt"); m_model->setData(2, data); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsMoved(KItemRange,QList)), DefaultTimeout)); - QCOMPARE(itemsInModel(), QStringList() << "a.txt" << "b.txt" << "c.txt" << "e.txt"); + QVERIFY(itemsMovedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << "a.txt" + << "b.txt" + << "c.txt" + << "e.txt"); expectedGroups.clear(); expectedGroups << QPair(0, QLatin1String("A")); @@ -1341,8 +2131,12 @@ void KFileItemModelTest::testNameRoleGroups() // Rename c.txt to d.txt. data.insert("text", "d.txt"); m_model->setData(2, data); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(groupsChanged()), DefaultTimeout)); - QCOMPARE(itemsInModel(), QStringList() << "a.txt" << "b.txt" << "d.txt" << "e.txt"); + QVERIFY(groupsChangedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << "a.txt" + << "b.txt" + << "d.txt" + << "e.txt"); expectedGroups.clear(); expectedGroups << QPair(0, QLatin1String("A")); @@ -1354,13 +2148,17 @@ void KFileItemModelTest::testNameRoleGroups() // Change d.txt back to c.txt, but this time using the dir lister's refreshItems() signal. const KFileItem fileItemD = m_model->fileItem(2); KFileItem fileItemC = fileItemD; - KUrl urlC = fileItemC.url(); - urlC.setFileName("c.txt"); + QUrl urlC = fileItemC.url().adjusted(QUrl::RemoveFilename); + urlC.setPath(urlC.path() + "c.txt"); fileItemC.setUrl(urlC); - m_model->slotRefreshItems(QList >() << qMakePair(fileItemD, fileItemC)); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(groupsChanged()), DefaultTimeout)); - QCOMPARE(itemsInModel(), QStringList() << "a.txt" << "b.txt" << "c.txt" << "e.txt"); + m_model->slotRefreshItems({qMakePair(fileItemD, fileItemC)}); + QVERIFY(groupsChangedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << "a.txt" + << "b.txt" + << "c.txt" + << "e.txt"); expectedGroups.clear(); expectedGroups << QPair(0, QLatin1String("A")); @@ -1372,23 +2170,29 @@ void KFileItemModelTest::testNameRoleGroups() void KFileItemModelTest::testNameRoleGroupsWithExpandedItems() { + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSet modelRoles = m_model->roles(); - modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount"; + modelRoles << "isExpanded" + << "isExpandable" + << "expandedParentsCount"; m_model->setRoles(modelRoles); - QStringList files; - files << "a/b.txt" << "a/c.txt" << "d/e.txt" << "d/f.txt"; - - m_testDir->createFiles(files); + m_testDir->createFiles({"a/b.txt", "a/c.txt", "d/e.txt", "d/f.txt"}); m_model->setGroupedSorting(true); + m_model->setGroupRole("text"); + m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); - QCOMPARE(itemsInModel(), QStringList() << "a" << "d"); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << "a" + << "d"); - QList > expectedGroups; + QList> expectedGroups; expectedGroups << QPair(0, QLatin1String("A")); expectedGroups << QPair(1, QLatin1String("D")); + QCOMPARE(m_model->groups(), expectedGroups); // Verify that expanding "a" and "d" will not change the groups (except for the index of "D"). @@ -1398,42 +2202,59 @@ void KFileItemModelTest::testNameRoleGroupsWithExpandedItems() m_model->setExpanded(0, true); QVERIFY(m_model->isExpanded(0)); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); - QCOMPARE(itemsInModel(), QStringList() << "a" << "b.txt" << "c.txt" << "d"); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << "a" + << "b.txt" + << "c.txt" + << "d"); QCOMPARE(m_model->groups(), expectedGroups); m_model->setExpanded(3, true); QVERIFY(m_model->isExpanded(3)); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); - QCOMPARE(itemsInModel(), QStringList() << "a" << "b.txt" << "c.txt" << "d" << "e.txt" << "f.txt"); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << "a" + << "b.txt" + << "c.txt" + << "d" + << "e.txt" + << "f.txt"); QCOMPARE(m_model->groups(), expectedGroups); } void KFileItemModelTest::testInconsistentModel() { + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSet modelRoles = m_model->roles(); - modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount"; + modelRoles << "isExpanded" + << "isExpandable" + << "expandedParentsCount"; m_model->setRoles(modelRoles); - QStringList files; - files << "a/b/c1.txt" << "a/b/c2.txt"; - - m_testDir->createFiles(files); + m_testDir->createFiles({"a/b/c1.txt", "a/b/c2.txt"}); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); QCOMPARE(itemsInModel(), QStringList() << "a"); // Expand "a/" and "a/b/". m_model->setExpanded(0, true); QVERIFY(m_model->isExpanded(0)); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); - QCOMPARE(itemsInModel(), QStringList() << "a" << "b"); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << "a" + << "b"); m_model->setExpanded(1, true); QVERIFY(m_model->isExpanded(1)); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); - QCOMPARE(itemsInModel(), QStringList() << "a" << "b" << "c1.txt" << "c2.txt"); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << "a" + << "b" + << "c1.txt" + << "c2.txt"); // Add the files "c1.txt" and "c2.txt" to the model also as top-level items. // Such a thing can in principle happen when performing a search, and there @@ -1444,41 +2265,54 @@ void KFileItemModelTest::testInconsistentModel() // Note that the first item in the list of added items must be new (i.e., not // in the model yet). Otherwise, KFileItemModel::slotItemsAdded() will see that // it receives items that are in the model already and ignore them. - KUrl url(m_model->directory().url() + "/a2"); - KFileItem newItem(KFileItem::Unknown, KFileItem::Unknown, url); + QUrl url(m_model->directory().url() + "/a2"); + KFileItem newItem(url); KFileItemList items; items << newItem << m_model->fileItem(2) << m_model->fileItem(3); m_model->slotItemsAdded(m_model->directory(), items); m_model->slotCompleted(); - QCOMPARE(itemsInModel(), QStringList() << "a" << "b" << "c1.txt" << "c2.txt" << "a2" << "c1.txt" << "c2.txt"); + QCOMPARE(itemsInModel(), + QStringList() << "a" + << "b" + << "c1.txt" + << "c2.txt" + << "a2" + << "c1.txt" + << "c2.txt"); m_model->setExpanded(0, false); // Test that the right items have been removed, see // https://bugs.kde.org/show_bug.cgi?id=324371 - QCOMPARE(itemsInModel(), QStringList() << "a" << "a2" << "c1.txt" << "c2.txt"); + QCOMPARE(itemsInModel(), + QStringList() << "a" + << "a2" + << "c1.txt" + << "c2.txt"); // Test that resorting does not cause a crash, see // https://bugs.kde.org/show_bug.cgi?id=325359 // The crash is not 100% reproducible, but Valgrind will report an invalid memory access. m_model->resortAllItems(); - } void KFileItemModelTest::testChangeRolesForFilteredItems() { + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSet modelRoles = m_model->roles(); modelRoles << "owner"; m_model->setRoles(modelRoles); - QStringList files; - files << "a.txt" << "aa.txt" << "aaa.txt"; - m_testDir->createFiles(files); + m_testDir->createFiles({"a.txt", "aa.txt", "aaa.txt"}); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); - QCOMPARE(itemsInModel(), QStringList() << "a.txt" << "aa.txt" << "aaa.txt"); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << "a.txt" + << "aa.txt" + << "aaa.txt"); for (int index = 0; index < m_model->count(); ++index) { // All items should have the "text" and "owner" roles, but not "group". @@ -1497,7 +2331,9 @@ void KFileItemModelTest::testChangeRolesForFilteredItems() // Modify the filter, such that "aa.txt" reappears, and verify that all items have the expected roles. m_model->setNameFilter("aa"); - QCOMPARE(itemsInModel(), QStringList() << "aa.txt" << "aaa.txt"); + QCOMPARE(itemsInModel(), + QStringList() << "aa.txt" + << "aaa.txt"); for (int index = 0; index < m_model->count(); ++index) { // All items should have the "text", "owner", and "group" roles. @@ -1512,7 +2348,10 @@ void KFileItemModelTest::testChangeRolesForFilteredItems() // Clear the filter, and verify that all items have the expected roles m_model->setNameFilter(QString()); - QCOMPARE(itemsInModel(), QStringList() << "a.txt" << "aa.txt" << "aaa.txt"); + QCOMPARE(itemsInModel(), + QStringList() << "a.txt" + << "aa.txt" + << "aaa.txt"); for (int index = 0; index < m_model->count(); ++index) { // All items should have the "text" and "group" roles, but now "owner". @@ -1526,30 +2365,34 @@ void KFileItemModelTest::testChangeSortRoleWhileFiltering() { KFileItemList items; - KIO::UDSEntry entry; - entry.insert(KIO::UDSEntry::UDS_FILE_TYPE, 0100000); // S_IFREG might not be defined on non-Unix platforms. - entry.insert(KIO::UDSEntry::UDS_ACCESS, 07777); - entry.insert(KIO::UDSEntry::UDS_SIZE, 0); - entry.insert(KIO::UDSEntry::UDS_MODIFICATION_TIME, 0); - entry.insert(KIO::UDSEntry::UDS_GROUP, "group"); - entry.insert(KIO::UDSEntry::UDS_ACCESS_TIME, 0); + KIO::UDSEntry entry[3]; - entry.insert(KIO::UDSEntry::UDS_NAME, "a.txt"); - entry.insert(KIO::UDSEntry::UDS_USER, "user-b"); - items.append(KFileItem(entry, m_testDir->url(), false, true)); + entry[0].fastInsert(KIO::UDSEntry::UDS_NAME, "a.txt"); + entry[0].fastInsert(KIO::UDSEntry::UDS_USER, "user-b"); - entry.insert(KIO::UDSEntry::UDS_NAME, "b.txt"); - entry.insert(KIO::UDSEntry::UDS_USER, "user-c"); - items.append(KFileItem(entry, m_testDir->url(), false, true)); + entry[1].fastInsert(KIO::UDSEntry::UDS_NAME, "b.txt"); + entry[1].fastInsert(KIO::UDSEntry::UDS_USER, "user-c"); - entry.insert(KIO::UDSEntry::UDS_NAME, "c.txt"); - entry.insert(KIO::UDSEntry::UDS_USER, "user-a"); - items.append(KFileItem(entry, m_testDir->url(), false, true)); + entry[2].fastInsert(KIO::UDSEntry::UDS_NAME, "c.txt"); + entry[2].fastInsert(KIO::UDSEntry::UDS_USER, "user-a"); + + for (int i = 0; i < 3; ++i) { + entry[i].fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, 0100000); // S_IFREG might not be defined on non-Unix platforms. + entry[i].fastInsert(KIO::UDSEntry::UDS_ACCESS, 07777); + entry[i].fastInsert(KIO::UDSEntry::UDS_SIZE, 0); + entry[i].fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, 0); + entry[i].fastInsert(KIO::UDSEntry::UDS_GROUP, "group"); + entry[i].fastInsert(KIO::UDSEntry::UDS_ACCESS_TIME, 0); + items.append(KFileItem(entry[i], m_testDir->url(), false, true)); + } m_model->slotItemsAdded(m_testDir->url(), items); m_model->slotCompleted(); - QCOMPARE(itemsInModel(), QStringList() << "a.txt" << "b.txt" << "c.txt"); + QCOMPARE(itemsInModel(), + QStringList() << "a.txt" + << "b.txt" + << "c.txt"); // Add a filter. m_model->setNameFilter("a"); @@ -1560,65 +2403,316 @@ void KFileItemModelTest::testChangeSortRoleWhileFiltering() // Clear the filter, and verify that the items are sorted correctly. m_model->setNameFilter(QString()); - QCOMPARE(itemsInModel(), QStringList() << "c.txt" << "a.txt" << "b.txt"); + QCOMPARE(itemsInModel(), + QStringList() << "c.txt" + << "a.txt" + << "b.txt"); } void KFileItemModelTest::testRefreshFilteredItems() { - QStringList files; - files << "a.txt" << "b.txt" << "c.jpg" << "d.jpg"; - m_testDir->createFiles(files); + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + + m_testDir->createFiles({"a.txt", "b.txt", "c.jpg", "d.jpg"}); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); - QCOMPARE(itemsInModel(), QStringList() << "a.txt" << "b.txt" << "c.jpg" << "d.jpg"); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << "a.txt" + << "b.txt" + << "c.jpg" + << "d.jpg"); const KFileItem fileItemC = m_model->fileItem(2); // Show only the .txt files. m_model->setNameFilter(".txt"); - QCOMPARE(itemsInModel(), QStringList() << "a.txt" << "b.txt"); + QCOMPARE(itemsInModel(), + QStringList() << "a.txt" + << "b.txt"); // Rename one of the .jpg files. KFileItem fileItemE = fileItemC; - KUrl urlE = fileItemE.url(); - urlE.setFileName("e.jpg"); + QUrl urlE = fileItemE.url().adjusted(QUrl::RemoveFilename); + urlE.setPath(urlE.path() + "/e.jpg"); fileItemE.setUrl(urlE); - m_model->slotRefreshItems(QList >() << qMakePair(fileItemC, fileItemE)); + m_model->slotRefreshItems({qMakePair(fileItemC, fileItemE)}); // Show all files again, and verify that the model has updated the file name. m_model->setNameFilter(QString()); - QCOMPARE(itemsInModel(), QStringList() << "a.txt" << "b.txt" << "d.jpg" << "e.jpg"); + QCOMPARE(itemsInModel(), + QStringList() << "a.txt" + << "b.txt" + << "d.jpg" + << "e.jpg"); } void KFileItemModelTest::testCreateMimeData() { + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QSet modelRoles = m_model->roles(); - modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount"; + modelRoles << "isExpanded" + << "isExpandable" + << "expandedParentsCount"; m_model->setRoles(modelRoles); - QStringList files; - files << "a/1"; - m_testDir->createFiles(files); + m_testDir->createFile("a/1"); m_model->loadDirectory(m_testDir->url()); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QVERIFY(itemsInsertedSpy.wait()); QCOMPARE(itemsInModel(), QStringList() << "a"); // Expand "a/". m_model->setExpanded(0, true); - QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); - QCOMPARE(itemsInModel(), QStringList() << "a" << "1"); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << "a" + << "1"); // Verify that creating the MIME data for a child of an expanded folder does // not cause a crash, see https://bugs.kde.org/show_bug.cgi?id=329119 KItemSet selection; selection.insert(1); - QMimeData* mimeData = m_model->createMimeData(selection); + QMimeData *mimeData = m_model->createMimeData(selection); delete mimeData; } +void KFileItemModelTest::testCollapseFolderWhileLoading() +{ + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + + QSet modelRoles = m_model->roles(); + modelRoles << "isExpanded" + << "isExpandable" + << "expandedParentsCount"; + m_model->setRoles(modelRoles); + + m_testDir->createFile("a2/b/c1.txt"); + + m_model->loadDirectory(m_testDir->url()); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(itemsInModel(), QStringList() << "a2"); + + // Expand "a2/". + m_model->setExpanded(0, true); + QVERIFY(m_model->isExpanded(0)); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << "a2" + << "b"); + + // Expand "a2/b/". + m_model->setExpanded(1, true); + QVERIFY(m_model->isExpanded(1)); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << "a2" + << "b" + << "c1.txt"); + + // Simulate that a new item "c2.txt" appears, but that the dir lister's completed() + // signal is not emitted yet. + const KFileItem fileItemC1 = m_model->fileItem(2); + KFileItem fileItemC2 = fileItemC1; + QUrl urlC2 = fileItemC2.url(); + urlC2 = urlC2.adjusted(QUrl::RemoveFilename); + urlC2.setPath(urlC2.path() + "c2.txt"); + fileItemC2.setUrl(urlC2); + + const QUrl urlB = m_model->fileItem(1).url(); + m_model->slotItemsAdded(urlB, KFileItemList() << fileItemC2); + QCOMPARE(itemsInModel(), + QStringList() << "a2" + << "b" + << "c1.txt"); + + // Collapse "a2/". This should also remove all its (indirect) children from + // the model and from the model's m_pendingItemsToInsert member. + m_model->setExpanded(0, false); + QCOMPARE(itemsInModel(), QStringList() << "a2"); + + // Simulate that the dir lister's completed() signal is emitted. If "c2.txt" + // is still in m_pendingItemsToInsert, then we might get a crash, see + // https://bugs.kde.org/show_bug.cgi?id=332102. Even if the crash is not + // reproducible here, Valgrind will complain, and the item "c2.txt" will appear + // without parent in the model. + m_model->slotCompleted(); + QCOMPARE(itemsInModel(), QStringList() << "a2"); + + // Expand "a2/" again. + m_model->setExpanded(0, true); + QVERIFY(m_model->isExpanded(0)); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << "a2" + << "b"); + + // Now simulate that a new folder "a1/" is appears, but that the dir lister's + // completed() signal is not emitted yet. + const KFileItem fileItemA2 = m_model->fileItem(0); + KFileItem fileItemA1 = fileItemA2; + QUrl urlA1 = fileItemA1.url().adjusted(QUrl::RemoveFilename); + urlA1.setPath(urlA1.path() + "a1"); + fileItemA1.setUrl(urlA1); + + m_model->slotItemsAdded(m_model->directory(), KFileItemList() << fileItemA1); + QCOMPARE(itemsInModel(), + QStringList() << "a2" + << "b"); + + // Collapse "a2/". Note that this will cause "a1/" to be added to the model, + // i.e., the index of "a2/" will change from 0 to 1. Check that this does not + // confuse the code which collapses the folder. + m_model->setExpanded(0, false); + QCOMPARE(itemsInModel(), + QStringList() << "a1" + << "a2"); + QVERIFY(!m_model->isExpanded(0)); + QVERIFY(!m_model->isExpanded(1)); +} + +void KFileItemModelTest::testDeleteFileMoreThanOnce() +{ + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + + m_testDir->createFiles({"a.txt", "b.txt", "c.txt", "d.txt"}); + + m_model->loadDirectory(m_testDir->url()); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(itemsInModel(), + QStringList() << "a.txt" + << "b.txt" + << "c.txt" + << "d.txt"); + + const KFileItem fileItemB = m_model->fileItem(1); + + // Tell the model that a list of items has been deleted, where "b.txt" appears twice in the list. + KFileItemList list; + list << fileItemB << fileItemB; + m_model->slotItemsDeleted(list); + + QVERIFY(m_model->isConsistent()); + QCOMPARE(itemsInModel(), + QStringList() << "a.txt" + << "c.txt" + << "d.txt"); +} + +void KFileItemModelTest::testInsertAfterExpand() +{ + m_model->m_dirLister->setAutoUpdate(true); + + QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted); + QVERIFY(itemsInsertedSpy.isValid()); + QSignalSpy itemsRemovedSpy(m_model, &KFileItemModel::itemsRemoved); + QVERIFY(itemsRemovedSpy.isValid()); + QSignalSpy loadingCompletedSpy(m_model, &KFileItemModel::directoryLoadingCompleted); + QVERIFY(loadingCompletedSpy.isValid()); + + // Test expanding subfolders in a folder with the items "a/", "a/a/" + QSet originalModelRoles = m_model->roles(); + QSet modelRoles = originalModelRoles; + modelRoles << "isExpanded" + << "isExpandable" + << "expandedParentsCount"; + m_model->setRoles(modelRoles); + + m_testDir->createFile("a/b/1"); + + m_model->loadDirectory(m_testDir->url()); + QVERIFY(itemsInsertedSpy.wait()); + + // So far, the model contains only "a/" + QCOMPARE(m_model->count(), 1); + QVERIFY(m_model->isExpandable(0)); + QVERIFY(!m_model->isExpanded(0)); + QVERIFY(m_model->expandedDirectories().empty()); + + QCOMPARE(itemsInsertedSpy.count(), 1); + { + KItemRangeList itemRangeList = itemsInsertedSpy.takeFirst().at(0).value(); + QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 1)); // 1 new item "a/" with index 0 + QCOMPARE(m_model->expandedParentsCount(0), 0); + } + itemsInsertedSpy.clear(); + + // Expand the folder "a/" -> "a/b" become visible + m_model->setExpanded(0, true); + QVERIFY(m_model->isExpanded(0)); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(m_model->count(), 2); // 3 items: "a/", "a/a/" + QCOMPARE(m_model->expandedDirectories(), QSet({QUrl::fromLocalFile(m_testDir->path() + "/a")})); + + QCOMPARE(itemsInsertedSpy.count(), 1); + { + KItemRangeList itemRangeList = itemsInsertedSpy.takeFirst().at(0).value(); + QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 1)); // 1 new item "a/b" with index 1 + QCOMPARE(m_model->expandedParentsCount(1), 1); + } + itemsInsertedSpy.clear(); + + // Expand "a/b" -> "a/b/1" becomes visible + m_model->setExpanded(1, true); + QVERIFY(itemsInsertedSpy.wait()); + QCOMPARE(m_model->expandedDirectories(), QSet({QUrl::fromLocalFile(m_testDir->path() + "/a"), QUrl::fromLocalFile(m_testDir->path() + "/a/b")})); + + QCOMPARE(itemsInsertedSpy.count(), 1); + { + KItemRangeList itemRangeList = itemsInsertedSpy.takeFirst().at(0).value(); + QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(2, 1)); // 1 new item "a/b/1" with index 2 + QCOMPARE(m_model->expandedParentsCount(2), 2); + } + itemsInsertedSpy.clear(); + + // Collapse "a" whilst leaving "b" expanded + m_model->setExpanded(0, false); + + // Insert additional files into "a/b/" + m_testDir->createFile("a/b/2"); + + QVERIFY(!itemsInsertedSpy.wait(5000)); + + QCOMPARE(itemsInModel(), {"a"}); + + m_model->setExpanded(0, true); + ; + QTRY_COMPARE(itemsInModel(), QStringList({"a", "b", "1", "2"})); + + QCOMPARE(m_model->expandedParentsCount(0), 0); // a + QCOMPARE(m_model->expandedParentsCount(1), 1); // a/b + QCOMPARE(m_model->expandedParentsCount(2), 2); // a/b/1 + QCOMPARE(m_model->expandedParentsCount(3), 2); // a/b/2 +} + +void KFileItemModelTest::testCurrentDirRemoved() +{ + m_model->m_dirLister->setAutoUpdate(true); + QSignalSpy currentDirectoryRemovedSpy(m_model, &KFileItemModel::currentDirectoryRemoved); + QVERIFY(currentDirectoryRemovedSpy.isValid()); + QSignalSpy loadingCompletedSpy(m_model, &KFileItemModel::directoryLoadingCompleted); + QVERIFY(loadingCompletedSpy.isValid()); + QSignalSpy dirListerClearSpy(m_model->m_dirLister, &KCoreDirLister::clear); + QVERIFY(dirListerClearSpy.isValid()); + + m_testDir->createFiles({"dir/a.txt", "dir/b.txt"}); + m_model->loadDirectory(QUrl::fromLocalFile(m_testDir->path() + "/dir/")); + QVERIFY(loadingCompletedSpy.wait()); + QCOMPARE(m_model->count(), 2); + QVERIFY(m_model->isConsistent()); + + m_testDir->removeDir("dir"); + QVERIFY(currentDirectoryRemovedSpy.wait()); + + // dirLister calls clear + QCOMPARE(dirListerClearSpy.count(), 2); + QVERIFY(m_model->isConsistent()); + QVERIFY(m_model->m_itemData.isEmpty()); + QCOMPARE(m_model->count(), 0); +} + QStringList KFileItemModelTest::itemsInModel() const { QStringList items; @@ -1628,6 +2722,6 @@ QStringList KFileItemModelTest::itemsInModel() const return items; } -QTEST_KDEMAIN(KFileItemModelTest, NoGUI) +QTEST_MAIN(KFileItemModelTest) #include "kfileitemmodeltest.moc"