]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/tests/kfileitemmodeltest.cpp
Merge remote-tracking branch 'origin/KDE/4.10'
[dolphin.git] / src / tests / kfileitemmodeltest.cpp
index c41fcb6df959b2c10498cc87986b4505a85bfeed..383575a97a8bb7bb91756770f0391ea6a61335ff 100644 (file)
 #include <qtest_kde.h>
 
 #include <KDirLister>
+#include <kio/job.h>
+
 #include "kitemviews/kfileitemmodel.h"
+#include "kitemviews/private/kfileitemmodeldirlister.h"
 #include "testdir.h"
 
 void myMessageOutput(QtMsgType type, const char* msg)
 {
     switch (type) {
-     case QtDebugMsg:
-         break;
-     case QtWarningMsg:
-         break;
-     case QtCriticalMsg:
-         fprintf(stderr, "Critical: %s\n", msg);
-         break;
-     case QtFatalMsg:
-         fprintf(stderr, "Fatal: %s\n", msg);
-         abort();
-     default:
+    case QtDebugMsg:
+        break;
+    case QtWarningMsg:
         break;
+    case QtCriticalMsg:
+        fprintf(stderr, "Critical: %s\n", msg);
+        break;
+    case QtFatalMsg:
+        fprintf(stderr, "Fatal: %s\n", msg);
+        abort();
+    default:
+       break;
     }
- }
+}
 
 namespace {
     const int DefaultTimeout = 5000;
@@ -62,26 +65,30 @@ private slots:
     void testDefaultGroupedSorting();
     void testNewItems();
     void testRemoveItems();
+    void testDirLoadingCompleted();
     void testSetData();
     void testSetDataWithModifiedSortRole_data();
     void testSetDataWithModifiedSortRole();
     void testModelConsistencyWhenInsertingItems();
     void testItemRangeConsistencyWhenInsertingItems();
     void testExpandItems();
+    void testExpandParentItems();
+    void testMakeExpandedItemHidden();
     void testSorting();
-
-    void testExpansionLevelsCompare_data();
-    void testExpansionLevelsCompare();
-
     void testIndexForKeyboardSearch();
+    void testNameFilter();
+    void testEmptyPath();
+    void testRefreshExpandedItem();
+    void testRemoveHiddenItems();
+    void collapseParentOfHiddenItems();
+    void removeParentOfHiddenItems();
+    void testGeneralParentChildRelationships();
 
 private:
-    bool isModelConsistent() const;
     QStringList itemsInModel() const;
 
 private:
     KFileItemModel* m_model;
-    KDirLister* m_dirLister;
     TestDir* m_testDir;
 };
 
@@ -96,8 +103,8 @@ void KFileItemModelTest::init()
     qRegisterMetaType<KFileItemList>("KFileItemList");
 
     m_testDir = new TestDir();
-    m_dirLister = new KDirLister();
-    m_model = new KFileItemModel(m_dirLister);
+    m_model = new KFileItemModel();
+    m_model->m_dirLister->setAutoUpdate(false);
 }
 
 void KFileItemModelTest::cleanup()
@@ -105,9 +112,6 @@ void KFileItemModelTest::cleanup()
     delete m_model;
     m_model = 0;
 
-    delete m_dirLister;
-    m_dirLister = 0;
-
     delete m_testDir;
     m_testDir = 0;
 }
@@ -115,27 +119,28 @@ void KFileItemModelTest::cleanup()
 void KFileItemModelTest::testDefaultRoles()
 {
     const QSet<QByteArray> roles = m_model->roles();
-    QCOMPARE(roles.count(), 2);
-    QVERIFY(roles.contains("name"));
+    QCOMPARE(roles.count(), 3);
+    QVERIFY(roles.contains("text"));
     QVERIFY(roles.contains("isDir"));
+    QVERIFY(roles.contains("isLink"));
 }
 
 void KFileItemModelTest::testDefaultSortRole()
 {
-    QCOMPARE(m_model->sortRole(), QByteArray("name"));
+    QCOMPARE(m_model->sortRole(), QByteArray("text"));
 
     QStringList files;
     files << "c.txt" << "a.txt" << "b.txt";
 
     m_testDir->createFiles(files);
 
-    m_dirLister->openUrl(m_testDir->url());
+    m_model->loadDirectory(m_testDir->url());
     QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
 
     QCOMPARE(m_model->count(), 3);
-    QCOMPARE(m_model->data(0)["name"].toString(), QString("a.txt"));
-    QCOMPARE(m_model->data(1)["name"].toString(), QString("b.txt"));
-    QCOMPARE(m_model->data(2)["name"].toString(), QString("c.txt"));
+    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"));
 }
 
 void KFileItemModelTest::testDefaultGroupedSorting()
@@ -149,32 +154,78 @@ void KFileItemModelTest::testNewItems()
     files << "a.txt" << "b.txt" << "c.txt";
     m_testDir->createFiles(files);
 
-    m_dirLister->openUrl(m_testDir->url());
+    m_model->loadDirectory(m_testDir->url());
     QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
 
     QCOMPARE(m_model->count(), 3);
 
-    QVERIFY(isModelConsistent());
+    QVERIFY(m_model->isConsistent());
 }
 
 void KFileItemModelTest::testRemoveItems()
 {
     m_testDir->createFile("a.txt");
-    m_dirLister->openUrl(m_testDir->url());
+    m_testDir->createFile("b.txt");
+    m_model->loadDirectory(m_testDir->url());
     QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
+    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));
     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)));
+
+    m_testDir->createFiles(QStringList() << "a.txt" << "b.txt" << "c.txt");
+
+    m_model->loadDirectory(m_testDir->url());
+    QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(directoryLoadingCompleted()), DefaultTimeout));
+    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_model->m_dirLister->updateDirectory(m_testDir->url());
+    QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(directoryLoadingCompleted()), DefaultTimeout));
+    QCOMPARE(loadingCompletedSpy.count(), 2);
+    QCOMPARE(itemsInsertedSpy.count(), 2);
+    QCOMPARE(itemsRemovedSpy.count(), 0);
+    QCOMPARE(m_model->count(), 5);
 
     m_testDir->removeFile("a.txt");
-    m_dirLister->updateDirectory(m_testDir->url());
+    m_testDir->createFile("f.txt");
+    m_model->m_dirLister->updateDirectory(m_testDir->url());
+    QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(directoryLoadingCompleted()), DefaultTimeout));
+    QCOMPARE(loadingCompletedSpy.count(), 3);
+    QCOMPARE(itemsInsertedSpy.count(), 3);
+    QCOMPARE(itemsRemovedSpy.count(), 1);
+    QCOMPARE(m_model->count(), 5);
+
+    m_testDir->removeFile("b.txt");
+    m_model->m_dirLister->updateDirectory(m_testDir->url());
     QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsRemoved(KItemRangeList)), DefaultTimeout));
-    QCOMPARE(m_model->count(), 0);
+    QCOMPARE(loadingCompletedSpy.count(), 4);
+    QCOMPARE(itemsInsertedSpy.count(), 3);
+    QCOMPARE(itemsRemovedSpy.count(), 2);
+    QCOMPARE(m_model->count(), 4);
+
+    QVERIFY(m_model->isConsistent());
 }
 
 void KFileItemModelTest::testSetData()
 {
     m_testDir->createFile("a.txt");
 
-    m_dirLister->openUrl(m_testDir->url());
+    m_model->loadDirectory(m_testDir->url());
     QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
 
     QHash<QByteArray, QVariant> values;
@@ -188,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()
@@ -225,13 +276,13 @@ void KFileItemModelTest::testSetDataWithModifiedSortRole()
 
     // Changing the value of a sort-role must result in
     // a reordering of the items.
-    QCOMPARE(m_model->sortRole(), QByteArray("name"));
+    QCOMPARE(m_model->sortRole(), QByteArray("text"));
 
     QStringList files;
     files << "a.txt" << "b.txt" << "c.txt";
     m_testDir->createFiles(files);
 
-    m_dirLister->openUrl(m_testDir->url());
+    m_model->loadDirectory(m_testDir->url());
     QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
 
     // Fill the "rating" role of each file:
@@ -265,11 +316,11 @@ void KFileItemModelTest::testSetDataWithModifiedSortRole()
     if (expectMoveSignal) {
         QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsMoved(KItemRange,QList<int>)), DefaultTimeout));
     }
-    
+
     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()
@@ -282,10 +333,8 @@ void KFileItemModelTest::testModelConsistencyWhenInsertingItems()
     // one itemsInserted()-signal. However in this test we want to stress
     // KFileItemModel to do a lot of insert operation and hence decrease
     // the timeout to 1 millisecond.
-    m_model->m_minimumUpdateIntervalTimer->setInterval(1);
-
     m_testDir->createFile("1");
-    m_dirLister->openUrl(m_testDir->url());
+    m_model->loadDirectory(m_testDir->url());
     QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
     QCOMPARE(m_model->count(), 1);
 
@@ -305,12 +354,12 @@ void KFileItemModelTest::testModelConsistencyWhenInsertingItems()
             m_testDir->createFile(QString::number(itemName));
         }
 
-        m_dirLister->updateDirectory(m_testDir->url());
+        m_model->m_dirLister->updateDirectory(m_testDir->url());
         if (spy.count() == 0) {
             QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
         }
 
-        QVERIFY(isModelConsistent());
+        QVERIFY(m_model->isConsistent());
     }
 
     QCOMPARE(m_model->count(), 201);
@@ -325,7 +374,7 @@ void KFileItemModelTest::testItemRangeConsistencyWhenInsertingItems()
     // 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_dirLister->openUrl(m_testDir->url());
+    m_model->loadDirectory(m_testDir->url());
     QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
 
     QCOMPARE(spy1.count(), 1);
@@ -350,7 +399,7 @@ void KFileItemModelTest::testItemRangeConsistencyWhenInsertingItems()
     m_testDir->createFiles(files);
 
     QSignalSpy spy2(m_model, SIGNAL(itemsInserted(KItemRangeList)));
-    m_dirLister->updateDirectory(m_testDir->url());
+    m_model->m_dirLister->updateDirectory(m_testDir->url());
     QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
 
     QCOMPARE(spy2.count(), 1);
@@ -367,7 +416,7 @@ void KFileItemModelTest::testExpandItems()
     // yields the correct result for "a/a/1" and "a/a-1/", whis is non-trivial because they share the
     // first three characters.
     QSet<QByteArray> modelRoles = m_model->roles();
-    modelRoles << "isExpanded" << "expansionLevel";
+    modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount";
     m_model->setRoles(modelRoles);
 
     QStringList files;
@@ -376,16 +425,16 @@ void KFileItemModelTest::testExpandItems()
 
     // Store the URLs of all folders in a set.
     QSet<KUrl> allFolders;
-    allFolders << KUrl(m_testDir->name() + "a") << KUrl(m_testDir->name() + "a/a") << KUrl(m_testDir->name() + "a/a-1");
+    allFolders << KUrl(m_testDir->name() + 'a') << KUrl(m_testDir->name() + "a/a") << KUrl(m_testDir->name() + "a/a-1");
 
-    m_dirLister->openUrl(m_testDir->url());
+    m_model->loadDirectory(m_testDir->url());
     QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
 
     // 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->expandedUrls().empty());
+    QVERIFY(m_model->expandedDirectories().empty());
 
     QSignalSpy spyInserted(m_model, SIGNAL(itemsInserted(KItemRangeList)));
 
@@ -394,7 +443,7 @@ void KFileItemModelTest::testExpandItems()
     QVERIFY(m_model->isExpanded(0));
     QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
     QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/a/", "a/a-1/"
-    QCOMPARE(m_model->expandedUrls(), QSet<KUrl>() << KUrl(m_testDir->name() + "a"));
+    QCOMPARE(m_model->expandedDirectories(), QSet<KUrl>() << KUrl(m_testDir->name() + 'a'));
 
     QCOMPARE(spyInserted.count(), 1);
     KItemRangeList itemRangeList = spyInserted.takeFirst().at(0).value<KItemRangeList>();
@@ -410,7 +459,7 @@ void KFileItemModelTest::testExpandItems()
     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->expandedUrls(), QSet<KUrl>() << KUrl(m_testDir->name() + "a") << KUrl(m_testDir->name() + "a/a"));
+    QCOMPARE(m_model->expandedDirectories(), QSet<KUrl>() << KUrl(m_testDir->name() + 'a') << KUrl(m_testDir->name() + "a/a"));
 
     QCOMPARE(spyInserted.count(), 1);
     itemRangeList = spyInserted.takeFirst().at(0).value<KItemRangeList>();
@@ -424,7 +473,7 @@ void KFileItemModelTest::testExpandItems()
     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"
-    QCOMPARE(m_model->expandedUrls(), allFolders);
+    QCOMPARE(m_model->expandedDirectories(), allFolders);
 
     QCOMPARE(spyInserted.count(), 1);
     itemRangeList = spyInserted.takeFirst().at(0).value<KItemRangeList>();
@@ -439,27 +488,141 @@ void KFileItemModelTest::testExpandItems()
     m_model->setExpanded(0, false);
     QVERIFY(!m_model->isExpanded(0));
     QCOMPARE(m_model->count(), 1);
-    QVERIFY(!m_model->expandedUrls().contains(KUrl(m_testDir->name() + "a"))); // TODO: Make sure that child URLs are also removed
+    QVERIFY(!m_model->expandedDirectories().contains(KUrl(m_testDir->name() + 'a'))); // TODO: Make sure that child URLs are also removed
 
     QCOMPARE(spyRemoved.count(), 1);
     itemRangeList = spyRemoved.takeFirst().at(0).value<KItemRangeList>();
     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();
     QCOMPARE(m_model->count(), 0);
-    QVERIFY(m_model->expandedUrls().empty());
+    QVERIFY(m_model->expandedDirectories().empty());
 
-    m_dirLister->openUrl(m_testDir->url());
-    m_model->restoreExpandedUrls(allFolders);
-    QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(loadingCompleted()), DefaultTimeout));
+    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(m_model->isExpanded(0));
     QVERIFY(m_model->isExpanded(1));
     QVERIFY(!m_model->isExpanded(2));
     QVERIFY(m_model->isExpanded(3));
     QVERIFY(!m_model->isExpanded(4));
-    QCOMPARE(m_model->expandedUrls(), allFolders);
+    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.
+    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->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"
+    QCOMPARE(m_model->expandedDirectories(), allFolders);
+}
+
+void KFileItemModelTest::testExpandParentItems()
+{
+    // Create a tree structure of folders:
+    // a 1/
+    // a 1/b1/
+    // a 1/b1/c1/
+    // a2/
+    // a2/b2/
+    // a2/b2/c2/
+    // a2/b2/c2/d2/
+    QSet<QByteArray> modelRoles = m_model->roles();
+    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_model->loadDirectory(m_testDir->url());
+    QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
+
+    // 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));
+
+    // 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.
+    QCOMPARE(m_model->count(), 4);
+    QVERIFY(!m_model->isExpanded(0));
+    QVERIFY(m_model->isExpanded(1));
+    QVERIFY(m_model->isExpanded(2));
+    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));
+
+    // 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.
+    QCOMPARE(m_model->count(), 5);
+    QVERIFY(m_model->isExpanded(0));
+    QVERIFY(!m_model->isExpanded(1));
+    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<QByteArray> 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()
@@ -467,6 +630,17 @@ void KFileItemModelTest::testSorting()
     // Create some files with different sizes and modification times to check the different sorting options
     QDateTime now = QDateTime::currentDateTime();
 
+    QSet<QByteArray> 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));
@@ -474,73 +648,97 @@ void KFileItemModelTest::testSorting()
     m_testDir->createFile("e", "An even larger file", now.addDays(-4));
     m_testDir->createFile(".f");
 
-    m_dirLister->openUrl(m_testDir->url());
+    m_model->loadDirectory(m_testDir->url());
+    QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
+
+    int index = m_model->index(KUrl(m_testDir->url().url() + 'c'));
+    m_model->setExpanded(index, true);
+    QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
+
+    index = m_model->index(KUrl(m_testDir->url().url() + "c/c-2"));
+    m_model->setExpanded(index, true);
     QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
 
     // Default: Sort by Name, ascending
-    QCOMPARE(m_model->sortRole(), QByteArray("name"));
+    QCOMPARE(m_model->sortRole(), QByteArray("text"));
     QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder);
-    QVERIFY(m_model->sortFoldersFirst());
-    //QVERIFY(!m_model->showHiddenFiles());
-    QCOMPARE(itemsInModel(), QStringList() << "c" << "a" << "b" << "d" << "e");
+    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<int>)));
 
+    // 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.takeFirst().at(1).value<QList<int> >(), QList<int>() << 2 << 4 << 5 << 3 << 0 << 1 << 6 << 7);
+
     // Sort by Name, descending
+    m_model->setSortDirectoriesFirst(true);
     m_model->setSortOrder(Qt::DescendingOrder);
-    QCOMPARE(m_model->sortRole(), QByteArray("name"));
+    QCOMPARE(m_model->sortRole(), QByteArray("text"));
     QCOMPARE(m_model->sortOrder(), Qt::DescendingOrder);
-    QCOMPARE(itemsInModel(), QStringList() << "c" << "e" << "d" << "b" << "a");
-    QCOMPARE(spyItemsMoved.count(), 1);
-    QCOMPARE(spyItemsMoved.takeFirst().at(1).value<QList<int> >(), QList<int>() << 0 << 4 << 3 << 2 << 1);
+    QCOMPARE(itemsInModel(), QStringList() << "c" << "c-2" << "c-3" << "c-1" << "e" << "d" << "b" << "a");
+    QCOMPARE(spyItemsMoved.count(), 2);
+    QCOMPARE(spyItemsMoved.takeFirst().at(1).value<QList<int> >(), QList<int>() << 4 << 5 << 0 << 3 << 1 << 2 << 6 << 7);
+    QCOMPARE(spyItemsMoved.takeFirst().at(1).value<QList<int> >(), QList<int>() << 0 << 1 << 2 << 3 << 7 << 6 << 5 << 4);
 
     // Sort by Date, descending
+    m_model->setSortDirectoriesFirst(true);
     m_model->setSortRole("date");
     QCOMPARE(m_model->sortRole(), QByteArray("date"));
     QCOMPARE(m_model->sortOrder(), Qt::DescendingOrder);
-    QCOMPARE(itemsInModel(), QStringList() << "c" << "b" << "d" << "a" << "e");
+    QCOMPARE(itemsInModel(), QStringList() << "c" << "c-2" << "c-3" << "c-1" << "b" << "d" << "a" << "e");
     QCOMPARE(spyItemsMoved.count(), 1);
-    QCOMPARE(spyItemsMoved.takeFirst().at(1).value<QList<int> >(), QList<int>() << 0 << 4 << 2 << 1 << 3);
+    QCOMPARE(spyItemsMoved.takeFirst().at(1).value<QList<int> >(), QList<int>() << 0 << 1 << 2 << 3 << 7 << 5 << 4 << 6);
 
     // Sort by Date, ascending
     m_model->setSortOrder(Qt::AscendingOrder);
     QCOMPARE(m_model->sortRole(), QByteArray("date"));
     QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder);
-    QCOMPARE(itemsInModel(), QStringList() << "c" << "e" << "a" << "d" << "b");
+    QCOMPARE(itemsInModel(), QStringList() << "c" << "c-2" << "c-3" << "c-1" << "e" << "a" << "d" << "b");
     QCOMPARE(spyItemsMoved.count(), 1);
-    QCOMPARE(spyItemsMoved.takeFirst().at(1).value<QList<int> >(), QList<int>() << 0 << 4 << 3 << 2 << 1);
+    QCOMPARE(spyItemsMoved.takeFirst().at(1).value<QList<int> >(), QList<int>() << 0 << 1 << 2 << 3 << 7 << 6 << 5 << 4);
 
     // Sort by Date, ascending, 'Sort Folders First' disabled
-    m_model->setSortFoldersFirst(false);
+    m_model->setSortDirectoriesFirst(false);
     QCOMPARE(m_model->sortRole(), QByteArray("date"));
     QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder);
-    QVERIFY(!m_model->sortFoldersFirst());
-    QCOMPARE(itemsInModel(), QStringList() << "e" << "a" << "c" << "d" << "b");
+    QVERIFY(!m_model->sortDirectoriesFirst());
+    QCOMPARE(itemsInModel(), QStringList() << "e" << "a" << "c" << "c-1" << "c-2" << "c-3" << "d" << "b");
     QCOMPARE(spyItemsMoved.count(), 1);
-    QCOMPARE(spyItemsMoved.takeFirst().at(1).value<QList<int> >(), QList<int>() << 2 << 0 << 1 << 3 << 4);
+    QCOMPARE(spyItemsMoved.takeFirst().at(1).value<QList<int> >(), QList<int>() << 2 << 4 << 5 << 3 << 0 << 1 << 6 << 7);
 
     // Sort by Name, ascending, 'Sort Folders First' disabled
-    m_model->setSortRole("name");
+    m_model->setSortRole("text");
     QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder);
-    QVERIFY(!m_model->sortFoldersFirst());
-    QCOMPARE(itemsInModel(), QStringList() << "a" << "b" << "c" << "d" << "e");
+    QVERIFY(!m_model->sortDirectoriesFirst());
+    QCOMPARE(itemsInModel(), QStringList() << "a" << "b" << "c" << "c-1" << "c-2" << "c-3" << "d" << "e");
     QCOMPARE(spyItemsMoved.count(), 1);
-    QCOMPARE(spyItemsMoved.takeFirst().at(1).value<QList<int> >(), QList<int>() << 4 << 0 << 2 << 3 << 1);
+    QCOMPARE(spyItemsMoved.takeFirst().at(1).value<QList<int> >(), QList<int>() << 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->sortFoldersFirst());
-    QCOMPARE(itemsInModel(), QStringList() << "c" << "a" << "b" << "e" << "d");
+    QVERIFY(!m_model->sortDirectoriesFirst());
+    QCOMPARE(itemsInModel(), QStringList() << "c" << "c-2" << "c-3" << "c-1" << "a" << "b" << "e" << "d");
     QCOMPARE(spyItemsMoved.count(), 1);
-    QCOMPARE(spyItemsMoved.takeFirst().at(1).value<QList<int> >(), QList<int>() << 1 << 2 << 0 << 4 << 3);
+    QCOMPARE(spyItemsMoved.takeFirst().at(1).value<QList<int> >(), QList<int>() << 4 << 5 << 0 << 3 << 1 << 2 << 7 << 6);
+
+    QSKIP("2 tests of testSorting() are temporary deactivated as in KFileItemModel resortAllItems() "
+          "always emits a itemsMoved() signal. Before adjusting the tests think about probably introducing "
+          "another signal", SkipSingle);
+    // Internal note: Check comment in KFileItemModel::resortAllItems() for details.
 
     // In 'Sort by Size' mode, folders are always first -> changing 'Sort Folders First' does not resort the model
-    m_model->setSortFoldersFirst(true);
+    m_model->setSortDirectoriesFirst(true);
     QCOMPARE(m_model->sortRole(), QByteArray("size"));
     QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder);
-    QVERIFY(m_model->sortFoldersFirst());
+    QVERIFY(m_model->sortDirectoriesFirst());
     QCOMPARE(itemsInModel(), QStringList() << "c" << "a" << "b" << "e" << "d");
     QCOMPARE(spyItemsMoved.count(), 0);
 
@@ -548,7 +746,7 @@ void KFileItemModelTest::testSorting()
     m_model->setSortOrder(Qt::DescendingOrder);
     QCOMPARE(m_model->sortRole(), QByteArray("size"));
     QCOMPARE(m_model->sortOrder(), Qt::DescendingOrder);
-    QVERIFY(m_model->sortFoldersFirst());
+    QVERIFY(m_model->sortDirectoriesFirst());
     QCOMPARE(itemsInModel(), QStringList() << "c" << "d" << "e" << "b" << "a");
     QCOMPARE(spyItemsMoved.count(), 1);
     QCOMPARE(spyItemsMoved.takeFirst().at(1).value<QList<int> >(), QList<int>() << 0 << 4 << 3 << 2 << 1);
@@ -556,39 +754,13 @@ void KFileItemModelTest::testSorting()
     // TODO: Sort by other roles; show/hide hidden files
 }
 
-void KFileItemModelTest::testExpansionLevelsCompare_data()
-{
-    QTest::addColumn<QString>("urlA");
-    QTest::addColumn<QString>("urlB");
-    QTest::addColumn<int>("result");
-
-    QTest::newRow("Equal") << "/a/b" << "/a/b" << 0;
-    QTest::newRow("Sub path: A < B") << "/a/b" << "/a/b/c" << -1;
-    QTest::newRow("Sub path: A > B") << "/a/b/c" << "/a/b" << +1;
-    QTest::newRow("Same level: /a/1 < /a-1/1") << "/a/1" << "/a-1/1" << -1;
-    QTest::newRow("Same level: /a-/1 > /a/1") << "/a-1/1" << "/a/1" << +1;
-    QTest::newRow("Different levels: /a/a/1 < /a/a-1") << "/a/a/1" << "/a/a-1" << -1;
-    QTest::newRow("Different levels: /a/a-1 > /a/a/1") << "/a/a-1" << "/a/a/1" << +1;
-}
-
-void KFileItemModelTest::testExpansionLevelsCompare()
-{
-    QFETCH(QString, urlA);
-    QFETCH(QString, urlB);
-    QFETCH(int, result);
-
-    const KFileItem a(KUrl(urlA), QString(), mode_t(-1));
-    const KFileItem b(KUrl(urlB), QString(), mode_t(-1));
-    QCOMPARE(m_model->expansionLevelsCompare(a, b), result);
-}
-
 void KFileItemModelTest::testIndexForKeyboardSearch()
 {
     QStringList files;
     files << "a" << "aa" << "Image.jpg" << "Image.png" << "Text" << "Text1" << "Text2" << "Text11";
     m_testDir->createFiles(files);
 
-    m_dirLister->openUrl(m_testDir->url());
+    m_model->loadDirectory(m_testDir->url());
     QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
 
     // Search from index 0
@@ -632,33 +804,339 @@ void KFileItemModelTest::testIndexForKeyboardSearch()
     // TODO: Maybe we should also test keyboard searches in directories which are not sorted by Name?
 }
 
-bool KFileItemModelTest::isModelConsistent() const
+void KFileItemModelTest::testNameFilter()
 {
-    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 << "A1" << "A2" << "Abc" << "Bcd" << "Cde";
+    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));
+
+    m_model->setNameFilter("A"); // Shows A1, A2 and Abc
+    QCOMPARE(m_model->count(), 3);
+
+    m_model->setNameFilter("A2"); // Shows only A2
+    QCOMPARE(m_model->count(), 1);
+
+    m_model->setNameFilter("A2"); // Shows only A1
+    QCOMPARE(m_model->count(), 1);
+
+    m_model->setNameFilter("Bc"); // Shows "Abc" and "Bcd"
+    QCOMPARE(m_model->count(), 2);
+
+    m_model->setNameFilter("bC"); // Shows "Abc" and "Bcd"
+    QCOMPARE(m_model->count(), 2);
+
+    m_model->setNameFilter(QString()); // Shows again all items
+    QCOMPARE(m_model->count(), 5);
+}
+
+/**
+ * Verifies that we do not crash when adding a KFileItem with an empty path.
+ * Before this issue was fixed, KFileItemModel::expandedParentsCountCompare()
+ * tried to always read the first character of the path, even if the path is empty.
+ */
+void KFileItemModelTest::testEmptyPath()
+{
+    QSet<QByteArray> roles;
+    roles.insert("text");
+    roles.insert("isExpanded");
+    roles.insert("isExpandable");
+    roles.insert("expandedParentsCount");
+    m_model->setRoles(roles);
+
+    const KUrl emptyUrl;
+    QVERIFY(emptyUrl.path().isEmpty());
+    
+    const KUrl url("file:///test/");
+    
+    KFileItemList items;
+    items << KFileItem(emptyUrl, QString(), KFileItem::Unknown) << KFileItem(url, QString(), KFileItem::Unknown);
+    m_model->slotItemsAdded(emptyUrl, items);
+    m_model->slotCompleted();
+}
+
+/**
+ * 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()
+{
+    QSet<QByteArray> modelRoles = m_model->roles();
+    modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount";
+    m_model->setRoles(modelRoles);
+
+    QStringList files;
+    files << "a/1" << "a/2" << "3" << "4";
+    m_testDir->createFiles(files);
+
+    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<QByteArray>)));
+
+    const KFileItem item = m_model->fileItem(0);
+    m_model->slotRefreshItems(QList<QPair<KFileItem, KFileItem> >() << 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<KItemRangeList>();
+    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<KItemRangeList>();
+    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<KItemRangeList>();
+    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<KItemRangeList>();
+    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<QByteArray> 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<QByteArray> 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"
 
-    return true;
+    // 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<QByteArray> 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<QVariant> arguments = itemsRemovedSpy.takeFirst();
+    KItemRangeList itemRangeList = arguments.at(0).value<KItemRangeList>();
+    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<KItemRangeList>();
+    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<KItemRangeList>();
+    QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 2));
+
+    // Clear filter, verify that no items reappear.
+    m_model->setNameFilter(QString());
+    QCOMPARE(itemsInModel(), QStringList() << "parent1");
 }
 
 QStringList KFileItemModelTest::itemsInModel() const
 {
     QStringList items;
-
     for (int i = 0; i < m_model->count(); i++) {
-        items << m_model->data(i).value("name").toString();
+        items << m_model->data(i).value("text").toString();
     }
-
     return items;
 }