+/**
+ * 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"
+
+ // 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");