X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/891ee0c41a3b2e80aa8191a6f8152c6095ce0351..04e825d022c77baad3345269da7a210e95274f07:/src/tests/kfileitemmodeltest.cpp diff --git a/src/tests/kfileitemmodeltest.cpp b/src/tests/kfileitemmodeltest.cpp index 65368b75b..383575a97 100644 --- a/src/tests/kfileitemmodeltest.cpp +++ b/src/tests/kfileitemmodeltest.cpp @@ -21,6 +21,8 @@ #include #include +#include + #include "kitemviews/kfileitemmodel.h" #include "kitemviews/private/kfileitemmodeldirlister.h" #include "testdir.h" @@ -71,13 +73,18 @@ private slots: void testItemRangeConsistencyWhenInsertingItems(); void testExpandItems(); void testExpandParentItems(); + void testMakeExpandedItemHidden(); void testSorting(); void testIndexForKeyboardSearch(); void testNameFilter(); void testEmptyPath(); + void testRefreshExpandedItem(); + void testRemoveHiddenItems(); + void collapseParentOfHiddenItems(); + void removeParentOfHiddenItems(); + void testGeneralParentChildRelationships(); private: - bool isModelConsistent() const; QStringList itemsInModel() const; private: @@ -152,7 +159,7 @@ void KFileItemModelTest::testNewItems() QCOMPARE(m_model->count(), 3); - QVERIFY(isModelConsistent()); + QVERIFY(m_model->isConsistent()); } void KFileItemModelTest::testRemoveItems() @@ -162,13 +169,13 @@ void KFileItemModelTest::testRemoveItems() m_model->loadDirectory(m_testDir->url()); QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); QCOMPARE(m_model->count(), 2); - QVERIFY(isModelConsistent()); + 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)); QCOMPARE(m_model->count(), 1); - QVERIFY(isModelConsistent()); + QVERIFY(m_model->isConsistent()); } void KFileItemModelTest::testDirLoadingCompleted() @@ -211,7 +218,7 @@ void KFileItemModelTest::testDirLoadingCompleted() QCOMPARE(itemsRemovedSpy.count(), 2); QCOMPARE(m_model->count(), 4); - QVERIFY(isModelConsistent()); + QVERIFY(m_model->isConsistent()); } void KFileItemModelTest::testSetData() @@ -232,7 +239,7 @@ void KFileItemModelTest::testSetData() values = m_model->data(0); QCOMPARE(values.value("customRole1").toString(), QString("Test1")); QCOMPARE(values.value("customRole2").toString(), QString("Test2")); - QVERIFY(isModelConsistent()); + QVERIFY(m_model->isConsistent()); } void KFileItemModelTest::testSetDataWithModifiedSortRole_data() @@ -313,7 +320,7 @@ void KFileItemModelTest::testSetDataWithModifiedSortRole() QCOMPARE(m_model->data(0).value("rating").toInt(), ratingIndex0); QCOMPARE(m_model->data(1).value("rating").toInt(), ratingIndex1); QCOMPARE(m_model->data(2).value("rating").toInt(), ratingIndex2); - QVERIFY(isModelConsistent()); + QVERIFY(m_model->isConsistent()); } void KFileItemModelTest::testModelConsistencyWhenInsertingItems() @@ -352,7 +359,7 @@ void KFileItemModelTest::testModelConsistencyWhenInsertingItems() QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); } - QVERIFY(isModelConsistent()); + QVERIFY(m_model->isConsistent()); } QCOMPARE(m_model->count(), 201); @@ -486,6 +493,7 @@ void KFileItemModelTest::testExpandItems() QCOMPARE(spyRemoved.count(), 1); itemRangeList = spyRemoved.takeFirst().at(0).value(); QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 4)); // 4 items removed + QVERIFY(m_model->isConsistent()); // Clear the model, reload the folder and try to restore the expanded folders. m_model->clear(); @@ -502,6 +510,7 @@ void KFileItemModelTest::testExpandItems() QVERIFY(m_model->isExpanded(3)); QVERIFY(!m_model->isExpanded(4)); QCOMPARE(m_model->expandedDirectories(), allFolders); + QVERIFY(m_model->isConsistent()); // Move to a sub folder, then call restoreExpandedFolders() *before* going back. // This is how DolphinView restores the expanded folders when navigating in history. @@ -564,6 +573,56 @@ void KFileItemModelTest::testExpandParentItems() QVERIFY(m_model->isExpanded(2)); QVERIFY(m_model->isExpanded(3)); QVERIFY(!m_model->isExpanded(4)); + QVERIFY(m_model->isConsistent()); +} + +/** + * Renaming an expanded folder by prepending its name with a dot makes it + * hidden. Verify that this does not cause an inconsistent model state and + * a crash later on, see https://bugs.kde.org/show_bug.cgi?id=311947 + */ +void KFileItemModelTest::testMakeExpandedItemHidden() +{ + QSet modelRoles = m_model->roles(); + 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_model->loadDirectory(m_testDir->url()); + QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + + // 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)); + + // 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)); + 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"; + + KIO::SimpleJob* job = KIO::rename(oldPath, newPath, KIO::HideProgressInfo); + bool ok = job->exec(); + QVERIFY(ok); + QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsRemoved(KItemRangeList)), DefaultTimeout)); + + // "1a/2" and its subfolders have disappeared now. + QVERIFY(m_model->isConsistent()); + QCOMPARE(m_model->count(), 3); + + m_model->setExpanded(0, false); + QCOMPARE(m_model->count(), 2); + } void KFileItemModelTest::testSorting() @@ -794,31 +853,282 @@ void KFileItemModelTest::testEmptyPath() KFileItemList items; items << KFileItem(emptyUrl, QString(), KFileItem::Unknown) << KFileItem(url, QString(), KFileItem::Unknown); - m_model->slotNewItems(items); + m_model->slotItemsAdded(emptyUrl, items); m_model->slotCompleted(); } -bool KFileItemModelTest::isModelConsistent() const +/** + * Verifies that the 'isExpanded' state of folders does not change when the + * 'refreshItems' signal is received, see https://bugs.kde.org/show_bug.cgi?id=299675. + */ +void KFileItemModelTest::testRefreshExpandedItem() { - if (m_model->m_items.count() != m_model->m_itemData.count()) { - return false; - } + QSet modelRoles = m_model->roles(); + modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount"; + m_model->setRoles(modelRoles); - for (int i = 0; i < m_model->count(); ++i) { - const KFileItem item = m_model->fileItem(i); - if (item.isNull()) { - qWarning() << "Item" << i << "is null"; - return false; - } + QStringList files; + files << "a/1" << "a/2" << "3" << "4"; + m_testDir->createFiles(files); - const int itemIndex = m_model->index(item); - if (itemIndex != i) { - qWarning() << "Item" << i << "has a wrong index:" << itemIndex; - return false; - } - } + m_model->loadDirectory(m_testDir->url()); + QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QCOMPARE(m_model->count(), 3); // "a/", "3", "4" + + m_model->setExpanded(0, true); + QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + 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()); + + QCOMPARE(m_model->count(), 5); // "a/", "a/1", "a/2", "3", "4" + QVERIFY(m_model->isExpanded(0)); +} + +/** + * 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 + */ +void KFileItemModelTest::testRemoveHiddenItems() +{ + m_testDir->createDir(".a"); + m_testDir->createDir(".b"); + m_testDir->createDir("c"); + m_testDir->createDir("d"); + m_testDir->createFiles(QStringList() << ".f" << ".g" << "h" << "i"); + + QSignalSpy spyItemsInserted(m_model, SIGNAL(itemsInserted(KItemRangeList))); + QSignalSpy spyItemsRemoved(m_model, SIGNAL(itemsRemoved(KItemRangeList))); + + 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(); + 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(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(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(itemRangeList, KItemRangeList() << KItemRange(0, 8)); + + // Hiding hidden files makes the dir lister emit its itemsDeleted signal. + // Verify that this does not make the model crash. + m_model->setShowHiddenFiles(false); +} + +/** + * Verify that filtered items are removed when their parent is collapsed. + */ +void KFileItemModelTest::collapseParentOfHiddenItems() +{ + QSet modelRoles = m_model->roles(); + 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_model->loadDirectory(m_testDir->url()); + QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QCOMPARE(m_model->count(), 1); // Only "a/" + + // Expand "a/". + m_model->setExpanded(0, true); + QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + 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)); + 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)); + 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. + m_model->setNameFilter("xyz"); + QCOMPARE(m_model->count(), 3); + QCOMPARE(itemsInModel(), QStringList() << "a" << "b" << "c"); + + // Collapse the folder "a/". + QSignalSpy spyItemsRemoved(m_model, SIGNAL(itemsRemoved(KItemRangeList))); + m_model->setExpanded(0, false); + QCOMPARE(spyItemsRemoved.count(), 1); + QCOMPARE(m_model->count(), 1); + QCOMPARE(itemsInModel(), QStringList() << "a"); + + // Remove the filter -> no files should appear (and we should not get a crash). + m_model->setNameFilter(QString()); + QCOMPARE(m_model->count(), 1); +} + +/** + * Verify that filtered items are removed when their parent is deleted. + */ +void KFileItemModelTest::removeParentOfHiddenItems() +{ + QSet modelRoles = m_model->roles(); + 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_model->loadDirectory(m_testDir->url()); + QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + QCOMPARE(m_model->count(), 1); // Only "a/" + + // Expand "a/". + m_model->setExpanded(0, true); + QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + 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)); + 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)); + 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. + m_model->setNameFilter("xyz"); + QCOMPARE(m_model->count(), 3); + 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"); + + // Remove the filter -> only the file "a/1" should appear. + m_model->setNameFilter(QString()); + QCOMPARE(m_model->count(), 2); + QCOMPARE(itemsInModel(), QStringList() << "a" << "1"); +} + +/** + * Create a tree structure where parent-child relationships can not be + * determined by parsing the URLs, and verify that KFileItemModel + * handles them correctly. + */ +void KFileItemModelTest::testGeneralParentChildRelationships() +{ + QSet modelRoles = m_model->roles(); + modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount"; + m_model->setRoles(modelRoles); + + QStringList files; + files << "parent1/realChild1/realGrandChild1" << "parent2/realChild2/realGrandChild2"; + m_testDir->createFiles(files); + + m_model->loadDirectory(m_testDir->url()); + QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + 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"); + + m_model->setExpanded(1, true); + QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + 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"); + + m_model->setExpanded(4, true); + QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout)); + 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(); + + m_model->slotItemsAdded(parent1, KFileItemList() << KFileItem(KUrl("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)); + 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)); + 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)); + 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))); + m_model->setNameFilter("xyz"); + QCOMPARE(itemsInModel(), QStringList() << "parent1" << "realChild1" << "parent2" << "realChild2"); + QCOMPARE(itemsRemovedSpy.count(), 1); + QList arguments = itemsRemovedSpy.takeFirst(); + KItemRangeList itemRangeList = arguments.at(0).value(); + QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(2, 3) << KItemRange(7, 3)); + + // Collapse "parent1". + m_model->setExpanded(0, false); + QCOMPARE(itemsInModel(), QStringList() << "parent1" << "parent2" << "realChild2"); + QCOMPARE(itemsRemovedSpy.count(), 1); + arguments = itemsRemovedSpy.takeFirst(); + itemRangeList = arguments.at(0).value(); + QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 1)); + + // Remove "parent2". + m_model->slotItemsDeleted(KFileItemList() << m_model->fileItem(1)); + QCOMPARE(itemsInModel(), QStringList() << "parent1"); + QCOMPARE(itemsRemovedSpy.count(), 1); + arguments = itemsRemovedSpy.takeFirst(); + itemRangeList = arguments.at(0).value(); + QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 2)); - return true; + // Clear filter, verify that no items reappear. + m_model->setNameFilter(QString()); + QCOMPARE(itemsInModel(), QStringList() << "parent1"); } QStringList KFileItemModelTest::itemsInModel() const