+void KFileItemModelTest::testCreateMimeData()
+{
+ QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
+
+ QSet<QByteArray> modelRoles = m_model->roles();
+ modelRoles << "isExpanded"
+ << "isExpandable"
+ << "expandedParentsCount";
+ m_model->setRoles(modelRoles);
+
+ m_testDir->createFile("a/1");
+
+ m_model->loadDirectory(m_testDir->url());
+ QVERIFY(itemsInsertedSpy.wait());
+ QCOMPARE(itemsInModel(), QStringList() << "a");
+
+ // Expand "a/".
+ m_model->setExpanded(0, true);
+ 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);
+ delete mimeData;
+}
+
+void KFileItemModelTest::testCollapseFolderWhileLoading()
+{
+ QSignalSpy itemsInsertedSpy(m_model, &KFileItemModel::itemsInserted);
+
+ QSet<QByteArray> 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<QByteArray> originalModelRoles = m_model->roles();
+ QSet<QByteArray> 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<KItemRangeList>();
+ 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>({QUrl::fromLocalFile(m_testDir->path() + "/a")}));
+
+ QCOMPARE(itemsInsertedSpy.count(), 1);
+ {
+ KItemRangeList itemRangeList = itemsInsertedSpy.takeFirst().at(0).value<KItemRangeList>();
+ 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<KItemRangeList>();
+ 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);