/* This file is part of the KDE project SPDX-FileCopyrightText: 2006-2007 David Faure SPDX-License-Identifier: LGPL-2.0-or-later */ #include #include "jobuidelegatefactory.h" #include "kdirmodeltest.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_UNIX #include #endif #include "kiotesthelper.h" #include "mockcoredelegateextensions.h" QTEST_MAIN(KDirModelTest) static QString specialChars() { #ifndef Q_OS_WIN return QStringLiteral(" special chars%:.pdf"); #else return QStringLiteral(" special chars%.pdf"); #endif } Q_DECLARE_METATYPE(KFileItemList) void KDirModelTest::initTestCase() { qputenv("LC_ALL", "en_US.UTF-8"); QStandardPaths::setTestModeEnabled(true); qRegisterMetaType("KFileItemList"); m_dirModelForExpand = nullptr; m_dirModel = nullptr; s_referenceTimeStamp = QDateTime::currentDateTime().addSecs(-30); // 30 seconds ago m_topLevelFileNames << QStringLiteral("toplevelfile_1") << QStringLiteral("toplevelfile_2") << QStringLiteral("toplevelfile_3") << specialChars(); recreateTestData(); fillModel(false); } void KDirModelTest::recreateTestData() { if (m_tempDir) { qDebug() << "Deleting old tempdir" << m_tempDir->path(); m_tempDir.reset(); qApp->processEvents(); // process inotify events so they don't pollute us later on } m_tempDir = std::make_unique(homeTmpDir()); qDebug() << "new tmp dir:" << m_tempDir->path(); // Create test data: /* * PATH/toplevelfile_1 * PATH/toplevelfile_2 * PATH/toplevelfile_3 * PATH/special chars%:.pdf * PATH/.hiddenfile * PATH/.hiddenfile2 * PATH/subdir * PATH/subdir/testfile * PATH/subdir/testsymlink * PATH/subdir/subsubdir * PATH/subdir/subsubdir/testfile * PATH/subdir/hasChildren * PATH/subdir/hasChildren/emptyDir * PATH/subdir/hasChildren/hiddenfileDir * PATH/subdir/hasChildren/hiddenfileDir/.hidden * PATH/subdir/hasChildren/hiddenDirDir * PATH/subdir/hasChildren/hiddenDirDir/.hidden * PATH/subdir/hasChildren/symlinkDir * PATH/subdir/hasChildren/symlinkDir/link * PATH/subdir/hasChildren/pipeDir * PATH/subdir/hasChildren/pipeDir/pipe */ const QString path = m_tempDir->path() + '/'; for (const QString &f : std::as_const(m_topLevelFileNames)) { createTestFile(path + f); } createTestFile(path + ".hiddenfile"); createTestFile(path + ".hiddenfile2"); createTestDirectory(path + "subdir"); createTestDirectory(path + "subdir/subsubdir", NoSymlink); createTestDirectory(path + "subdir/hasChildren", Empty); createTestDirectory(path + "subdir/hasChildren/emptyDir", Empty); createTestDirectory(path + "subdir/hasChildren/hiddenfileDir", Empty); createTestFile(path + "subdir/hasChildren/hiddenfileDir/.hidden"); createTestDirectory(path + "subdir/hasChildren/hiddenDirDir", Empty); createTestDirectory(path + "subdir/hasChildren/hiddenDirDir/.hidden", Empty); createTestDirectory(path + "subdir/hasChildren/symlinkDir", Empty); createTestSymlink(path + "subdir/hasChildren/symlinkDir/link", QString(path + "toplevelfile_1").toUtf8()); createTestDirectory(path + "subdir/hasChildren/pipeDir", Empty); createTestPipe(path + "subdir/hasChildren/pipeDir/pipe"); m_dirIndex = QModelIndex(); m_fileIndex = QModelIndex(); m_secondFileIndex = QModelIndex(); } void KDirModelTest::cleanupTestCase() { m_tempDir.reset(); delete m_dirModel; m_dirModel = nullptr; delete m_dirModelForExpand; m_dirModelForExpand = nullptr; } void KDirModelTest::fillModel(bool reload, bool expectAllIndexes) { if (!m_dirModel) { m_dirModel = new KDirModel; } m_dirModel->dirLister()->setAutoErrorHandlingEnabled(false); const QString path = m_tempDir->path() + '/'; KDirLister *dirLister = m_dirModel->dirLister(); qDebug() << "Calling openUrl"; m_dirModel->openUrl(QUrl::fromLocalFile(path), reload ? KDirModel::Reload : KDirModel::NoFlags); QSignalSpy spyCompleted(dirLister, qOverload<>(&KCoreDirLister::completed)); QVERIFY(spyCompleted.wait()); if (expectAllIndexes) { collectKnownIndexes(); } } // Called after test function void KDirModelTest::cleanup() { if (m_dirModel) { disconnect(m_dirModel->dirLister(), nullptr, this, nullptr); m_dirModel->dirLister()->setNameFilter(QString()); m_dirModel->dirLister()->setMimeFilter(QStringList()); m_dirModel->dirLister()->emitChanges(); } } void KDirModelTest::collectKnownIndexes() { m_dirIndex = QModelIndex(); m_fileIndex = QModelIndex(); m_secondFileIndex = QModelIndex(); // Create the indexes once and for all // The trouble is that the order of listing is undefined, one can get 1/2/3/subdir or subdir/3/2/1 for instance. for (int row = 0; row < m_topLevelFileNames.count() + 1 /*subdir*/; ++row) { QModelIndex idx = m_dirModel->index(row, 0, QModelIndex()); QVERIFY(idx.isValid()); KFileItem item = m_dirModel->itemForIndex(idx); qDebug() << item.url() << "isDir=" << item.isDir(); QString fileName = item.url().fileName(); if (item.isDir()) { m_dirIndex = idx; } else if (fileName == QLatin1String("toplevelfile_1")) { m_fileIndex = idx; } else if (fileName == QLatin1String("toplevelfile_2")) { m_secondFileIndex = idx; } else if (fileName.startsWith(QLatin1String(" special"))) { m_specialFileIndex = idx; } } QVERIFY(m_dirIndex.isValid()); QVERIFY(m_fileIndex.isValid()); QVERIFY(m_secondFileIndex.isValid()); QVERIFY(m_specialFileIndex.isValid()); // Now list subdir/ QSignalSpy spyCompleted(m_dirModel->dirLister(), qOverload<>(&KCoreDirLister::completed)); QVERIFY(m_dirModel->canFetchMore(m_dirIndex)); m_dirModel->fetchMore(m_dirIndex); qDebug() << "Listing subdir/"; if (spyCompleted.isEmpty()) { QVERIFY(spyCompleted.wait()); } // Index of a file inside a directory (subdir/testfile) QModelIndex subdirIndex; m_fileInDirIndex = QModelIndex(); for (int row = 0; row < 4; ++row) { QModelIndex idx = m_dirModel->index(row, 0, m_dirIndex); const KFileItem item = m_dirModel->itemForIndex(idx); if (item.isDir() && item.name() == QLatin1String("subsubdir")) { subdirIndex = idx; } else if (item.name() == QLatin1String("testfile")) { m_fileInDirIndex = idx; } } // List subdir/subsubdir QVERIFY(m_dirModel->canFetchMore(subdirIndex)); qDebug() << "Listing subdir/subsubdir"; spyCompleted.clear(); m_dirModel->fetchMore(subdirIndex); QVERIFY(spyCompleted.wait()); // Index of ... well, subdir/subsubdir/testfile m_fileInSubdirIndex = m_dirModel->index(0, 0, subdirIndex); } void KDirModelTest::testRowCount() { const int topLevelRowCount = m_dirModel->rowCount(); QCOMPARE(topLevelRowCount, m_topLevelFileNames.count() + 1 /*subdir*/); const int subdirRowCount = m_dirModel->rowCount(m_dirIndex); QCOMPARE(subdirRowCount, 4); QVERIFY(m_fileIndex.isValid()); const int fileRowCount = m_dirModel->rowCount(m_fileIndex); // #176555 QCOMPARE(fileRowCount, 0); } void KDirModelTest::testIndex() { QVERIFY(m_dirModel->hasChildren()); // Index of the first file QVERIFY(m_fileIndex.isValid()); QCOMPARE(m_fileIndex.model(), static_cast(m_dirModel)); // QCOMPARE(m_fileIndex.row(), 0); QCOMPARE(m_fileIndex.column(), 0); QVERIFY(!m_fileIndex.parent().isValid()); QVERIFY(!m_dirModel->hasChildren(m_fileIndex)); // Index of a directory QVERIFY(m_dirIndex.isValid()); QCOMPARE(m_dirIndex.model(), static_cast(m_dirModel)); // QCOMPARE(m_dirIndex.row(), 3); // ordering isn't guaranteed QCOMPARE(m_dirIndex.column(), 0); QVERIFY(!m_dirIndex.parent().isValid()); QVERIFY(m_dirModel->hasChildren(m_dirIndex)); // Index of a file inside a directory (subdir/testfile) QVERIFY(m_fileInDirIndex.isValid()); QCOMPARE(m_fileInDirIndex.model(), static_cast(m_dirModel)); // QCOMPARE(m_fileInDirIndex.row(), 0); // ordering isn't guaranteed QCOMPARE(m_fileInDirIndex.column(), 0); QVERIFY(m_fileInDirIndex.parent() == m_dirIndex); QVERIFY(!m_dirModel->hasChildren(m_fileInDirIndex)); // Index of subdir/subsubdir/testfile QVERIFY(m_fileInSubdirIndex.isValid()); QCOMPARE(m_fileInSubdirIndex.model(), static_cast(m_dirModel)); QCOMPARE(m_fileInSubdirIndex.row(), 0); // we can check it because it's the only file there QCOMPARE(m_fileInSubdirIndex.column(), 0); QVERIFY(m_fileInSubdirIndex.parent().parent() == m_dirIndex); QVERIFY(!m_dirModel->hasChildren(m_fileInSubdirIndex)); // Test sibling() by going from subdir/testfile to subdir/subsubdir const QModelIndex subsubdirIndex = m_fileInSubdirIndex.parent(); QVERIFY(subsubdirIndex.isValid()); QModelIndex sibling1 = m_dirModel->sibling(subsubdirIndex.row(), 0, m_fileInDirIndex); QVERIFY(sibling1.isValid()); QVERIFY(sibling1 == subsubdirIndex); QVERIFY(m_dirModel->hasChildren(subsubdirIndex)); // Invalid sibling call QVERIFY(!m_dirModel->sibling(2, 0, m_fileInSubdirIndex).isValid()); // Test index() with a valid parent (dir). QModelIndex index2 = m_dirModel->index(m_fileInSubdirIndex.row(), m_fileInSubdirIndex.column(), subsubdirIndex); QVERIFY(index2.isValid()); QVERIFY(index2 == m_fileInSubdirIndex); // Test index() with a non-parent (file). QModelIndex index3 = m_dirModel->index(m_fileInSubdirIndex.row(), m_fileInSubdirIndex.column(), m_fileIndex); QVERIFY(!index3.isValid()); } void KDirModelTest::testNames() { QString fileName = m_dirModel->data(m_fileIndex, Qt::DisplayRole).toString(); QCOMPARE(fileName, QString("toplevelfile_1")); QString specialFileName = m_dirModel->data(m_specialFileIndex, Qt::DisplayRole).toString(); QCOMPARE(specialFileName, specialChars()); QString dirName = m_dirModel->data(m_dirIndex, Qt::DisplayRole).toString(); QCOMPARE(dirName, QString("subdir")); QString fileInDirName = m_dirModel->data(m_fileInDirIndex, Qt::DisplayRole).toString(); QCOMPARE(fileInDirName, QString("testfile")); QString fileInSubdirName = m_dirModel->data(m_fileInSubdirIndex, Qt::DisplayRole).toString(); QCOMPARE(fileInSubdirName, QString("testfile")); } void KDirModelTest::testItemForIndex() { // root item KFileItem rootItem = m_dirModel->itemForIndex(QModelIndex()); QVERIFY(!rootItem.isNull()); QCOMPARE(rootItem.name(), QString(".")); KFileItem fileItem = m_dirModel->itemForIndex(m_fileIndex); QVERIFY(!fileItem.isNull()); QCOMPARE(fileItem.name(), QString("toplevelfile_1")); QVERIFY(!fileItem.isDir()); QCOMPARE(fileItem.url().toLocalFile(), QString(m_tempDir->path() + "/toplevelfile_1")); KFileItem dirItem = m_dirModel->itemForIndex(m_dirIndex); QVERIFY(!dirItem.isNull()); QCOMPARE(dirItem.name(), QString("subdir")); QVERIFY(dirItem.isDir()); QCOMPARE(dirItem.url().toLocalFile(), QString(m_tempDir->path() + "/subdir")); KFileItem fileInDirItem = m_dirModel->itemForIndex(m_fileInDirIndex); QVERIFY(!fileInDirItem.isNull()); QCOMPARE(fileInDirItem.name(), QString("testfile")); QVERIFY(!fileInDirItem.isDir()); QCOMPARE(fileInDirItem.url().toLocalFile(), QString(m_tempDir->path() + "/subdir/testfile")); KFileItem fileInSubdirItem = m_dirModel->itemForIndex(m_fileInSubdirIndex); QVERIFY(!fileInSubdirItem.isNull()); QCOMPARE(fileInSubdirItem.name(), QString("testfile")); QVERIFY(!fileInSubdirItem.isDir()); QCOMPARE(fileInSubdirItem.url().toLocalFile(), QString(m_tempDir->path() + "/subdir/subsubdir/testfile")); } void KDirModelTest::testIndexForItem() { KFileItem rootItem = m_dirModel->itemForIndex(QModelIndex()); QModelIndex rootIndex = m_dirModel->indexForItem(rootItem); QVERIFY(!rootIndex.isValid()); KFileItem fileItem = m_dirModel->itemForIndex(m_fileIndex); QModelIndex fileIndex = m_dirModel->indexForItem(fileItem); QCOMPARE(fileIndex, m_fileIndex); KFileItem dirItem = m_dirModel->itemForIndex(m_dirIndex); QModelIndex dirIndex = m_dirModel->indexForItem(dirItem); QCOMPARE(dirIndex, m_dirIndex); KFileItem fileInDirItem = m_dirModel->itemForIndex(m_fileInDirIndex); QModelIndex fileInDirIndex = m_dirModel->indexForItem(fileInDirItem); QCOMPARE(fileInDirIndex, m_fileInDirIndex); KFileItem fileInSubdirItem = m_dirModel->itemForIndex(m_fileInSubdirIndex); QModelIndex fileInSubdirIndex = m_dirModel->indexForItem(fileInSubdirItem); QCOMPARE(fileInSubdirIndex, m_fileInSubdirIndex); } void KDirModelTest::testData() { // First file QModelIndex idx1col0 = m_dirModel->index(m_fileIndex.row(), 0, QModelIndex()); QCOMPARE(idx1col0.data().toString(), QString("toplevelfile_1")); QModelIndex idx1col1 = m_dirModel->index(m_fileIndex.row(), 1, QModelIndex()); QString size1 = m_dirModel->data(idx1col1, Qt::DisplayRole).toString(); QCOMPARE(size1, QString("11 B")); KFileItem item = m_dirModel->data(m_fileIndex, KDirModel::FileItemRole).value(); KFileItem fileItem = m_dirModel->itemForIndex(m_fileIndex); QCOMPARE(item, fileItem); QCOMPARE(m_dirModel->data(m_fileIndex, KDirModel::ChildCountRole).toInt(), (int)KDirModel::ChildCountUnknown); // Second file QModelIndex idx2col0 = m_dirModel->index(m_secondFileIndex.row(), 0, QModelIndex()); QString display2 = m_dirModel->data(idx2col0, Qt::DisplayRole).toString(); QCOMPARE(display2, QString("toplevelfile_2")); // Subdir: check child count QCOMPARE(m_dirModel->data(m_dirIndex, KDirModel::ChildCountRole).toInt(), 4); // Subsubdir: check child count QCOMPARE(m_dirModel->data(m_fileInSubdirIndex.parent(), KDirModel::ChildCountRole).toInt(), 1); } void KDirModelTest::testIcon() { /** * Create test data only used in testIcon() * PATH/subdirwithcustomicon * PATH/subdirwithcustomicon/.directory * PATH/subdirwithcustomicon/exampleIcon.svg * PATH/subdirwithcustomicon/subdirwithinvalidicon * PATH/subdirwithcustomicon/subdirwithinvalidicon/.directory * PATH/subdirwithcustomicon/subdirwithinvalidsvgicon * PATH/subdirwithcustomicon/subdirwithinvalidsvgicon/.directory */ const QString path(m_tempDir->path() + '/'); const QString subdirWithCustomIconPath(path + "subdirwithcustomicon/"); createTestDirectory(path + "subdirwithcustomicon/", Empty); const QString exampleIconPath = subdirWithCustomIconPath + "exampleIcon.svg"; const QByteArray exampleSvgIconContent( "" "" ""); const QByteArray directoryFileContent( "[Desktop Entry]\n" "Icon=" + exampleIconPath.toLatin1()); struct Entry { bool isDir; QString name; QByteArray content; }; const std::array topLevelContent{{ {false, exampleIconPath, exampleSvgIconContent}, {false, subdirWithCustomIconPath + ".directory", directoryFileContent}, {true, subdirWithCustomIconPath + "subdirwithinvalidicon", QByteArray()}, {true, subdirWithCustomIconPath + "subdirwithinvalidsvgicon", QByteArray()}, }}; for (const auto &entry : topLevelContent) { if (entry.isDir) { createTestDirectory(entry.name, Empty); } else { createTestFile(entry.name, false, entry.content); } } // Add an extra suffix to the end of the path to make it become invalid const QByteArray directoryFileWithInvalidIconContent( "[Desktop Entry]\n" "Icon=" + exampleIconPath.toLatin1() + ".png"); createTestFile(subdirWithCustomIconPath + "subdirwithinvalidicon/.directory", false, directoryFileWithInvalidIconContent); // Add an extra ".svg" to the end of the path to make it become invalid const QByteArray directoryFileWithInvalidSvgIconContent( "[Desktop Entry]\n" "Icon=" + exampleIconPath.toLatin1() + ".svg"); createTestFile(subdirWithCustomIconPath + "subdirwithinvalidsvgicon/.directory", false, directoryFileWithInvalidSvgIconContent); // Refresh the directory after adding files KDirLister *dirLister = m_dirModel->dirLister(); QSignalSpy completedSpy(dirLister, qOverload<>(&KCoreDirLister::completed)); dirLister->openUrl(QUrl::fromLocalFile(path), KDirLister::Reload); QVERIFY(completedSpy.wait()); completedSpy.clear(); QModelIndex dirWithIconIndex; // subdirwithcustomicon for (int row = 0; row < m_topLevelFileNames.count() + 2 /*subdir, subdirwithinvalidicon*/; ++row) { const QModelIndex idx = m_dirModel->index(row, 0, QModelIndex()); QVERIFY(idx.isValid()); const KFileItem item = m_dirModel->itemForIndex(idx); if (item.isDir() && item.name() == QStringLiteral("subdirwithcustomicon")) { dirWithIconIndex = idx; break; } } QVERIFY(dirWithIconIndex.isValid()); // Now list subdirwithcustomicon/ QVERIFY(m_dirModel->canFetchMore(dirWithIconIndex)); qDebug() << "Listing subdirwithcustomicon"; m_dirModel->fetchMore(dirWithIconIndex); QVERIFY(completedSpy.wait()); completedSpy.clear(); // Indexes of files inside a directory QModelIndex dirWithInvalidIconIndex; // subdirwithcustomicon/subdirwithinvalidicon QModelIndex dirWithInvalidSvgIconIndex; // subdirwithcustomicon/subdirwithinvalidsvgicon for (int row = 0; row < static_cast(topLevelContent.size()); ++row) { QModelIndex idx = m_dirModel->index(row, 0, dirWithIconIndex); const KFileItem item = m_dirModel->itemForIndex(idx); if (item.isDir()) { if (item.name() == QLatin1String("subdirwithinvalidicon")) { dirWithInvalidIconIndex = idx; } else if (item.name() == QLatin1String("subdirwithinvalidsvgicon")) { dirWithInvalidSvgIconIndex = idx; } } } QVERIFY(dirWithInvalidIconIndex.isValid()); QVERIFY(dirWithInvalidSvgIconIndex.isValid()); // Create an expected icon image const QSize comparedSize(100, 100); const QImage expectedIconImage = QIcon(exampleIconPath).pixmap(comparedSize).toImage(); // Test the custom icon is correctly loaded for the directory const QIcon icon = m_dirModel->data(dirWithIconIndex, Qt::DecorationRole).value(); QCOMPARE(m_dirModel->itemForIndex(dirWithIconIndex).iconName(), exampleIconPath); QVERIFY(icon.pixmap(comparedSize).toImage() == expectedIconImage); // Test "unknown" icon will be used when the icon path refers to an invalid icon const QIcon icon2 = m_dirModel->data(dirWithInvalidIconIndex, Qt::DecorationRole).value(); QCOMPARE(icon2.name(), "unknown"); // Test "unknown" icon will be used when the icon path refers to an invalid svg icon const QIcon icon3 = m_dirModel->data(dirWithInvalidSvgIconIndex, Qt::DecorationRole).value(); QCOMPARE(icon3.name(), "unknown"); // Delete the folder QDir dir(subdirWithCustomIconPath); QVERIFY(dir.removeRecursively()); // Refresh the directory again after deleting the folder dirLister->openUrl(QUrl::fromLocalFile(path), KDirLister::Reload); QVERIFY(completedSpy.wait()); completedSpy.clear(); } void KDirModelTest::testReload() { fillModel(true); testItemForIndex(); } // We want more info than just "the values differ", if they do. /* clang-format off */ #define COMPARE_INDEXES(a, b) \ QCOMPARE(a.row(), b.row()); \ QCOMPARE(a.column(), b.column()); \ QCOMPARE(a.model(), b.model()); \ QCOMPARE(a.parent().isValid(), b.parent().isValid()); \ QCOMPARE(a, b); /* clang-format on */ void KDirModelTest::testModifyFile() { const QString file = m_tempDir->path() + "/toplevelfile_2"; QSignalSpy spyDataChanged(m_dirModel, &QAbstractItemModel::dataChanged); // "Touch" the file setTimeStamp(file, s_referenceTimeStamp.addSecs(20)); // In stat mode, kdirwatch doesn't notice file changes; we need to trigger it // by creating a file. // createTestFile(m_tempDir->path() + "/toplevelfile_5"); KDirWatch::self()->setDirty(m_tempDir->path()); // Wait for KDirWatch to notify the change (especially when using Stat) if (spyDataChanged.isEmpty()) { QVERIFY(spyDataChanged.wait()); } // If we come here, then dataChanged() was emitted - all good. const QVariantList dataChanged = spyDataChanged[0]; QModelIndex receivedIndex = dataChanged[0].value(); COMPARE_INDEXES(receivedIndex, m_secondFileIndex); receivedIndex = dataChanged[1].value(); QCOMPARE(receivedIndex.row(), m_secondFileIndex.row()); // only compare row; column is count-1 } void KDirModelTest::testRenameFile() { const QUrl url = QUrl::fromLocalFile(m_tempDir->path() + "/toplevelfile_2"); const QUrl newUrl = QUrl::fromLocalFile(m_tempDir->path() + "/toplevelfile_2_renamed"); QSignalSpy spyDataChanged(m_dirModel, &QAbstractItemModel::dataChanged); KIO::SimpleJob *job = KIO::rename(url, newUrl, KIO::HideProgressInfo); QVERIFY(job->exec()); // Wait for the DBUS signal from KDirNotify, it's the one the triggers dataChanged if (spyDataChanged.isEmpty()) { QVERIFY(spyDataChanged.wait()); } // If we come here, then dataChanged() was emitted - all good. QCOMPARE(spyDataChanged.count(), 1); COMPARE_INDEXES(spyDataChanged[0][0].value(), m_secondFileIndex); QModelIndex receivedIndex = spyDataChanged[0][1].value(); QCOMPARE(receivedIndex.row(), m_secondFileIndex.row()); // only compare row; column is count-1 // check renaming happened QCOMPARE(m_dirModel->itemForIndex(m_secondFileIndex).url().toString(), newUrl.toString()); // check that KDirLister::cachedItemForUrl won't give a bad name if copying that item (#195385) KFileItem cachedItem = KDirLister::cachedItemForUrl(newUrl); QVERIFY(!cachedItem.isNull()); QCOMPARE(cachedItem.name(), QString("toplevelfile_2_renamed")); QCOMPARE(cachedItem.entry().stringValue(KIO::UDSEntry::UDS_NAME), QString("toplevelfile_2_renamed")); // Put things back to normal spyDataChanged.clear(); job = KIO::rename(newUrl, url, KIO::HideProgressInfo); QVERIFY(job->exec()); // Wait for the DBUS signal from KDirNotify, it's the one the triggers dataChanged if (spyDataChanged.isEmpty()) { QVERIFY(spyDataChanged.wait()); } QCOMPARE(m_dirModel->itemForIndex(m_secondFileIndex).url().toString(), url.toString()); } void KDirModelTest::testMoveDirectory() { testMoveDirectory(QStringLiteral("subdir")); } void KDirModelTest::testMoveDirectory(const QString &dir /*just a dir name, no slash*/) { const QString path = m_tempDir->path() + '/'; const QString srcdir = path + dir; QVERIFY(QDir(srcdir).exists()); QTemporaryDir destDir(homeTmpDir()); const QString dest = destDir.path() + '/'; QVERIFY(QDir(dest).exists()); // Move qDebug() << "Moving" << srcdir << "to" << dest; KIO::CopyJob *job = KIO::move(QUrl::fromLocalFile(srcdir), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); job->setUiDelegate(nullptr); job->setUiDelegateExtension(nullptr); QVERIFY(job->exec()); // wait for kdirnotify QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); if (spyRowsRemoved.isEmpty()) { QVERIFY(spyRowsRemoved.wait()); } QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir")).isValid()); QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed")).isValid()); // Move back qDebug() << "Moving" << dest + dir << "back to" << srcdir; job = KIO::move(QUrl::fromLocalFile(dest + dir), QUrl::fromLocalFile(srcdir), KIO::HideProgressInfo); job->setUiDelegate(nullptr); job->setUiDelegateExtension(nullptr); QVERIFY(job->exec()); QSignalSpy spyRowsInserted(m_dirModel, &QAbstractItemModel::rowsInserted); if (spyRowsInserted.isEmpty()) { QVERIFY(spyRowsInserted.wait()); } QVERIFY(QDir(srcdir).exists()); // m_dirIndex is invalid after the above... fillModel(true); } void KDirModelTest::testRenameDirectory() // #172945, #174703, (and #180156) { const QString path = m_tempDir->path() + '/'; const QUrl url = QUrl::fromLocalFile(path + "subdir"); const QUrl newUrl = QUrl::fromLocalFile(path + "subdir_renamed"); // For #180156 we need a second kdirmodel, viewing the subdir being renamed. // I'm abusing m_dirModelForExpand for that purpose. delete m_dirModelForExpand; m_dirModelForExpand = new KDirModel; KDirLister *dirListerForExpand = m_dirModelForExpand->dirLister(); QSignalSpy spyCompleted(dirListerForExpand, qOverload<>(&KCoreDirLister::completed)); dirListerForExpand->openUrl(url); // async QVERIFY(spyCompleted.wait()); // Now do the renaming QSignalSpy spyDataChanged(m_dirModel, &QAbstractItemModel::dataChanged); KIO::SimpleJob *job = KIO::rename(url, newUrl, KIO::HideProgressInfo); QVERIFY(job->exec()); // Wait for the DBUS signal from KDirNotify, it's the one the triggers dataChanged if (spyDataChanged.isEmpty()) { QVERIFY(spyDataChanged.wait()); } spyDataChanged.clear(); // If we come here, then dataChanged() was emitted - all good. // QCOMPARE(spyDataChanged.count(), 1); // it was in fact emitted 5 times... // COMPARE_INDEXES(spyDataChanged[0][0].value(), m_dirIndex); // QModelIndex receivedIndex = spyDataChanged[0][1].value(); // QCOMPARE(receivedIndex.row(), m_dirIndex.row()); // only compare row; column is count-1 // check renaming happened QCOMPARE(m_dirModel->itemForIndex(m_dirIndex).url().toString(), newUrl.toString()); qDebug() << newUrl << "indexForUrl=" << m_dirModel->indexForUrl(newUrl) << "m_dirIndex=" << m_dirIndex; QCOMPARE(m_dirModel->indexForUrl(newUrl), m_dirIndex); QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed")).isValid()); QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed/testfile")).isValid()); QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed/subsubdir")).isValid()); QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed/subsubdir/testfile")).isValid()); // Check the other kdirmodel got redirected QCOMPARE(dirListerForExpand->url().toLocalFile(), QString(path + "subdir_renamed")); qDebug() << "calling testMoveDirectory(subdir_renamed)"; // Test moving the renamed directory; if something inside KDirModel // wasn't properly updated by the renaming, this would detect it and crash (#180673) testMoveDirectory(QStringLiteral("subdir_renamed")); // Put things back to normal job = KIO::rename(newUrl, url, KIO::HideProgressInfo); QVERIFY(job->exec()); // Wait for the DBUS signal from KDirNotify, it's the one the triggers dataChanged if (spyDataChanged.isEmpty()) { QVERIFY(spyDataChanged.wait()); } QCOMPARE(m_dirModel->itemForIndex(m_dirIndex).url().toString(), url.toString()); QCOMPARE(m_dirModel->itemForIndex(m_dirIndex).url().toString(), url.toString()); QCOMPARE(m_dirModel->indexForUrl(url), m_dirIndex); QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir")).isValid()); QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir/testfile")).isValid()); QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir/subsubdir")).isValid()); QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir/subsubdir/testfile")).isValid()); QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed")).isValid()); QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed/testfile")).isValid()); QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed/subsubdir")).isValid()); QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed/subsubdir/testfile")).isValid()); // TODO INVESTIGATE // QCOMPARE(dirListerForExpand->url().toLocalFile(), path+"subdir"); delete m_dirModelForExpand; m_dirModelForExpand = nullptr; } void KDirModelTest::testRenameDirectoryInCache() // #188807 { // Ensure the stuff is in cache. fillModel(true); const QString path = m_tempDir->path() + '/'; QVERIFY(!m_dirModel->dirLister()->findByUrl(QUrl::fromLocalFile(path)).isNull()); // No more dirmodel nor dirlister. delete m_dirModel; m_dirModel = nullptr; // Now let's rename a directory that is in KCoreDirListerCache const QUrl url = QUrl::fromLocalFile(path); QUrl newUrl = url.adjusted(QUrl::StripTrailingSlash); newUrl.setPath(newUrl.path() + "_renamed"); qDebug() << newUrl; KIO::SimpleJob *job = KIO::rename(url, newUrl, KIO::HideProgressInfo); QVERIFY(job->exec()); // Put things back to normal job = KIO::rename(newUrl, url, KIO::HideProgressInfo); QVERIFY(job->exec()); // KDirNotify emits FileRenamed for each rename() above, which in turn // re-lists the directory. We need to wait for both signals to be emitted // otherwise the dirlister will not be in the state we expect. QTest::qWait(200); fillModel(true); QVERIFY(m_dirIndex.isValid()); KFileItem rootItem = m_dirModel->dirLister()->findByUrl(QUrl::fromLocalFile(path)); QVERIFY(!rootItem.isNull()); } void KDirModelTest::testChmodDirectory() // #53397 { #ifdef WITH_QTDBUS QSignalSpy spyDataChanged(m_dirModel, &QAbstractItemModel::dataChanged); const QString path = m_tempDir->path() + '/'; KFileItem rootItem = m_dirModel->itemForIndex(QModelIndex()); const mode_t origPerm = rootItem.permissions(); mode_t newPerm = origPerm ^ S_IWGRP; // const QFile::Permissions origPerm = rootItem.filePermissions(); // QVERIFY(origPerm & QFile::ReadOwner); // const QFile::Permissions newPerm = origPerm ^ QFile::WriteGroup; QVERIFY(newPerm != origPerm); KIO::Job *job = KIO::chmod({rootItem}, newPerm, S_IWGRP /*TODO: QFile::WriteGroup*/, QString(), QString(), false, KIO::HideProgressInfo); job->setUiDelegate(nullptr); QVERIFY(job->exec()); // ChmodJob doesn't talk to KDirNotify, kpropertiesdialog does. // [this allows to group notifications after all the changes one can make in the dialog] org::kde::KDirNotify::emitFilesChanged(QList{QUrl::fromLocalFile(path)}); // Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved if (spyDataChanged.isEmpty()) { QVERIFY(spyDataChanged.wait()); } // If we come here, then dataChanged() was emitted - all good. QCOMPARE(spyDataChanged.count(), 1); QModelIndex receivedIndex = spyDataChanged[0][0].value(); qDebug() << "receivedIndex" << receivedIndex; QVERIFY(!receivedIndex.isValid()); const KFileItem newRootItem = m_dirModel->itemForIndex(QModelIndex()); QVERIFY(!newRootItem.isNull()); QCOMPARE(QString::number(newRootItem.permissions(), 16), QString::number(newPerm, 16)); #endif } enum { NoFlag = 0, NewDir = 1, // whether to re-create a new QTemporaryDir completely, to avoid cached fileitems ListFinalDir = 2, // whether to list the target dir at the same time, like k3b, for #193364 Recreate = 4, CacheSubdir = 8, // put subdir in the cache before expandToUrl // flags, next item is 16! }; void KDirModelTest::testExpandToUrl_data() { QTest::addColumn("flags"); // see enum above QTest::addColumn("expandToPath"); // relative path QTest::addColumn("expectedExpandSignals"); QTest::newRow("the root, nothing to do") << int(NoFlag) << QString() << QStringList(); QTest::newRow(".") << int(NoFlag) << "." << (QStringList()); QTest::newRow("subdir") << int(NoFlag) << "subdir" << QStringList{QStringLiteral("subdir")}; QTest::newRow("subdir/.") << int(NoFlag) << "subdir/." << QStringList{QStringLiteral("subdir")}; const QString subsubdir = QStringLiteral("subdir/subsubdir"); // Must list root, emit expand for subdir, list subdir, emit expand for subsubdir. QTest::newRow("subdir/subsubdir") << int(NoFlag) << subsubdir << QStringList{QStringLiteral("subdir"), subsubdir}; // Must list root, emit expand for subdir, list subdir, emit expand for subsubdir, list subsubdir. const QString subsubdirfile = subsubdir + "/testfile"; QTest::newRow("subdir/subsubdir/testfile sync") << int(NoFlag) << subsubdirfile << QStringList{QStringLiteral("subdir"), subsubdir, subsubdirfile}; #ifndef Q_OS_WIN // Expand a symlink to a directory (#219547) const QString dirlink = m_tempDir->path() + "/dirlink"; createTestSymlink(dirlink, "subdir"); // dirlink -> subdir QVERIFY(QFileInfo(dirlink).isSymLink()); // If this test fails, your first move should be to enable all debug output and see if KDirWatch says inotify failed QTest::newRow("dirlink") << int(NoFlag) << "dirlink/subsubdir" << QStringList{QStringLiteral("dirlink"), QStringLiteral("dirlink/subsubdir")}; #endif // Do a cold-cache test too, but nowadays it doesn't change anything anymore, // apart from testing different code paths inside KDirLister. QTest::newRow("subdir/subsubdir/testfile with reload") << int(NewDir) << subsubdirfile << QStringList{QStringLiteral("subdir"), subsubdir, subsubdirfile}; QTest::newRow("hold dest dir") // #193364 << int(NewDir | ListFinalDir) << subsubdirfile << QStringList{QStringLiteral("subdir"), subsubdir, subsubdirfile}; // Put subdir in cache too (#175035) QTest::newRow("hold subdir and dest dir") << int(NewDir | CacheSubdir | ListFinalDir | Recreate) << subsubdirfile << QStringList{QStringLiteral("subdir"), subsubdir, subsubdirfile}; // Make sure the last test has the Recreate option set, for the subsequent test methods. } void KDirModelTest::testExpandToUrl() { QFETCH(int, flags); QFETCH(QString, expandToPath); // relative QFETCH(QStringList, expectedExpandSignals); if (flags & NewDir) { recreateTestData(); // WARNING! m_dirIndex, m_fileIndex, m_secondFileIndex etc. are not valid anymore after this point! } const QString path = m_tempDir->path() + '/'; if (flags & CacheSubdir) { // This way, the listDir for subdir will find items in cache, and will schedule a CachedItemsJob m_dirModel->dirLister()->openUrl(QUrl::fromLocalFile(path + "subdir")); QSignalSpy completedSpy(m_dirModel->dirLister(), qOverload<>(&KCoreDirLister::completed)); QVERIFY(completedSpy.wait(2000)); } if (flags & ListFinalDir) { // This way, the last listDir will find items in cache, and will schedule a CachedItemsJob m_dirModel->dirLister()->openUrl(QUrl::fromLocalFile(path + "subdir/subsubdir")); QSignalSpy completedSpy(m_dirModel->dirLister(), qOverload<>(&KCoreDirLister::completed)); QVERIFY(completedSpy.wait(2000)); } if (!m_dirModelForExpand || (flags & NewDir)) { delete m_dirModelForExpand; m_dirModelForExpand = new KDirModel; connect(m_dirModelForExpand, &KDirModel::expand, this, &KDirModelTest::slotExpand); connect(m_dirModelForExpand, &QAbstractItemModel::rowsInserted, this, &KDirModelTest::slotRowsInserted); KDirLister *dirListerForExpand = m_dirModelForExpand->dirLister(); dirListerForExpand->openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); // async } m_rowsInsertedEmitted = false; m_expectedExpandSignals = expectedExpandSignals; m_nextExpectedExpandSignals = 0; QSignalSpy spyExpand(m_dirModelForExpand, &KDirModel::expand); m_urlToExpandTo = QUrl::fromLocalFile(path + expandToPath); // If KDirModel doesn't know this URL yet, then we want to see rowsInserted signals // being emitted, so that the slots can get the index to that url then. m_expectRowsInserted = !expandToPath.isEmpty() && !m_dirModelForExpand->indexForUrl(m_urlToExpandTo).isValid(); QVERIFY(QFileInfo::exists(m_urlToExpandTo.toLocalFile())); m_dirModelForExpand->expandToUrl(m_urlToExpandTo); if (expectedExpandSignals.isEmpty()) { QTest::qWait(20); // to make sure we process queued connection calls, otherwise spyExpand.count() is always 0 even if there's a bug... QCOMPARE(spyExpand.count(), 0); } else { if (spyExpand.count() < expectedExpandSignals.count()) { QTRY_COMPARE(spyExpand.count(), expectedExpandSignals.count()); } if (m_expectRowsInserted) { QVERIFY(m_rowsInsertedEmitted); } } // Now it should exist if (!expandToPath.isEmpty() && expandToPath != QLatin1String(".")) { qDebug() << "Do I know" << m_urlToExpandTo << "?"; QVERIFY(m_dirModelForExpand->indexForUrl(m_urlToExpandTo).isValid()); } if (flags & ListFinalDir) { testUpdateParentAfterExpand(); } if (flags & Recreate) { // Clean up, for the next tests recreateTestData(); fillModel(false); } } void KDirModelTest::slotExpand(const QModelIndex &index) { QVERIFY(index.isValid()); const QString path = m_tempDir->path() + '/'; KFileItem item = m_dirModelForExpand->itemForIndex(index); QVERIFY(!item.isNull()); qDebug() << item.url().toLocalFile(); QCOMPARE(item.url().toLocalFile(), QString(path + m_expectedExpandSignals[m_nextExpectedExpandSignals++])); // if rowsInserted wasn't emitted yet, then any proxy model would be unable to do anything with index at this point if (item.url() == m_urlToExpandTo) { QVERIFY(m_dirModelForExpand->indexForUrl(m_urlToExpandTo).isValid()); if (m_expectRowsInserted) { QVERIFY(m_rowsInsertedEmitted); } } } void KDirModelTest::slotRowsInserted(const QModelIndex &, int, int) { m_rowsInsertedEmitted = true; } // This code is called by testExpandToUrl void KDirModelTest::testUpdateParentAfterExpand() // #193364 { const QString path = m_tempDir->path() + '/'; const QString file = path + "subdir/aNewFile"; qDebug() << "Creating" << file; QVERIFY(!QFile::exists(file)); createTestFile(file); QSignalSpy spyRowsInserted(m_dirModelForExpand, &QAbstractItemModel::rowsInserted); QVERIFY(spyRowsInserted.wait(1000)); } void KDirModelTest::testFilter() { QVERIFY(m_dirIndex.isValid()); const int oldTopLevelRowCount = m_dirModel->rowCount(); const int oldSubdirRowCount = m_dirModel->rowCount(m_dirIndex); QSignalSpy spyItemsFilteredByMime(m_dirModel->dirLister(), &KCoreDirLister::itemsFilteredByMime); QSignalSpy spyItemsDeleted(m_dirModel->dirLister(), &KCoreDirLister::itemsDeleted); QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); m_dirModel->dirLister()->setNameFilter(QStringLiteral("toplevel*")); QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); // no change yet QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); // no change yet m_dirModel->dirLister()->emitChanges(); QCOMPARE(m_dirModel->rowCount(), 4); // 3 toplevel* files, one subdir QCOMPARE(m_dirModel->rowCount(m_dirIndex), 2); // the files get filtered out, subsubdir and hasChildren are remaining // In the subdir, we can get rowsRemoved signals like (1,2) or (0,0)+(2,2), // depending on the order of the files in the model. // So QCOMPARE(spyRowsRemoved.count(), 3) is fragile, we rather need // to sum up the removed rows per parent directory. QMap rowsRemovedPerDir; for (int i = 0; i < spyRowsRemoved.count(); ++i) { const QVariantList args = spyRowsRemoved[i]; const QModelIndex parentIdx = args[0].value(); QString dirName; if (parentIdx.isValid()) { const KFileItem item = m_dirModel->itemForIndex(parentIdx); dirName = item.name(); } else { dirName = QStringLiteral("root"); } rowsRemovedPerDir[dirName] += args[2].toInt() - args[1].toInt() + 1; // qDebug() << parentIdx << args[1].toInt() << args[2].toInt(); } QCOMPARE(rowsRemovedPerDir.count(), 3); // once for every dir QCOMPARE(rowsRemovedPerDir.value("root"), 1); // one from toplevel ('special chars') QCOMPARE(rowsRemovedPerDir.value("subdir"), 2); // two from subdir QCOMPARE(rowsRemovedPerDir.value("subsubdir"), 1); // one from subsubdir QCOMPARE(spyItemsDeleted.count(), 3); // once for every dir QCOMPARE(spyItemsDeleted[0][0].value().count(), 1); // one from toplevel ('special chars') QCOMPARE(spyItemsDeleted[1][0].value().count(), 2); // two from subdir QCOMPARE(spyItemsDeleted[2][0].value().count(), 1); // one from subsubdir QCOMPARE(spyItemsFilteredByMime.count(), 0); spyItemsDeleted.clear(); spyItemsFilteredByMime.clear(); // Reset the filter qDebug() << "reset to no filter"; m_dirModel->dirLister()->setNameFilter(QString()); m_dirModel->dirLister()->emitChanges(); QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); QCOMPARE(spyItemsDeleted.count(), 0); QCOMPARE(spyItemsFilteredByMime.count(), 0); // The order of things changed because of filtering. // Fill again, so that m_fileIndex etc. are correct again. fillModel(true); } void KDirModelTest::testFilterPatterns() { QVERIFY(m_dirIndex.isValid()); const int oldTopLevelRowCount = m_dirModel->rowCount(); const int oldSubdirRowCount = m_dirModel->rowCount(m_dirIndex); // matching without wildcard -> no files m_dirModel->dirLister()->setNameFilter(QStringLiteral("toplevel")); QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); // no change yet QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); // no change yet m_dirModel->dirLister()->emitChanges(); QCOMPARE(m_dirModel->rowCount(), 1); // the files get filtered out, subdir QCOMPARE(m_dirModel->rowCount(m_dirIndex), 2); // the files get filtered out, subsubdir and hasChildren // Reset the filter m_dirModel->dirLister()->setNameFilter(QString()); m_dirModel->dirLister()->emitChanges(); // Back to original state QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); m_dirModel->dirLister()->setNameFilter(QStringLiteral("toplevel*")); // Matching with a wildcard QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); // no change yet QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); // no change yet m_dirModel->dirLister()->emitChanges(); QCOMPARE(m_dirModel->rowCount(), 4); // 3 files, one subdir with "toplevel*" in the name QCOMPARE(m_dirModel->rowCount(m_dirIndex), 2); // the files get filtered out, subsubdir and hasChildren are remaining m_dirModel->dirLister()->setNameFilter(QString()); m_dirModel->dirLister()->emitChanges(); QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); // The order of things changed because of filtering. // Fill again, so that m_fileIndex etc. are correct again. fillModel(true); } void KDirModelTest::testMimeFilter() { QVERIFY(m_dirIndex.isValid()); const int oldTopLevelRowCount = m_dirModel->rowCount(); const int oldSubdirRowCount = m_dirModel->rowCount(m_dirIndex); QSignalSpy spyItemsFilteredByMime(m_dirModel->dirLister(), &KCoreDirLister::itemsFilteredByMime); QSignalSpy spyItemsDeleted(m_dirModel->dirLister(), &KCoreDirLister::itemsDeleted); QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); m_dirModel->dirLister()->setMimeFilter({QStringLiteral("application/pdf")}); QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); // no change yet QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); // no change yet m_dirModel->dirLister()->emitChanges(); QCOMPARE(m_dirModel->rowCount(), 1); // 1 pdf files, no subdir anymore QVERIFY(spyRowsRemoved.count() >= 1); // depends on contiguity... QVERIFY(spyItemsDeleted.count() >= 1); // once for every dir QCOMPARE(spyItemsFilteredByMime.count(), 3); spyItemsDeleted.clear(); spyItemsFilteredByMime.clear(); // Reset the filter m_dirModel->dirLister()->setMimeFilter({}); m_dirModel->dirLister()->emitChanges(); QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); QCOMPARE(spyItemsDeleted.count(), 0); QCOMPARE(spyItemsFilteredByMime.count(), 0); // The order of things changed because of filtering. // Fill again, so that m_fileIndex etc. are correct again. fillModel(true); } void KDirModelTest::testMimeExcludeFilter() { QVERIFY(m_dirIndex.isValid()); const int oldTopLevelRowCount = m_dirModel->rowCount(); const int oldSubdirRowCount = m_dirModel->rowCount(m_dirIndex); QSignalSpy spyItemsFilteredByMime(m_dirModel->dirLister(), &KCoreDirLister::itemsFilteredByMime); QSignalSpy spyItemsDeleted(m_dirModel->dirLister(), &KCoreDirLister::itemsDeleted); QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); m_dirModel->dirLister()->setMimeExcludeFilter({QStringLiteral("application/pdf")}); QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); // no change yet QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); // no change yet m_dirModel->dirLister()->emitChanges(); QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount - 1); // no pdf files anymore QCOMPARE(spyRowsRemoved.count(), 1); // one pdf file removed ... QCOMPARE(spyItemsDeleted.count(), 1); QCOMPARE(spyItemsFilteredByMime.count(), 1); QCOMPARE(spyItemsFilteredByMime[0][0].value().count(), 1); spyItemsFilteredByMime.clear(); spyItemsDeleted.clear(); // Reset the exclude filter m_dirModel->dirLister()->setMimeExcludeFilter({}); m_dirModel->dirLister()->emitChanges(); QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); QCOMPARE(spyItemsDeleted.count(), 0); QCOMPARE(spyItemsFilteredByMime.count(), 0); // The order of things changed because of filtering. // Fill again, so that m_fileIndex etc. are correct again. fillModel(true); } void KDirModelTest::testShowHiddenFiles() // #174788 { KDirLister *dirLister = m_dirModel->dirLister(); QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); QSignalSpy spyNewItems(dirLister, &KCoreDirLister::newItems); QSignalSpy spyRowsInserted(m_dirModel, &QAbstractItemModel::rowsInserted); dirLister->setShowHiddenFiles(true); dirLister->emitChanges(); const int numberOfDotFiles = 2; QCOMPARE(spyNewItems.count(), 1); QCOMPARE(spyNewItems[0][0].value().count(), numberOfDotFiles); QCOMPARE(spyRowsInserted.count(), 1); QCOMPARE(spyRowsRemoved.count(), 0); spyNewItems.clear(); spyRowsInserted.clear(); dirLister->setShowHiddenFiles(false); dirLister->emitChanges(); QCOMPARE(spyNewItems.count(), 0); QCOMPARE(spyRowsInserted.count(), 0); QCOMPARE(spyRowsRemoved.count(), 1); } void KDirModelTest::testMultipleSlashes() { const QString path = m_tempDir->path() + '/'; QModelIndex index = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir//testfile")); QVERIFY(index.isValid()); index = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir//subsubdir//")); QVERIFY(index.isValid()); index = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir///subsubdir////testfile")); QVERIFY(index.isValid()); } void KDirModelTest::testUrlWithRef() // #171117 { const QString path = m_tempDir->path() + '/'; KDirLister *dirLister = m_dirModel->dirLister(); QUrl url = QUrl::fromLocalFile(path); url.setFragment(QStringLiteral("ref")); QVERIFY(url.url().endsWith(QLatin1String("#ref"))); QSignalSpy spyCompleted(dirLister, qOverload<>(&KCoreDirLister::completed)); dirLister->openUrl(url, KDirLister::NoFlags); QVERIFY(spyCompleted.wait()); QCOMPARE(dirLister->url().toString(), url.toString(QUrl::StripTrailingSlash)); collectKnownIndexes(); } // void KDirModelTest::testFontUrlWithHost() // #160057 --> moved to kio_fonts (kfontinst/kio/autotests) void KDirModelTest::testRemoteUrlWithHost() // #178416 { if (!KProtocolInfo::isKnownProtocol(QStringLiteral("remote"))) { QSKIP("kio_remote not installed"); } QUrl url(QStringLiteral("remote://foo")); KDirLister *dirLister = m_dirModel->dirLister(); QSignalSpy spyCompleted(dirLister, qOverload<>(&KCoreDirLister::completed)); dirLister->openUrl(url, KDirLister::NoFlags); QVERIFY(spyCompleted.wait()); QCOMPARE(dirLister->url().toString(), QString("remote://foo")); } void KDirModelTest::testZipFile() // # 171721 { const QString path = QFileInfo(QFINDTESTDATA("wronglocalsizes.zip")).absolutePath(); KDirLister *dirLister = m_dirModel->dirLister(); QSignalSpy spyCompleted(dirLister, qOverload<>(&KCoreDirLister::completed)); dirLister->openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); QVERIFY(spyCompleted.wait()); QUrl zipUrl(QUrl::fromLocalFile(path)); zipUrl.setPath(zipUrl.path() + "/wronglocalsizes.zip"); // just a zip file lying here for other reasons QVERIFY(QFile::exists(zipUrl.toLocalFile())); zipUrl.setScheme(QStringLiteral("zip")); QModelIndex index = m_dirModel->indexForUrl(zipUrl); QVERIFY(!index.isValid()); // protocol mismatch, can't find it! zipUrl.setScheme(QStringLiteral("file")); index = m_dirModel->indexForUrl(zipUrl); QVERIFY(index.isValid()); } class MyDirLister : public KDirLister { public: void emitItemsDeleted(const KFileItemList &items) { Q_EMIT itemsDeleted(items); } }; void KDirModelTest::testBug196695() { KFileItem rootItem(QUrl::fromLocalFile(m_tempDir->path()), QString(), KFileItem::Unknown); KFileItem childItem(QUrl::fromLocalFile(QString(m_tempDir->path() + "/toplevelfile_1")), QString(), KFileItem::Unknown); KFileItemList list; // Important: the root item must not be first in the list to trigger bug 196695 list << childItem << rootItem; MyDirLister *dirLister = static_cast(m_dirModel->dirLister()); dirLister->emitItemsDeleted(list); fillModel(true); } void KDirModelTest::testMimeData() { QModelIndex index0 = m_dirModel->index(0, 0); QVERIFY(index0.isValid()); QModelIndex index1 = m_dirModel->index(1, 0); QVERIFY(index1.isValid()); QList indexes; indexes << index0 << index1; QMimeData *mimeData = m_dirModel->mimeData(indexes); QVERIFY(mimeData); QVERIFY(mimeData->hasUrls()); const QList urls = mimeData->urls(); QCOMPARE(urls.count(), indexes.count()); delete mimeData; } void KDirModelTest::testDotHiddenFile_data() { QTest::addColumn("fileContents"); QTest::addColumn("expectedListing"); const QStringList allItems{QStringLiteral("toplevelfile_1"), QStringLiteral("toplevelfile_2"), QStringLiteral("toplevelfile_3"), specialChars(), QStringLiteral("subdir")}; QTest::newRow("empty_file") << (QStringList{}) << allItems; QTest::newRow("simple_name") << (QStringList{QStringLiteral("toplevelfile_1")}) << QStringList(allItems.mid(1)); QStringList allButSpecialChars = allItems; allButSpecialChars.removeAt(3); QTest::newRow("special_chars") << (QStringList{specialChars()}) << allButSpecialChars; QStringList allButSubdir = allItems; allButSubdir.removeAt(4); QTest::newRow("subdir") << (QStringList{QStringLiteral("subdir")}) << allButSubdir; QTest::newRow("many_lines") << (QStringList{QStringLiteral("subdir"), QStringLiteral("toplevelfile_1"), QStringLiteral("toplevelfile_3"), QStringLiteral("toplevelfile_2")}) << QStringList{specialChars()}; } void KDirModelTest::testDotHiddenFile() { QFETCH(QStringList, fileContents); QFETCH(QStringList, expectedListing); const QString path = m_tempDir->path() + '/'; const QString dotHiddenFile = path + ".hidden"; QTest::qWait(1000); // mtime-based cache, so we need to wait for 1 second QFile dh(dotHiddenFile); QVERIFY(dh.open(QIODevice::WriteOnly)); dh.write(fileContents.join('\n').toUtf8()); dh.close(); // Do it twice: once to read from the file and once to use the cache for (int i = 0; i < 2; ++i) { fillModel(true, false); QStringList files; for (int row = 0; row < m_dirModel->rowCount(); ++row) { files.append(m_dirModel->index(row, KDirModel::Name).data().toString()); } files.sort(); expectedListing.sort(); QCOMPARE(files, expectedListing); } dh.remove(); } void KDirModelTest::testShowRoot() { KDirModel dirModel; const QUrl homeUrl = QUrl::fromLocalFile(QDir::homePath()); const QUrl fsRootUrl = QUrl(QStringLiteral("file:///")); // openUrl("/", ShowRoot) should create a "/" item dirModel.openUrl(fsRootUrl, KDirModel::ShowRoot); QTRY_COMPARE(dirModel.rowCount(), 1); const QModelIndex rootIndex = dirModel.index(0, 0); QVERIFY(rootIndex.isValid()); QCOMPARE(rootIndex.data().toString(), QStringLiteral("/")); QVERIFY(!dirModel.parent(rootIndex).isValid()); QCOMPARE(dirModel.itemForIndex(rootIndex).url(), QUrl(QStringLiteral("file:///"))); QCOMPARE(dirModel.itemForIndex(rootIndex).name(), QStringLiteral("/")); // expandToUrl should work dirModel.expandToUrl(homeUrl); QTRY_VERIFY(dirModel.indexForUrl(homeUrl).isValid()); // test itemForIndex and indexForUrl QCOMPARE(dirModel.itemForIndex(QModelIndex()).url(), QUrl()); QVERIFY(!dirModel.indexForUrl(QUrl()).isValid()); const QUrl slashUrl = QUrl::fromLocalFile(QStringLiteral("/")); QCOMPARE(dirModel.indexForUrl(slashUrl), rootIndex); // switching to another URL should also show a root node QSignalSpy spyRowsRemoved(&dirModel, &QAbstractItemModel::rowsRemoved); const QUrl tempUrl = QUrl::fromLocalFile(QDir::tempPath()); dirModel.openUrl(tempUrl, KDirModel::ShowRoot); QTRY_COMPARE(dirModel.rowCount(), 1); QCOMPARE(spyRowsRemoved.count(), 1); const QModelIndex newRootIndex = dirModel.index(0, 0); QVERIFY(newRootIndex.isValid()); QCOMPARE(newRootIndex.data().toString(), QFileInfo(QDir::tempPath()).fileName()); QVERIFY(!dirModel.parent(newRootIndex).isValid()); QVERIFY(!dirModel.indexForUrl(slashUrl).isValid()); QCOMPARE(dirModel.itemForIndex(newRootIndex).url(), tempUrl); } void KDirModelTest::testShowRootWithTrailingSlash() { // GIVEN KDirModel dirModel; const QUrl homeUrl = QUrl::fromLocalFile(QDir::homePath() + QLatin1Char('/')); // WHEN dirModel.openUrl(homeUrl, KDirModel::ShowRoot); QTRY_VERIFY(dirModel.indexForUrl(homeUrl).isValid()); } void KDirModelTest::testShowRootAndExpandToUrl() { // call expandToUrl without waiting for initial listing of root node KDirModel dirModel; dirModel.openUrl(QUrl(QStringLiteral("file:///")), KDirModel::ShowRoot); const QUrl homeUrl = QUrl::fromLocalFile(QDir::homePath()); dirModel.expandToUrl(homeUrl); QTRY_VERIFY(dirModel.indexForUrl(homeUrl).isValid()); } void KDirModelTest::testHasChildren_data() { QTest::addColumn("dirsOnly"); QTest::addColumn("withHidden"); QTest::newRow("with_files_and_no_hidden") << false << false; QTest::newRow("dirs_only_and_no_hidden") << true << false; QTest::newRow("with_files_and_hidden") << false << true; QTest::newRow("dirs_only_with_hidden") << true << true; } // Test hasChildren without first populating the dirs void KDirModelTest::testHasChildren() { QFETCH(bool, dirsOnly); QFETCH(bool, withHidden); m_dirModel->dirLister()->setDirOnlyMode(dirsOnly); m_dirModel->dirLister()->setShowHiddenFiles(withHidden); fillModel(true, false); QVERIFY(m_dirModel->hasChildren()); auto findDir = [this](const QModelIndex &parentIndex, const QString &name) { for (int row = 0; row < m_dirModel->rowCount(parentIndex); ++row) { QModelIndex idx = m_dirModel->index(row, 0, parentIndex); if (m_dirModel->itemForIndex(idx).isDir() && m_dirModel->itemForIndex(idx).name() == name) { return idx; } } return QModelIndex(); }; m_dirIndex = findDir(QModelIndex(), "subdir"); QVERIFY(m_dirIndex.isValid()); QVERIFY(m_dirModel->hasChildren(m_dirIndex)); auto listDir = [this](const QModelIndex &index) { QSignalSpy completedSpy(m_dirModel->dirLister(), qOverload<>(&KDirLister::completed)); m_dirModel->fetchMore(index); return completedSpy.wait(); }; // Now list subdir/ QVERIFY(listDir(m_dirIndex)); const QModelIndex subsubdirIndex = findDir(m_dirIndex, "subsubdir"); QVERIFY(subsubdirIndex.isValid()); QCOMPARE(m_dirModel->hasChildren(subsubdirIndex), !dirsOnly); const QModelIndex hasChildrenDirIndex = findDir(m_dirIndex, "hasChildren"); QVERIFY(hasChildrenDirIndex.isValid()); QVERIFY(m_dirModel->hasChildren(hasChildrenDirIndex)); // Now list hasChildren/ QVERIFY(listDir(hasChildrenDirIndex)); QModelIndex testDirIndex = findDir(hasChildrenDirIndex, "emptyDir"); QVERIFY(testDirIndex.isValid()); QVERIFY(!m_dirModel->hasChildren(testDirIndex)); testDirIndex = findDir(hasChildrenDirIndex, "hiddenfileDir"); QVERIFY(testDirIndex.isValid()); QCOMPARE(m_dirModel->hasChildren(testDirIndex), !dirsOnly && withHidden); testDirIndex = findDir(hasChildrenDirIndex, "hiddenDirDir"); QVERIFY(testDirIndex.isValid()); QCOMPARE(m_dirModel->hasChildren(testDirIndex), withHidden); testDirIndex = findDir(hasChildrenDirIndex, "pipeDir"); QVERIFY(testDirIndex.isValid()); QCOMPARE(m_dirModel->hasChildren(testDirIndex), !dirsOnly); testDirIndex = findDir(hasChildrenDirIndex, "symlinkDir"); QVERIFY(testDirIndex.isValid()); QCOMPARE(m_dirModel->hasChildren(testDirIndex), !dirsOnly); m_dirModel->dirLister()->setDirOnlyMode(false); m_dirModel->dirLister()->setShowHiddenFiles(false); } void KDirModelTest::testInvalidUrl() { QSignalSpy completedSpy(m_dirModel->dirLister(), qOverload<>(&KCoreDirLister::completed)); m_dirModel->openUrl(QUrl(":/")); // currently ends up in KCoreDirLister::handleError. TODO: add error signal to KDirModel } void KDirModelTest::testDeleteFile() { fillModel(true); QVERIFY(m_fileIndex.isValid()); const int oldTopLevelRowCount = m_dirModel->rowCount(); const QString path = m_tempDir->path() + '/'; const QString file = path + "toplevelfile_1"; const QUrl url = QUrl::fromLocalFile(file); QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); KIO::DeleteJob *job = KIO::del(url, KIO::HideProgressInfo); QVERIFY(job->exec()); // Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved if (spyRowsRemoved.isEmpty()) { QVERIFY(spyRowsRemoved.wait()); } // If we come here, then rowsRemoved() was emitted - all good. const int topLevelRowCount = m_dirModel->rowCount(); QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 1); // one less than before QCOMPARE(spyRowsRemoved.count(), 1); QCOMPARE(spyRowsRemoved[0][1].toInt(), m_fileIndex.row()); QCOMPARE(spyRowsRemoved[0][2].toInt(), m_fileIndex.row()); QModelIndex fileIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "toplevelfile_1")); QVERIFY(!fileIndex.isValid()); // Recreate the file, for consistency in the next tests // So the second part of this test is a "testCreateFile" createTestFile(file); // Tricky problem - KDirLister::openUrl will emit items from cache // and then schedule an update; so just calling fillModel would // not wait enough, it would abort due to not finding toplevelfile_1 // in the items from cache. This progressive-emitting behavior is fine // for GUIs but not for unit tests ;-) fillModel(true, false); fillModel(false); } void KDirModelTest::testDeleteFileWhileListing() // doesn't really test that yet, the kdirwatch deleted signal comes too late { const int oldTopLevelRowCount = m_dirModel->rowCount(); const QString path = m_tempDir->path() + '/'; const QString file = path + "toplevelfile_1"; const QUrl url = QUrl::fromLocalFile(file); KDirLister *dirLister = m_dirModel->dirLister(); QSignalSpy spyCompleted(dirLister, qOverload<>(&KCoreDirLister::completed)); dirLister->openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); if (!spyCompleted.isEmpty()) { QSKIP("listing completed too early"); } QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); KIO::DeleteJob *job = KIO::del(url, KIO::HideProgressInfo); QVERIFY(job->exec()); if (spyCompleted.isEmpty()) { QVERIFY(spyCompleted.wait()); } QVERIFY(spyRowsRemoved.wait(1000)); const int topLevelRowCount = m_dirModel->rowCount(); QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 1); // one less than before QCOMPARE(spyRowsRemoved.count(), 1); QCOMPARE(spyRowsRemoved[0][1].toInt(), m_fileIndex.row()); QCOMPARE(spyRowsRemoved[0][2].toInt(), m_fileIndex.row()); QModelIndex fileIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "toplevelfile_1")); QVERIFY(!fileIndex.isValid()); qDebug() << "Test done, recreating file"; // Recreate the file, for consistency in the next tests // So the second part of this test is a "testCreateFile" createTestFile(file); fillModel(true, false); // see testDeleteFile fillModel(false); } void KDirModelTest::testOverwriteFileWithDir() // #151851 c4 { QSKIP("TODO testOverwriteFileWithDir doesn't pass FIXME"); fillModel(false); const QString path = m_tempDir->path() + '/'; const QString dir = path + "subdir"; const QString file = path + "toplevelfile_1"; const int oldTopLevelRowCount = m_dirModel->rowCount(); bool removalWithinTopLevel = false; bool dataChangedAtFirstLevel = false; auto rrc = connect(m_dirModel, &KDirModel::rowsRemoved, this, [&removalWithinTopLevel](const QModelIndex &index) { if (!index.isValid()) { // yes, that's what we have been waiting for removalWithinTopLevel = true; } }); auto dcc = connect(m_dirModel, &KDirModel::dataChanged, this, [&dataChangedAtFirstLevel](const QModelIndex &index) { if (index.isValid() && !index.parent().isValid()) { // a change of a node whose parent is root, yay, that's it dataChangedAtFirstLevel = true; } }); KIO::Job *job = KIO::move(QUrl::fromLocalFile(dir), QUrl::fromLocalFile(file), KIO::HideProgressInfo); delete KIO::delegateExtension(job); auto *askUserHandler = new MockAskUserInterface(job->uiDelegate()); askUserHandler->m_renameResult = KIO::Result_Overwrite; QVERIFY(job->exec()); QCOMPARE(askUserHandler->m_askUserRenameCalled, 1); // Wait for a removal within the top level (that's for the old file going away), and also // for a dataChanged which notifies us that a file has become a directory QTRY_VERIFY(removalWithinTopLevel); QTRY_VERIFY(dataChangedAtFirstLevel); m_dirModel->disconnect(rrc); m_dirModel->disconnect(dcc); // If we come here, then rowsRemoved() was emitted - all good. const int topLevelRowCount = m_dirModel->rowCount(); QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 1); // one less than before QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(dir)).isValid()); QModelIndex newIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "toplevelfile_1")); QVERIFY(newIndex.isValid()); KFileItem newItem = m_dirModel->itemForIndex(newIndex); QVERIFY(newItem.isDir()); // yes, the file is a dir now ;-) qDebug() << "========= Test done, recreating test data ========="; recreateTestData(); fillModel(false); } void KDirModelTest::testDeleteFiles() { const int oldTopLevelRowCount = m_dirModel->rowCount(); const QString file = m_tempDir->path() + "/toplevelfile_"; QList urls; urls << QUrl::fromLocalFile(file + '1') << QUrl::fromLocalFile(file + '2') << QUrl::fromLocalFile(file + '3'); QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); KIO::DeleteJob *job = KIO::del(urls, KIO::HideProgressInfo); QVERIFY(job->exec()); int numRowsRemoved = 0; while (numRowsRemoved < 3) { QTest::qWait(20); numRowsRemoved = 0; for (int sigNum = 0; sigNum < spyRowsRemoved.count(); ++sigNum) { numRowsRemoved += spyRowsRemoved[sigNum][2].toInt() - spyRowsRemoved[sigNum][1].toInt() + 1; } // qDebug() << "numRowsRemoved=" << numRowsRemoved; } const int topLevelRowCount = m_dirModel->rowCount(); QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 3); // three less than before qDebug() << "Recreating test data"; recreateTestData(); qDebug() << "Re-filling model"; fillModel(false); } // A renaming that looks more like a deletion to the model void KDirModelTest::testRenameFileToHidden() // #174721 { const QUrl url = QUrl::fromLocalFile(m_tempDir->path() + "/toplevelfile_2"); const QUrl newUrl = QUrl::fromLocalFile(m_tempDir->path() + "/.toplevelfile_2"); QSignalSpy spyDataChanged(m_dirModel, &QAbstractItemModel::dataChanged); QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); QSignalSpy spyRowsInserted(m_dirModel, &QAbstractItemModel::rowsInserted); KIO::SimpleJob *job = KIO::rename(url, newUrl, KIO::HideProgressInfo); QVERIFY(job->exec()); // Wait for the DBUS signal from KDirNotify, it's the one the triggers KDirLister if (spyRowsRemoved.isEmpty()) { QVERIFY(spyRowsRemoved.wait()); } // If we come here, then rowsRemoved() was emitted - all good. QCOMPARE(spyDataChanged.count(), 0); QCOMPARE(spyRowsRemoved.count(), 1); QCOMPARE(spyRowsInserted.count(), 0); COMPARE_INDEXES(spyRowsRemoved[0][0].value(), QModelIndex()); // parent is invalid const int row = spyRowsRemoved[0][1].toInt(); QCOMPARE(row, m_secondFileIndex.row()); // only compare row spyRowsRemoved.clear(); // Put things back to normal, should make the file reappear job = KIO::rename(newUrl, url, KIO::HideProgressInfo); QVERIFY(job->exec()); // Wait for the DBUS signal from KDirNotify, it's the one the triggers KDirLister if (spyRowsInserted.isEmpty()) { QVERIFY(spyRowsInserted.wait()); } QCOMPARE(spyDataChanged.count(), 0); QCOMPARE(spyRowsRemoved.count(), 0); QCOMPARE(spyRowsInserted.count(), 1); const int newRow = spyRowsInserted[0][1].toInt(); m_secondFileIndex = m_dirModel->index(newRow, 0); QVERIFY(m_secondFileIndex.isValid()); QCOMPARE(m_dirModel->itemForIndex(m_secondFileIndex).url().toString(), url.toString()); } void KDirModelTest::testDeleteDirectory() { const QString path = m_tempDir->path() + '/'; const QUrl url = QUrl::fromLocalFile(path + "subdir/subsubdir"); QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); QSignalSpy spyDirWatchDeleted(KDirWatch::self(), &KDirWatch::deleted); KIO::DeleteJob *job = KIO::del(url, KIO::HideProgressInfo); QVERIFY(job->exec()); // Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved if (spyRowsRemoved.isEmpty()) { QVERIFY(spyRowsRemoved.wait()); } // If we come here, then rowsRemoved() was emitted - all good. QCOMPARE(spyRowsRemoved.count(), 1); QModelIndex deletedDirIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir/subsubdir")); QVERIFY(!deletedDirIndex.isValid()); QModelIndex dirIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir")); QVERIFY(dirIndex.isValid()); // Not emitted because DeleteJobPrivate::currentSourceStated calls stopDirScan // We don't need it anyway, we have the DBus signal // QCOMPARE(spyDirWatchDeleted.count(), 1); } void KDirModelTest::testDeleteCurrentDirectory() { const int oldTopLevelRowCount = m_dirModel->rowCount(); const QString path = m_tempDir->path() + '/'; const QUrl url = QUrl::fromLocalFile(path); QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); KIO::DeleteJob *job = KIO::del(url, KIO::HideProgressInfo); QVERIFY(job->exec()); // Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved if (spyRowsRemoved.isEmpty()) { QVERIFY(spyRowsRemoved.wait()); } // If we come here, then rowsRemoved() was emitted - all good. QTRY_COMPARE(m_dirModel->rowCount(), 0); // empty // We can get rowsRemoved for subdirs first, since kdirwatch notices that. QVERIFY(spyRowsRemoved.count() >= 1); // Look for the signal(s) that had QModelIndex() as parent. int i; int numDeleted = 0; for (i = 0; i < spyRowsRemoved.count(); ++i) { const int from = spyRowsRemoved[i][1].toInt(); const int to = spyRowsRemoved[i][2].toInt(); qDebug() << spyRowsRemoved[i][0].value() << from << to; if (!spyRowsRemoved[i][0].value().isValid()) { numDeleted += (to - from) + 1; } } QCOMPARE(numDeleted, oldTopLevelRowCount); QModelIndex fileIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "toplevelfile_1")); QVERIFY(!fileIndex.isValid()); } void KDirModelTest::testQUrlHash() { const int count = 3000; // Prepare an array of QUrls so that url constructing isn't part of the timing QList urls; urls.resize(count); for (int i = 0; i < count; ++i) { urls[i] = QUrl("http://www.kde.org/path/" + QString::number(i)); } QHash qurlHash; QHash kurlHash; QElapsedTimer dt; dt.start(); for (int i = 0; i < count; ++i) { qurlHash.insert(urls[i], i); } // qDebug() << "inserting" << count << "urls into QHash using old qHash:" << dt.elapsed() << "msecs"; dt.start(); for (int i = 0; i < count; ++i) { kurlHash.insert(urls[i], i); } // qDebug() << "inserting" << count << "urls into QHash using new qHash:" << dt.elapsed() << "msecs"; // Nice results: for count=30000 I got 4515 (before) and 103 (after) dt.start(); for (int i = 0; i < count; ++i) { QCOMPARE(qurlHash.value(urls[i]), i); } // qDebug() << "looking up" << count << "urls into QHash using old qHash:" << dt.elapsed() << "msecs"; dt.start(); for (int i = 0; i < count; ++i) { QCOMPARE(kurlHash.value(urls[i]), i); } // qDebug() << "looking up" << count << "urls into QHash using new qHash:" << dt.elapsed() << "msecs"; // Nice results: for count=30000 I got 4296 (before) and 63 (after) } #include "moc_kdirmodeltest.cpp"