File indexing completed on 2025-01-19 12:48:34
0001 /* 0002 This file is part of the KDE project 0003 SPDX-FileCopyrightText: 2006-2007 David Faure <faure@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include <array> 0009 0010 #include "jobuidelegatefactory.h" 0011 #include "kdirmodeltest.h" 0012 #include <KDirWatch> 0013 #include <kdirlister.h> 0014 #include <kdirnotify.h> 0015 #include <kio/chmodjob.h> 0016 #include <kio/copyjob.h> 0017 #include <kio/deletejob.h> 0018 #include <kio/simplejob.h> 0019 #include <kprotocolinfo.h> 0020 0021 #include <QDebug> 0022 #include <QMimeData> 0023 #include <QSignalSpy> 0024 #include <QUrl> 0025 0026 #ifdef Q_OS_UNIX 0027 #include <utime.h> 0028 #endif 0029 #include "kiotesthelper.h" 0030 #include "mockcoredelegateextensions.h" 0031 0032 QTEST_MAIN(KDirModelTest) 0033 0034 static QString specialChars() 0035 { 0036 #ifndef Q_OS_WIN 0037 return QStringLiteral(" special chars%:.pdf"); 0038 #else 0039 return QStringLiteral(" special chars%.pdf"); 0040 #endif 0041 } 0042 0043 Q_DECLARE_METATYPE(KFileItemList) 0044 0045 void KDirModelTest::initTestCase() 0046 { 0047 qputenv("LC_ALL", "en_US.UTF-8"); 0048 0049 QStandardPaths::setTestModeEnabled(true); 0050 0051 qRegisterMetaType<KFileItemList>("KFileItemList"); 0052 0053 m_dirModelForExpand = nullptr; 0054 m_dirModel = nullptr; 0055 s_referenceTimeStamp = QDateTime::currentDateTime().addSecs(-30); // 30 seconds ago 0056 m_topLevelFileNames << QStringLiteral("toplevelfile_1") << QStringLiteral("toplevelfile_2") << QStringLiteral("toplevelfile_3") << specialChars(); 0057 recreateTestData(); 0058 0059 fillModel(false); 0060 } 0061 0062 void KDirModelTest::recreateTestData() 0063 { 0064 if (m_tempDir) { 0065 qDebug() << "Deleting old tempdir" << m_tempDir->path(); 0066 m_tempDir.reset(); 0067 qApp->processEvents(); // process inotify events so they don't pollute us later on 0068 } 0069 0070 m_tempDir = std::make_unique<QTemporaryDir>(homeTmpDir()); 0071 qDebug() << "new tmp dir:" << m_tempDir->path(); 0072 // Create test data: 0073 /* 0074 * PATH/toplevelfile_1 0075 * PATH/toplevelfile_2 0076 * PATH/toplevelfile_3 0077 * PATH/special chars%:.pdf 0078 * PATH/.hiddenfile 0079 * PATH/.hiddenfile2 0080 * PATH/subdir 0081 * PATH/subdir/testfile 0082 * PATH/subdir/testsymlink 0083 * PATH/subdir/subsubdir 0084 * PATH/subdir/subsubdir/testfile 0085 * PATH/subdir/hasChildren 0086 * PATH/subdir/hasChildren/emptyDir 0087 * PATH/subdir/hasChildren/hiddenfileDir 0088 * PATH/subdir/hasChildren/hiddenfileDir/.hidden 0089 * PATH/subdir/hasChildren/hiddenDirDir 0090 * PATH/subdir/hasChildren/hiddenDirDir/.hidden 0091 * PATH/subdir/hasChildren/symlinkDir 0092 * PATH/subdir/hasChildren/symlinkDir/link 0093 * PATH/subdir/hasChildren/pipeDir 0094 * PATH/subdir/hasChildren/pipeDir/pipe 0095 */ 0096 const QString path = m_tempDir->path() + '/'; 0097 for (const QString &f : std::as_const(m_topLevelFileNames)) { 0098 createTestFile(path + f); 0099 } 0100 createTestFile(path + ".hiddenfile"); 0101 createTestFile(path + ".hiddenfile2"); 0102 createTestDirectory(path + "subdir"); 0103 createTestDirectory(path + "subdir/subsubdir", NoSymlink); 0104 createTestDirectory(path + "subdir/hasChildren", Empty); 0105 createTestDirectory(path + "subdir/hasChildren/emptyDir", Empty); 0106 createTestDirectory(path + "subdir/hasChildren/hiddenfileDir", Empty); 0107 createTestFile(path + "subdir/hasChildren/hiddenfileDir/.hidden"); 0108 createTestDirectory(path + "subdir/hasChildren/hiddenDirDir", Empty); 0109 createTestDirectory(path + "subdir/hasChildren/hiddenDirDir/.hidden", Empty); 0110 createTestDirectory(path + "subdir/hasChildren/symlinkDir", Empty); 0111 createTestSymlink(path + "subdir/hasChildren/symlinkDir/link", QString(path + "toplevelfile_1").toUtf8()); 0112 createTestDirectory(path + "subdir/hasChildren/pipeDir", Empty); 0113 createTestPipe(path + "subdir/hasChildren/pipeDir/pipe"); 0114 0115 m_dirIndex = QModelIndex(); 0116 m_fileIndex = QModelIndex(); 0117 m_secondFileIndex = QModelIndex(); 0118 } 0119 0120 void KDirModelTest::cleanupTestCase() 0121 { 0122 m_tempDir.reset(); 0123 0124 delete m_dirModel; 0125 m_dirModel = nullptr; 0126 0127 delete m_dirModelForExpand; 0128 m_dirModelForExpand = nullptr; 0129 } 0130 0131 void KDirModelTest::fillModel(bool reload, bool expectAllIndexes) 0132 { 0133 if (!m_dirModel) { 0134 m_dirModel = new KDirModel; 0135 } 0136 m_dirModel->dirLister()->setAutoErrorHandlingEnabled(false); 0137 const QString path = m_tempDir->path() + '/'; 0138 KDirLister *dirLister = m_dirModel->dirLister(); 0139 qDebug() << "Calling openUrl"; 0140 m_dirModel->openUrl(QUrl::fromLocalFile(path), reload ? KDirModel::Reload : KDirModel::NoFlags); 0141 QSignalSpy spyCompleted(dirLister, qOverload<>(&KCoreDirLister::completed)); 0142 QVERIFY(spyCompleted.wait()); 0143 0144 if (expectAllIndexes) { 0145 collectKnownIndexes(); 0146 } 0147 } 0148 0149 // Called after test function 0150 void KDirModelTest::cleanup() 0151 { 0152 if (m_dirModel) { 0153 disconnect(m_dirModel->dirLister(), nullptr, this, nullptr); 0154 m_dirModel->dirLister()->setNameFilter(QString()); 0155 m_dirModel->dirLister()->setMimeFilter(QStringList()); 0156 m_dirModel->dirLister()->emitChanges(); 0157 } 0158 } 0159 0160 void KDirModelTest::collectKnownIndexes() 0161 { 0162 m_dirIndex = QModelIndex(); 0163 m_fileIndex = QModelIndex(); 0164 m_secondFileIndex = QModelIndex(); 0165 // Create the indexes once and for all 0166 // The trouble is that the order of listing is undefined, one can get 1/2/3/subdir or subdir/3/2/1 for instance. 0167 for (int row = 0; row < m_topLevelFileNames.count() + 1 /*subdir*/; ++row) { 0168 QModelIndex idx = m_dirModel->index(row, 0, QModelIndex()); 0169 QVERIFY(idx.isValid()); 0170 KFileItem item = m_dirModel->itemForIndex(idx); 0171 qDebug() << item.url() << "isDir=" << item.isDir(); 0172 QString fileName = item.url().fileName(); 0173 if (item.isDir()) { 0174 m_dirIndex = idx; 0175 } else if (fileName == QLatin1String("toplevelfile_1")) { 0176 m_fileIndex = idx; 0177 } else if (fileName == QLatin1String("toplevelfile_2")) { 0178 m_secondFileIndex = idx; 0179 } else if (fileName.startsWith(QLatin1String(" special"))) { 0180 m_specialFileIndex = idx; 0181 } 0182 } 0183 QVERIFY(m_dirIndex.isValid()); 0184 QVERIFY(m_fileIndex.isValid()); 0185 QVERIFY(m_secondFileIndex.isValid()); 0186 QVERIFY(m_specialFileIndex.isValid()); 0187 0188 // Now list subdir/ 0189 QSignalSpy spyCompleted(m_dirModel->dirLister(), qOverload<>(&KCoreDirLister::completed)); 0190 QVERIFY(m_dirModel->canFetchMore(m_dirIndex)); 0191 m_dirModel->fetchMore(m_dirIndex); 0192 qDebug() << "Listing subdir/"; 0193 if (spyCompleted.isEmpty()) { 0194 QVERIFY(spyCompleted.wait()); 0195 } 0196 0197 // Index of a file inside a directory (subdir/testfile) 0198 QModelIndex subdirIndex; 0199 m_fileInDirIndex = QModelIndex(); 0200 for (int row = 0; row < 4; ++row) { 0201 QModelIndex idx = m_dirModel->index(row, 0, m_dirIndex); 0202 const KFileItem item = m_dirModel->itemForIndex(idx); 0203 if (item.isDir() && item.name() == QLatin1String("subsubdir")) { 0204 subdirIndex = idx; 0205 } else if (item.name() == QLatin1String("testfile")) { 0206 m_fileInDirIndex = idx; 0207 } 0208 } 0209 0210 // List subdir/subsubdir 0211 QVERIFY(m_dirModel->canFetchMore(subdirIndex)); 0212 qDebug() << "Listing subdir/subsubdir"; 0213 spyCompleted.clear(); 0214 m_dirModel->fetchMore(subdirIndex); 0215 QVERIFY(spyCompleted.wait()); 0216 0217 // Index of ... well, subdir/subsubdir/testfile 0218 m_fileInSubdirIndex = m_dirModel->index(0, 0, subdirIndex); 0219 } 0220 0221 void KDirModelTest::testRowCount() 0222 { 0223 const int topLevelRowCount = m_dirModel->rowCount(); 0224 QCOMPARE(topLevelRowCount, m_topLevelFileNames.count() + 1 /*subdir*/); 0225 const int subdirRowCount = m_dirModel->rowCount(m_dirIndex); 0226 QCOMPARE(subdirRowCount, 4); 0227 0228 QVERIFY(m_fileIndex.isValid()); 0229 const int fileRowCount = m_dirModel->rowCount(m_fileIndex); // #176555 0230 QCOMPARE(fileRowCount, 0); 0231 } 0232 0233 void KDirModelTest::testIndex() 0234 { 0235 QVERIFY(m_dirModel->hasChildren()); 0236 0237 // Index of the first file 0238 QVERIFY(m_fileIndex.isValid()); 0239 QCOMPARE(m_fileIndex.model(), static_cast<const QAbstractItemModel *>(m_dirModel)); 0240 // QCOMPARE(m_fileIndex.row(), 0); 0241 QCOMPARE(m_fileIndex.column(), 0); 0242 QVERIFY(!m_fileIndex.parent().isValid()); 0243 QVERIFY(!m_dirModel->hasChildren(m_fileIndex)); 0244 0245 // Index of a directory 0246 QVERIFY(m_dirIndex.isValid()); 0247 QCOMPARE(m_dirIndex.model(), static_cast<const QAbstractItemModel *>(m_dirModel)); 0248 // QCOMPARE(m_dirIndex.row(), 3); // ordering isn't guaranteed 0249 QCOMPARE(m_dirIndex.column(), 0); 0250 QVERIFY(!m_dirIndex.parent().isValid()); 0251 QVERIFY(m_dirModel->hasChildren(m_dirIndex)); 0252 0253 // Index of a file inside a directory (subdir/testfile) 0254 QVERIFY(m_fileInDirIndex.isValid()); 0255 QCOMPARE(m_fileInDirIndex.model(), static_cast<const QAbstractItemModel *>(m_dirModel)); 0256 // QCOMPARE(m_fileInDirIndex.row(), 0); // ordering isn't guaranteed 0257 QCOMPARE(m_fileInDirIndex.column(), 0); 0258 QVERIFY(m_fileInDirIndex.parent() == m_dirIndex); 0259 QVERIFY(!m_dirModel->hasChildren(m_fileInDirIndex)); 0260 0261 // Index of subdir/subsubdir/testfile 0262 QVERIFY(m_fileInSubdirIndex.isValid()); 0263 QCOMPARE(m_fileInSubdirIndex.model(), static_cast<const QAbstractItemModel *>(m_dirModel)); 0264 QCOMPARE(m_fileInSubdirIndex.row(), 0); // we can check it because it's the only file there 0265 QCOMPARE(m_fileInSubdirIndex.column(), 0); 0266 QVERIFY(m_fileInSubdirIndex.parent().parent() == m_dirIndex); 0267 QVERIFY(!m_dirModel->hasChildren(m_fileInSubdirIndex)); 0268 0269 // Test sibling() by going from subdir/testfile to subdir/subsubdir 0270 const QModelIndex subsubdirIndex = m_fileInSubdirIndex.parent(); 0271 QVERIFY(subsubdirIndex.isValid()); 0272 QModelIndex sibling1 = m_dirModel->sibling(subsubdirIndex.row(), 0, m_fileInDirIndex); 0273 QVERIFY(sibling1.isValid()); 0274 QVERIFY(sibling1 == subsubdirIndex); 0275 QVERIFY(m_dirModel->hasChildren(subsubdirIndex)); 0276 0277 // Invalid sibling call 0278 QVERIFY(!m_dirModel->sibling(2, 0, m_fileInSubdirIndex).isValid()); 0279 0280 // Test index() with a valid parent (dir). 0281 QModelIndex index2 = m_dirModel->index(m_fileInSubdirIndex.row(), m_fileInSubdirIndex.column(), subsubdirIndex); 0282 QVERIFY(index2.isValid()); 0283 QVERIFY(index2 == m_fileInSubdirIndex); 0284 0285 // Test index() with a non-parent (file). 0286 QModelIndex index3 = m_dirModel->index(m_fileInSubdirIndex.row(), m_fileInSubdirIndex.column(), m_fileIndex); 0287 QVERIFY(!index3.isValid()); 0288 } 0289 0290 void KDirModelTest::testNames() 0291 { 0292 QString fileName = m_dirModel->data(m_fileIndex, Qt::DisplayRole).toString(); 0293 QCOMPARE(fileName, QString("toplevelfile_1")); 0294 0295 QString specialFileName = m_dirModel->data(m_specialFileIndex, Qt::DisplayRole).toString(); 0296 QCOMPARE(specialFileName, specialChars()); 0297 0298 QString dirName = m_dirModel->data(m_dirIndex, Qt::DisplayRole).toString(); 0299 QCOMPARE(dirName, QString("subdir")); 0300 0301 QString fileInDirName = m_dirModel->data(m_fileInDirIndex, Qt::DisplayRole).toString(); 0302 QCOMPARE(fileInDirName, QString("testfile")); 0303 0304 QString fileInSubdirName = m_dirModel->data(m_fileInSubdirIndex, Qt::DisplayRole).toString(); 0305 QCOMPARE(fileInSubdirName, QString("testfile")); 0306 } 0307 0308 void KDirModelTest::testItemForIndex() 0309 { 0310 // root item 0311 KFileItem rootItem = m_dirModel->itemForIndex(QModelIndex()); 0312 QVERIFY(!rootItem.isNull()); 0313 QCOMPARE(rootItem.name(), QString(".")); 0314 0315 KFileItem fileItem = m_dirModel->itemForIndex(m_fileIndex); 0316 QVERIFY(!fileItem.isNull()); 0317 QCOMPARE(fileItem.name(), QString("toplevelfile_1")); 0318 QVERIFY(!fileItem.isDir()); 0319 QCOMPARE(fileItem.url().toLocalFile(), QString(m_tempDir->path() + "/toplevelfile_1")); 0320 0321 KFileItem dirItem = m_dirModel->itemForIndex(m_dirIndex); 0322 QVERIFY(!dirItem.isNull()); 0323 QCOMPARE(dirItem.name(), QString("subdir")); 0324 QVERIFY(dirItem.isDir()); 0325 QCOMPARE(dirItem.url().toLocalFile(), QString(m_tempDir->path() + "/subdir")); 0326 0327 KFileItem fileInDirItem = m_dirModel->itemForIndex(m_fileInDirIndex); 0328 QVERIFY(!fileInDirItem.isNull()); 0329 QCOMPARE(fileInDirItem.name(), QString("testfile")); 0330 QVERIFY(!fileInDirItem.isDir()); 0331 QCOMPARE(fileInDirItem.url().toLocalFile(), QString(m_tempDir->path() + "/subdir/testfile")); 0332 0333 KFileItem fileInSubdirItem = m_dirModel->itemForIndex(m_fileInSubdirIndex); 0334 QVERIFY(!fileInSubdirItem.isNull()); 0335 QCOMPARE(fileInSubdirItem.name(), QString("testfile")); 0336 QVERIFY(!fileInSubdirItem.isDir()); 0337 QCOMPARE(fileInSubdirItem.url().toLocalFile(), QString(m_tempDir->path() + "/subdir/subsubdir/testfile")); 0338 } 0339 0340 void KDirModelTest::testIndexForItem() 0341 { 0342 KFileItem rootItem = m_dirModel->itemForIndex(QModelIndex()); 0343 QModelIndex rootIndex = m_dirModel->indexForItem(rootItem); 0344 QVERIFY(!rootIndex.isValid()); 0345 0346 KFileItem fileItem = m_dirModel->itemForIndex(m_fileIndex); 0347 QModelIndex fileIndex = m_dirModel->indexForItem(fileItem); 0348 QCOMPARE(fileIndex, m_fileIndex); 0349 0350 KFileItem dirItem = m_dirModel->itemForIndex(m_dirIndex); 0351 QModelIndex dirIndex = m_dirModel->indexForItem(dirItem); 0352 QCOMPARE(dirIndex, m_dirIndex); 0353 0354 KFileItem fileInDirItem = m_dirModel->itemForIndex(m_fileInDirIndex); 0355 QModelIndex fileInDirIndex = m_dirModel->indexForItem(fileInDirItem); 0356 QCOMPARE(fileInDirIndex, m_fileInDirIndex); 0357 0358 KFileItem fileInSubdirItem = m_dirModel->itemForIndex(m_fileInSubdirIndex); 0359 QModelIndex fileInSubdirIndex = m_dirModel->indexForItem(fileInSubdirItem); 0360 QCOMPARE(fileInSubdirIndex, m_fileInSubdirIndex); 0361 } 0362 0363 void KDirModelTest::testData() 0364 { 0365 // First file 0366 QModelIndex idx1col0 = m_dirModel->index(m_fileIndex.row(), 0, QModelIndex()); 0367 QCOMPARE(idx1col0.data().toString(), QString("toplevelfile_1")); 0368 QModelIndex idx1col1 = m_dirModel->index(m_fileIndex.row(), 1, QModelIndex()); 0369 QString size1 = m_dirModel->data(idx1col1, Qt::DisplayRole).toString(); 0370 QCOMPARE(size1, QString("11 B")); 0371 0372 KFileItem item = m_dirModel->data(m_fileIndex, KDirModel::FileItemRole).value<KFileItem>(); 0373 KFileItem fileItem = m_dirModel->itemForIndex(m_fileIndex); 0374 QCOMPARE(item, fileItem); 0375 0376 QCOMPARE(m_dirModel->data(m_fileIndex, KDirModel::ChildCountRole).toInt(), (int)KDirModel::ChildCountUnknown); 0377 0378 // Second file 0379 QModelIndex idx2col0 = m_dirModel->index(m_secondFileIndex.row(), 0, QModelIndex()); 0380 QString display2 = m_dirModel->data(idx2col0, Qt::DisplayRole).toString(); 0381 QCOMPARE(display2, QString("toplevelfile_2")); 0382 0383 // Subdir: check child count 0384 QCOMPARE(m_dirModel->data(m_dirIndex, KDirModel::ChildCountRole).toInt(), 4); 0385 0386 // Subsubdir: check child count 0387 QCOMPARE(m_dirModel->data(m_fileInSubdirIndex.parent(), KDirModel::ChildCountRole).toInt(), 1); 0388 } 0389 0390 void KDirModelTest::testIcon() 0391 { 0392 /** 0393 * Create test data only used in testIcon() 0394 * PATH/subdirwithcustomicon 0395 * PATH/subdirwithcustomicon/.directory 0396 * PATH/subdirwithcustomicon/exampleIcon.svg 0397 * PATH/subdirwithcustomicon/subdirwithinvalidicon 0398 * PATH/subdirwithcustomicon/subdirwithinvalidicon/.directory 0399 * PATH/subdirwithcustomicon/subdirwithinvalidsvgicon 0400 * PATH/subdirwithcustomicon/subdirwithinvalidsvgicon/.directory 0401 */ 0402 const QString path(m_tempDir->path() + '/'); 0403 0404 const QString subdirWithCustomIconPath(path + "subdirwithcustomicon/"); 0405 createTestDirectory(path + "subdirwithcustomicon/", Empty); 0406 0407 const QString exampleIconPath = subdirWithCustomIconPath + "exampleIcon.svg"; 0408 const QByteArray exampleSvgIconContent( 0409 "<svg version='1.1' width='100' height='100' xmlns='http://www.w3.org/2000/svg'>" 0410 "<rect width='100%' height='100%' fill='red' />" 0411 "</svg>"); 0412 const QByteArray directoryFileContent( 0413 "[Desktop Entry]\n" 0414 "Icon=" 0415 + exampleIconPath.toLatin1()); 0416 0417 struct Entry { 0418 bool isDir; 0419 QString name; 0420 QByteArray content; 0421 }; 0422 0423 const std::array<Entry, 4> topLevelContent{{ 0424 {false, exampleIconPath, exampleSvgIconContent}, 0425 {false, subdirWithCustomIconPath + ".directory", directoryFileContent}, 0426 {true, subdirWithCustomIconPath + "subdirwithinvalidicon", QByteArray()}, 0427 {true, subdirWithCustomIconPath + "subdirwithinvalidsvgicon", QByteArray()}, 0428 }}; 0429 0430 for (const auto &entry : topLevelContent) { 0431 if (entry.isDir) { 0432 createTestDirectory(entry.name, Empty); 0433 } else { 0434 createTestFile(entry.name, false, entry.content); 0435 } 0436 } 0437 0438 // Add an extra suffix to the end of the path to make it become invalid 0439 const QByteArray directoryFileWithInvalidIconContent( 0440 "[Desktop Entry]\n" 0441 "Icon=" 0442 + exampleIconPath.toLatin1() + ".png"); 0443 createTestFile(subdirWithCustomIconPath + "subdirwithinvalidicon/.directory", false, directoryFileWithInvalidIconContent); 0444 0445 // Add an extra ".svg" to the end of the path to make it become invalid 0446 const QByteArray directoryFileWithInvalidSvgIconContent( 0447 "[Desktop Entry]\n" 0448 "Icon=" 0449 + exampleIconPath.toLatin1() + ".svg"); 0450 createTestFile(subdirWithCustomIconPath + "subdirwithinvalidsvgicon/.directory", false, directoryFileWithInvalidSvgIconContent); 0451 0452 // Refresh the directory after adding files 0453 KDirLister *dirLister = m_dirModel->dirLister(); 0454 QSignalSpy completedSpy(dirLister, qOverload<>(&KCoreDirLister::completed)); 0455 dirLister->openUrl(QUrl::fromLocalFile(path), KDirLister::Reload); 0456 QVERIFY(completedSpy.wait()); 0457 completedSpy.clear(); 0458 0459 QModelIndex dirWithIconIndex; // subdirwithcustomicon 0460 0461 for (int row = 0; row < m_topLevelFileNames.count() + 2 /*subdir, subdirwithinvalidicon*/; ++row) { 0462 const QModelIndex idx = m_dirModel->index(row, 0, QModelIndex()); 0463 QVERIFY(idx.isValid()); 0464 const KFileItem item = m_dirModel->itemForIndex(idx); 0465 if (item.isDir() && item.name() == QStringLiteral("subdirwithcustomicon")) { 0466 dirWithIconIndex = idx; 0467 break; 0468 } 0469 } 0470 0471 QVERIFY(dirWithIconIndex.isValid()); 0472 0473 // Now list subdirwithcustomicon/ 0474 QVERIFY(m_dirModel->canFetchMore(dirWithIconIndex)); 0475 qDebug() << "Listing subdirwithcustomicon"; 0476 m_dirModel->fetchMore(dirWithIconIndex); 0477 QVERIFY(completedSpy.wait()); 0478 completedSpy.clear(); 0479 0480 // Indexes of files inside a directory 0481 QModelIndex dirWithInvalidIconIndex; // subdirwithcustomicon/subdirwithinvalidicon 0482 QModelIndex dirWithInvalidSvgIconIndex; // subdirwithcustomicon/subdirwithinvalidsvgicon 0483 0484 for (int row = 0; row < static_cast<int>(topLevelContent.size()); ++row) { 0485 QModelIndex idx = m_dirModel->index(row, 0, dirWithIconIndex); 0486 const KFileItem item = m_dirModel->itemForIndex(idx); 0487 if (item.isDir()) { 0488 if (item.name() == QLatin1String("subdirwithinvalidicon")) { 0489 dirWithInvalidIconIndex = idx; 0490 } else if (item.name() == QLatin1String("subdirwithinvalidsvgicon")) { 0491 dirWithInvalidSvgIconIndex = idx; 0492 } 0493 } 0494 } 0495 0496 QVERIFY(dirWithInvalidIconIndex.isValid()); 0497 QVERIFY(dirWithInvalidSvgIconIndex.isValid()); 0498 0499 // Create an expected icon image 0500 const QSize comparedSize(100, 100); 0501 const QImage expectedIconImage = QIcon(exampleIconPath).pixmap(comparedSize).toImage(); 0502 0503 // Test the custom icon is correctly loaded for the directory 0504 const QIcon icon = m_dirModel->data(dirWithIconIndex, Qt::DecorationRole).value<QIcon>(); 0505 QCOMPARE(m_dirModel->itemForIndex(dirWithIconIndex).iconName(), exampleIconPath); 0506 QVERIFY(icon.pixmap(comparedSize).toImage() == expectedIconImage); 0507 0508 // Test "unknown" icon will be used when the icon path refers to an invalid icon 0509 const QIcon icon2 = m_dirModel->data(dirWithInvalidIconIndex, Qt::DecorationRole).value<QIcon>(); 0510 QCOMPARE(icon2.name(), "unknown"); 0511 0512 // Test "unknown" icon will be used when the icon path refers to an invalid svg icon 0513 const QIcon icon3 = m_dirModel->data(dirWithInvalidSvgIconIndex, Qt::DecorationRole).value<QIcon>(); 0514 QCOMPARE(icon3.name(), "unknown"); 0515 0516 // Delete the folder 0517 QDir dir(subdirWithCustomIconPath); 0518 QVERIFY(dir.removeRecursively()); 0519 0520 // Refresh the directory again after deleting the folder 0521 dirLister->openUrl(QUrl::fromLocalFile(path), KDirLister::Reload); 0522 QVERIFY(completedSpy.wait()); 0523 completedSpy.clear(); 0524 } 0525 0526 void KDirModelTest::testReload() 0527 { 0528 fillModel(true); 0529 testItemForIndex(); 0530 } 0531 0532 // We want more info than just "the values differ", if they do. 0533 /* clang-format off */ 0534 #define COMPARE_INDEXES(a, b) \ 0535 QCOMPARE(a.row(), b.row()); \ 0536 QCOMPARE(a.column(), b.column()); \ 0537 QCOMPARE(a.model(), b.model()); \ 0538 QCOMPARE(a.parent().isValid(), b.parent().isValid()); \ 0539 QCOMPARE(a, b); 0540 /* clang-format on */ 0541 0542 void KDirModelTest::testModifyFile() 0543 { 0544 const QString file = m_tempDir->path() + "/toplevelfile_2"; 0545 0546 QSignalSpy spyDataChanged(m_dirModel, &QAbstractItemModel::dataChanged); 0547 0548 // "Touch" the file 0549 setTimeStamp(file, s_referenceTimeStamp.addSecs(20)); 0550 0551 // In stat mode, kdirwatch doesn't notice file changes; we need to trigger it 0552 // by creating a file. 0553 // createTestFile(m_tempDir->path() + "/toplevelfile_5"); 0554 KDirWatch::self()->setDirty(m_tempDir->path()); 0555 0556 // Wait for KDirWatch to notify the change (especially when using Stat) 0557 if (spyDataChanged.isEmpty()) { 0558 QVERIFY(spyDataChanged.wait()); 0559 } 0560 0561 // If we come here, then dataChanged() was emitted - all good. 0562 const QVariantList dataChanged = spyDataChanged[0]; 0563 QModelIndex receivedIndex = dataChanged[0].value<QModelIndex>(); 0564 COMPARE_INDEXES(receivedIndex, m_secondFileIndex); 0565 receivedIndex = dataChanged[1].value<QModelIndex>(); 0566 QCOMPARE(receivedIndex.row(), m_secondFileIndex.row()); // only compare row; column is count-1 0567 } 0568 0569 void KDirModelTest::testRenameFile() 0570 { 0571 const QUrl url = QUrl::fromLocalFile(m_tempDir->path() + "/toplevelfile_2"); 0572 const QUrl newUrl = QUrl::fromLocalFile(m_tempDir->path() + "/toplevelfile_2_renamed"); 0573 0574 QSignalSpy spyDataChanged(m_dirModel, &QAbstractItemModel::dataChanged); 0575 0576 KIO::SimpleJob *job = KIO::rename(url, newUrl, KIO::HideProgressInfo); 0577 QVERIFY(job->exec()); 0578 0579 // Wait for the DBUS signal from KDirNotify, it's the one the triggers dataChanged 0580 if (spyDataChanged.isEmpty()) { 0581 QVERIFY(spyDataChanged.wait()); 0582 } 0583 0584 // If we come here, then dataChanged() was emitted - all good. 0585 QCOMPARE(spyDataChanged.count(), 1); 0586 COMPARE_INDEXES(spyDataChanged[0][0].value<QModelIndex>(), m_secondFileIndex); 0587 QModelIndex receivedIndex = spyDataChanged[0][1].value<QModelIndex>(); 0588 QCOMPARE(receivedIndex.row(), m_secondFileIndex.row()); // only compare row; column is count-1 0589 0590 // check renaming happened 0591 QCOMPARE(m_dirModel->itemForIndex(m_secondFileIndex).url().toString(), newUrl.toString()); 0592 0593 // check that KDirLister::cachedItemForUrl won't give a bad name if copying that item (#195385) 0594 KFileItem cachedItem = KDirLister::cachedItemForUrl(newUrl); 0595 QVERIFY(!cachedItem.isNull()); 0596 QCOMPARE(cachedItem.name(), QString("toplevelfile_2_renamed")); 0597 QCOMPARE(cachedItem.entry().stringValue(KIO::UDSEntry::UDS_NAME), QString("toplevelfile_2_renamed")); 0598 0599 // Put things back to normal 0600 spyDataChanged.clear(); 0601 job = KIO::rename(newUrl, url, KIO::HideProgressInfo); 0602 QVERIFY(job->exec()); 0603 // Wait for the DBUS signal from KDirNotify, it's the one the triggers dataChanged 0604 if (spyDataChanged.isEmpty()) { 0605 QVERIFY(spyDataChanged.wait()); 0606 } 0607 QCOMPARE(m_dirModel->itemForIndex(m_secondFileIndex).url().toString(), url.toString()); 0608 } 0609 0610 void KDirModelTest::testMoveDirectory() 0611 { 0612 testMoveDirectory(QStringLiteral("subdir")); 0613 } 0614 0615 void KDirModelTest::testMoveDirectory(const QString &dir /*just a dir name, no slash*/) 0616 { 0617 const QString path = m_tempDir->path() + '/'; 0618 const QString srcdir = path + dir; 0619 QVERIFY(QDir(srcdir).exists()); 0620 QTemporaryDir destDir(homeTmpDir()); 0621 const QString dest = destDir.path() + '/'; 0622 QVERIFY(QDir(dest).exists()); 0623 0624 // Move 0625 qDebug() << "Moving" << srcdir << "to" << dest; 0626 KIO::CopyJob *job = KIO::move(QUrl::fromLocalFile(srcdir), QUrl::fromLocalFile(dest), KIO::HideProgressInfo); 0627 job->setUiDelegate(nullptr); 0628 job->setUiDelegateExtension(nullptr); 0629 QVERIFY(job->exec()); 0630 0631 // wait for kdirnotify 0632 QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); 0633 if (spyRowsRemoved.isEmpty()) { 0634 QVERIFY(spyRowsRemoved.wait()); 0635 } 0636 0637 QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir")).isValid()); 0638 QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed")).isValid()); 0639 0640 // Move back 0641 qDebug() << "Moving" << dest + dir << "back to" << srcdir; 0642 job = KIO::move(QUrl::fromLocalFile(dest + dir), QUrl::fromLocalFile(srcdir), KIO::HideProgressInfo); 0643 job->setUiDelegate(nullptr); 0644 job->setUiDelegateExtension(nullptr); 0645 QVERIFY(job->exec()); 0646 0647 QSignalSpy spyRowsInserted(m_dirModel, &QAbstractItemModel::rowsInserted); 0648 if (spyRowsInserted.isEmpty()) { 0649 QVERIFY(spyRowsInserted.wait()); 0650 } 0651 0652 QVERIFY(QDir(srcdir).exists()); 0653 0654 // m_dirIndex is invalid after the above... 0655 fillModel(true); 0656 } 0657 0658 void KDirModelTest::testRenameDirectory() // #172945, #174703, (and #180156) 0659 { 0660 const QString path = m_tempDir->path() + '/'; 0661 const QUrl url = QUrl::fromLocalFile(path + "subdir"); 0662 const QUrl newUrl = QUrl::fromLocalFile(path + "subdir_renamed"); 0663 0664 // For #180156 we need a second kdirmodel, viewing the subdir being renamed. 0665 // I'm abusing m_dirModelForExpand for that purpose. 0666 delete m_dirModelForExpand; 0667 m_dirModelForExpand = new KDirModel; 0668 KDirLister *dirListerForExpand = m_dirModelForExpand->dirLister(); 0669 QSignalSpy spyCompleted(dirListerForExpand, qOverload<>(&KCoreDirLister::completed)); 0670 dirListerForExpand->openUrl(url); // async 0671 QVERIFY(spyCompleted.wait()); 0672 0673 // Now do the renaming 0674 QSignalSpy spyDataChanged(m_dirModel, &QAbstractItemModel::dataChanged); 0675 KIO::SimpleJob *job = KIO::rename(url, newUrl, KIO::HideProgressInfo); 0676 QVERIFY(job->exec()); 0677 0678 // Wait for the DBUS signal from KDirNotify, it's the one the triggers dataChanged 0679 if (spyDataChanged.isEmpty()) { 0680 QVERIFY(spyDataChanged.wait()); 0681 } 0682 spyDataChanged.clear(); 0683 0684 // If we come here, then dataChanged() was emitted - all good. 0685 // QCOMPARE(spyDataChanged.count(), 1); // it was in fact emitted 5 times... 0686 // COMPARE_INDEXES(spyDataChanged[0][0].value<QModelIndex>(), m_dirIndex); 0687 // QModelIndex receivedIndex = spyDataChanged[0][1].value<QModelIndex>(); 0688 // QCOMPARE(receivedIndex.row(), m_dirIndex.row()); // only compare row; column is count-1 0689 0690 // check renaming happened 0691 QCOMPARE(m_dirModel->itemForIndex(m_dirIndex).url().toString(), newUrl.toString()); 0692 qDebug() << newUrl << "indexForUrl=" << m_dirModel->indexForUrl(newUrl) << "m_dirIndex=" << m_dirIndex; 0693 QCOMPARE(m_dirModel->indexForUrl(newUrl), m_dirIndex); 0694 QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed")).isValid()); 0695 QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed/testfile")).isValid()); 0696 QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed/subsubdir")).isValid()); 0697 QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed/subsubdir/testfile")).isValid()); 0698 0699 // Check the other kdirmodel got redirected 0700 QCOMPARE(dirListerForExpand->url().toLocalFile(), QString(path + "subdir_renamed")); 0701 0702 qDebug() << "calling testMoveDirectory(subdir_renamed)"; 0703 0704 // Test moving the renamed directory; if something inside KDirModel 0705 // wasn't properly updated by the renaming, this would detect it and crash (#180673) 0706 testMoveDirectory(QStringLiteral("subdir_renamed")); 0707 0708 // Put things back to normal 0709 job = KIO::rename(newUrl, url, KIO::HideProgressInfo); 0710 QVERIFY(job->exec()); 0711 // Wait for the DBUS signal from KDirNotify, it's the one the triggers dataChanged 0712 if (spyDataChanged.isEmpty()) { 0713 QVERIFY(spyDataChanged.wait()); 0714 } 0715 QCOMPARE(m_dirModel->itemForIndex(m_dirIndex).url().toString(), url.toString()); 0716 0717 QCOMPARE(m_dirModel->itemForIndex(m_dirIndex).url().toString(), url.toString()); 0718 QCOMPARE(m_dirModel->indexForUrl(url), m_dirIndex); 0719 QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir")).isValid()); 0720 QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir/testfile")).isValid()); 0721 QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir/subsubdir")).isValid()); 0722 QVERIFY(m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir/subsubdir/testfile")).isValid()); 0723 QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed")).isValid()); 0724 QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed/testfile")).isValid()); 0725 QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed/subsubdir")).isValid()); 0726 QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir_renamed/subsubdir/testfile")).isValid()); 0727 0728 // TODO INVESTIGATE 0729 // QCOMPARE(dirListerForExpand->url().toLocalFile(), path+"subdir"); 0730 0731 delete m_dirModelForExpand; 0732 m_dirModelForExpand = nullptr; 0733 } 0734 0735 void KDirModelTest::testRenameDirectoryInCache() // #188807 0736 { 0737 // Ensure the stuff is in cache. 0738 fillModel(true); 0739 const QString path = m_tempDir->path() + '/'; 0740 QVERIFY(!m_dirModel->dirLister()->findByUrl(QUrl::fromLocalFile(path)).isNull()); 0741 0742 // No more dirmodel nor dirlister. 0743 delete m_dirModel; 0744 m_dirModel = nullptr; 0745 0746 // Now let's rename a directory that is in KCoreDirListerCache 0747 const QUrl url = QUrl::fromLocalFile(path); 0748 QUrl newUrl = url.adjusted(QUrl::StripTrailingSlash); 0749 newUrl.setPath(newUrl.path() + "_renamed"); 0750 qDebug() << newUrl; 0751 KIO::SimpleJob *job = KIO::rename(url, newUrl, KIO::HideProgressInfo); 0752 QVERIFY(job->exec()); 0753 0754 // Put things back to normal 0755 job = KIO::rename(newUrl, url, KIO::HideProgressInfo); 0756 QVERIFY(job->exec()); 0757 0758 // KDirNotify emits FileRenamed for each rename() above, which in turn 0759 // re-lists the directory. We need to wait for both signals to be emitted 0760 // otherwise the dirlister will not be in the state we expect. 0761 QTest::qWait(200); 0762 0763 fillModel(true); 0764 0765 QVERIFY(m_dirIndex.isValid()); 0766 KFileItem rootItem = m_dirModel->dirLister()->findByUrl(QUrl::fromLocalFile(path)); 0767 QVERIFY(!rootItem.isNull()); 0768 } 0769 0770 void KDirModelTest::testChmodDirectory() // #53397 0771 { 0772 QSignalSpy spyDataChanged(m_dirModel, &QAbstractItemModel::dataChanged); 0773 const QString path = m_tempDir->path() + '/'; 0774 KFileItem rootItem = m_dirModel->itemForIndex(QModelIndex()); 0775 const mode_t origPerm = rootItem.permissions(); 0776 mode_t newPerm = origPerm ^ S_IWGRP; 0777 // const QFile::Permissions origPerm = rootItem.filePermissions(); 0778 // QVERIFY(origPerm & QFile::ReadOwner); 0779 // const QFile::Permissions newPerm = origPerm ^ QFile::WriteGroup; 0780 QVERIFY(newPerm != origPerm); 0781 KIO::Job *job = KIO::chmod({rootItem}, newPerm, S_IWGRP /*TODO: QFile::WriteGroup*/, QString(), QString(), false, KIO::HideProgressInfo); 0782 job->setUiDelegate(nullptr); 0783 QVERIFY(job->exec()); 0784 // ChmodJob doesn't talk to KDirNotify, kpropertiesdialog does. 0785 // [this allows to group notifications after all the changes one can make in the dialog] 0786 org::kde::KDirNotify::emitFilesChanged(QList<QUrl>{QUrl::fromLocalFile(path)}); 0787 // Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved 0788 if (spyDataChanged.isEmpty()) { 0789 QVERIFY(spyDataChanged.wait()); 0790 } 0791 0792 // If we come here, then dataChanged() was emitted - all good. 0793 QCOMPARE(spyDataChanged.count(), 1); 0794 QModelIndex receivedIndex = spyDataChanged[0][0].value<QModelIndex>(); 0795 qDebug() << "receivedIndex" << receivedIndex; 0796 QVERIFY(!receivedIndex.isValid()); 0797 0798 const KFileItem newRootItem = m_dirModel->itemForIndex(QModelIndex()); 0799 QVERIFY(!newRootItem.isNull()); 0800 QCOMPARE(QString::number(newRootItem.permissions(), 16), QString::number(newPerm, 16)); 0801 } 0802 0803 enum { 0804 NoFlag = 0, 0805 NewDir = 1, // whether to re-create a new QTemporaryDir completely, to avoid cached fileitems 0806 ListFinalDir = 2, // whether to list the target dir at the same time, like k3b, for #193364 0807 Recreate = 4, 0808 CacheSubdir = 8, // put subdir in the cache before expandToUrl 0809 // flags, next item is 16! 0810 }; 0811 0812 void KDirModelTest::testExpandToUrl_data() 0813 { 0814 QTest::addColumn<int>("flags"); // see enum above 0815 QTest::addColumn<QString>("expandToPath"); // relative path 0816 QTest::addColumn<QStringList>("expectedExpandSignals"); 0817 0818 QTest::newRow("the root, nothing to do") << int(NoFlag) << QString() << QStringList(); 0819 QTest::newRow(".") << int(NoFlag) << "." << (QStringList()); 0820 QTest::newRow("subdir") << int(NoFlag) << "subdir" << QStringList{QStringLiteral("subdir")}; 0821 QTest::newRow("subdir/.") << int(NoFlag) << "subdir/." << QStringList{QStringLiteral("subdir")}; 0822 0823 const QString subsubdir = QStringLiteral("subdir/subsubdir"); 0824 // Must list root, emit expand for subdir, list subdir, emit expand for subsubdir. 0825 QTest::newRow("subdir/subsubdir") << int(NoFlag) << subsubdir << QStringList{QStringLiteral("subdir"), subsubdir}; 0826 0827 // Must list root, emit expand for subdir, list subdir, emit expand for subsubdir, list subsubdir. 0828 const QString subsubdirfile = subsubdir + "/testfile"; 0829 QTest::newRow("subdir/subsubdir/testfile sync") << int(NoFlag) << subsubdirfile << QStringList{QStringLiteral("subdir"), subsubdir, subsubdirfile}; 0830 0831 #ifndef Q_OS_WIN 0832 // Expand a symlink to a directory (#219547) 0833 const QString dirlink = m_tempDir->path() + "/dirlink"; 0834 createTestSymlink(dirlink, "subdir"); // dirlink -> subdir 0835 QVERIFY(QFileInfo(dirlink).isSymLink()); 0836 // If this test fails, your first move should be to enable all debug output and see if KDirWatch says inotify failed 0837 QTest::newRow("dirlink") << int(NoFlag) << "dirlink/subsubdir" << QStringList{QStringLiteral("dirlink"), QStringLiteral("dirlink/subsubdir")}; 0838 #endif 0839 0840 // Do a cold-cache test too, but nowadays it doesn't change anything anymore, 0841 // apart from testing different code paths inside KDirLister. 0842 QTest::newRow("subdir/subsubdir/testfile with reload") << int(NewDir) << subsubdirfile << QStringList{QStringLiteral("subdir"), subsubdir, subsubdirfile}; 0843 0844 QTest::newRow("hold dest dir") // #193364 0845 << int(NewDir | ListFinalDir) << subsubdirfile << QStringList{QStringLiteral("subdir"), subsubdir, subsubdirfile}; 0846 0847 // Put subdir in cache too (#175035) 0848 QTest::newRow("hold subdir and dest dir") << int(NewDir | CacheSubdir | ListFinalDir | Recreate) << subsubdirfile 0849 << QStringList{QStringLiteral("subdir"), subsubdir, subsubdirfile}; 0850 0851 // Make sure the last test has the Recreate option set, for the subsequent test methods. 0852 } 0853 0854 void KDirModelTest::testExpandToUrl() 0855 { 0856 QFETCH(int, flags); 0857 QFETCH(QString, expandToPath); // relative 0858 QFETCH(QStringList, expectedExpandSignals); 0859 0860 if (flags & NewDir) { 0861 recreateTestData(); 0862 // WARNING! m_dirIndex, m_fileIndex, m_secondFileIndex etc. are not valid anymore after this point! 0863 } 0864 0865 const QString path = m_tempDir->path() + '/'; 0866 if (flags & CacheSubdir) { 0867 // This way, the listDir for subdir will find items in cache, and will schedule a CachedItemsJob 0868 m_dirModel->dirLister()->openUrl(QUrl::fromLocalFile(path + "subdir")); 0869 QSignalSpy completedSpy(m_dirModel->dirLister(), qOverload<>(&KCoreDirLister::completed)); 0870 QVERIFY(completedSpy.wait(2000)); 0871 } 0872 if (flags & ListFinalDir) { 0873 // This way, the last listDir will find items in cache, and will schedule a CachedItemsJob 0874 m_dirModel->dirLister()->openUrl(QUrl::fromLocalFile(path + "subdir/subsubdir")); 0875 QSignalSpy completedSpy(m_dirModel->dirLister(), qOverload<>(&KCoreDirLister::completed)); 0876 QVERIFY(completedSpy.wait(2000)); 0877 } 0878 0879 if (!m_dirModelForExpand || (flags & NewDir)) { 0880 delete m_dirModelForExpand; 0881 m_dirModelForExpand = new KDirModel; 0882 connect(m_dirModelForExpand, &KDirModel::expand, this, &KDirModelTest::slotExpand); 0883 connect(m_dirModelForExpand, &QAbstractItemModel::rowsInserted, this, &KDirModelTest::slotRowsInserted); 0884 KDirLister *dirListerForExpand = m_dirModelForExpand->dirLister(); 0885 dirListerForExpand->openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); // async 0886 } 0887 m_rowsInsertedEmitted = false; 0888 m_expectedExpandSignals = expectedExpandSignals; 0889 m_nextExpectedExpandSignals = 0; 0890 QSignalSpy spyExpand(m_dirModelForExpand, &KDirModel::expand); 0891 m_urlToExpandTo = QUrl::fromLocalFile(path + expandToPath); 0892 // If KDirModel doesn't know this URL yet, then we want to see rowsInserted signals 0893 // being emitted, so that the slots can get the index to that url then. 0894 m_expectRowsInserted = !expandToPath.isEmpty() && !m_dirModelForExpand->indexForUrl(m_urlToExpandTo).isValid(); 0895 QVERIFY(QFileInfo::exists(m_urlToExpandTo.toLocalFile())); 0896 m_dirModelForExpand->expandToUrl(m_urlToExpandTo); 0897 if (expectedExpandSignals.isEmpty()) { 0898 QTest::qWait(20); // to make sure we process queued connection calls, otherwise spyExpand.count() is always 0 even if there's a bug... 0899 QCOMPARE(spyExpand.count(), 0); 0900 } else { 0901 if (spyExpand.count() < expectedExpandSignals.count()) { 0902 QTRY_COMPARE(spyExpand.count(), expectedExpandSignals.count()); 0903 } 0904 if (m_expectRowsInserted) { 0905 QVERIFY(m_rowsInsertedEmitted); 0906 } 0907 } 0908 0909 // Now it should exist 0910 if (!expandToPath.isEmpty() && expandToPath != QLatin1String(".")) { 0911 qDebug() << "Do I know" << m_urlToExpandTo << "?"; 0912 QVERIFY(m_dirModelForExpand->indexForUrl(m_urlToExpandTo).isValid()); 0913 } 0914 0915 if (flags & ListFinalDir) { 0916 testUpdateParentAfterExpand(); 0917 } 0918 0919 if (flags & Recreate) { 0920 // Clean up, for the next tests 0921 recreateTestData(); 0922 fillModel(false); 0923 } 0924 } 0925 0926 void KDirModelTest::slotExpand(const QModelIndex &index) 0927 { 0928 QVERIFY(index.isValid()); 0929 const QString path = m_tempDir->path() + '/'; 0930 KFileItem item = m_dirModelForExpand->itemForIndex(index); 0931 QVERIFY(!item.isNull()); 0932 qDebug() << item.url().toLocalFile(); 0933 QCOMPARE(item.url().toLocalFile(), QString(path + m_expectedExpandSignals[m_nextExpectedExpandSignals++])); 0934 0935 // if rowsInserted wasn't emitted yet, then any proxy model would be unable to do anything with index at this point 0936 if (item.url() == m_urlToExpandTo) { 0937 QVERIFY(m_dirModelForExpand->indexForUrl(m_urlToExpandTo).isValid()); 0938 if (m_expectRowsInserted) { 0939 QVERIFY(m_rowsInsertedEmitted); 0940 } 0941 } 0942 } 0943 0944 void KDirModelTest::slotRowsInserted(const QModelIndex &, int, int) 0945 { 0946 m_rowsInsertedEmitted = true; 0947 } 0948 0949 // This code is called by testExpandToUrl 0950 void KDirModelTest::testUpdateParentAfterExpand() // #193364 0951 { 0952 const QString path = m_tempDir->path() + '/'; 0953 const QString file = path + "subdir/aNewFile"; 0954 qDebug() << "Creating" << file; 0955 QVERIFY(!QFile::exists(file)); 0956 createTestFile(file); 0957 QSignalSpy spyRowsInserted(m_dirModelForExpand, &QAbstractItemModel::rowsInserted); 0958 QVERIFY(spyRowsInserted.wait(1000)); 0959 } 0960 0961 void KDirModelTest::testFilter() 0962 { 0963 QVERIFY(m_dirIndex.isValid()); 0964 const int oldTopLevelRowCount = m_dirModel->rowCount(); 0965 const int oldSubdirRowCount = m_dirModel->rowCount(m_dirIndex); 0966 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 100) 0967 QSignalSpy spyItemsFilteredByMime(m_dirModel->dirLister(), &KCoreDirLister::itemsFilteredByMime); 0968 #endif 0969 QSignalSpy spyItemsDeleted(m_dirModel->dirLister(), &KCoreDirLister::itemsDeleted); 0970 QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); 0971 m_dirModel->dirLister()->setNameFilter(QStringLiteral("toplevel*")); 0972 QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); // no change yet 0973 QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); // no change yet 0974 m_dirModel->dirLister()->emitChanges(); 0975 0976 QCOMPARE(m_dirModel->rowCount(), 4); // 3 toplevel* files, one subdir 0977 QCOMPARE(m_dirModel->rowCount(m_dirIndex), 2); // the files get filtered out, subsubdir and hasChildren are remaining 0978 0979 // In the subdir, we can get rowsRemoved signals like (1,2) or (0,0)+(2,2), 0980 // depending on the order of the files in the model. 0981 // So QCOMPARE(spyRowsRemoved.count(), 3) is fragile, we rather need 0982 // to sum up the removed rows per parent directory. 0983 QMap<QString, int> rowsRemovedPerDir; 0984 for (int i = 0; i < spyRowsRemoved.count(); ++i) { 0985 const QVariantList args = spyRowsRemoved[i]; 0986 const QModelIndex parentIdx = args[0].value<QModelIndex>(); 0987 QString dirName; 0988 if (parentIdx.isValid()) { 0989 const KFileItem item = m_dirModel->itemForIndex(parentIdx); 0990 dirName = item.name(); 0991 } else { 0992 dirName = QStringLiteral("root"); 0993 } 0994 rowsRemovedPerDir[dirName] += args[2].toInt() - args[1].toInt() + 1; 0995 // qDebug() << parentIdx << args[1].toInt() << args[2].toInt(); 0996 } 0997 QCOMPARE(rowsRemovedPerDir.count(), 3); // once for every dir 0998 QCOMPARE(rowsRemovedPerDir.value("root"), 1); // one from toplevel ('special chars') 0999 QCOMPARE(rowsRemovedPerDir.value("subdir"), 2); // two from subdir 1000 QCOMPARE(rowsRemovedPerDir.value("subsubdir"), 1); // one from subsubdir 1001 QCOMPARE(spyItemsDeleted.count(), 3); // once for every dir 1002 QCOMPARE(spyItemsDeleted[0][0].value<KFileItemList>().count(), 1); // one from toplevel ('special chars') 1003 QCOMPARE(spyItemsDeleted[1][0].value<KFileItemList>().count(), 2); // two from subdir 1004 QCOMPARE(spyItemsDeleted[2][0].value<KFileItemList>().count(), 1); // one from subsubdir 1005 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 100) 1006 QCOMPARE(spyItemsFilteredByMime.count(), 0); 1007 #endif 1008 spyItemsDeleted.clear(); 1009 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 100) 1010 spyItemsFilteredByMime.clear(); 1011 #endif 1012 1013 // Reset the filter 1014 qDebug() << "reset to no filter"; 1015 m_dirModel->dirLister()->setNameFilter(QString()); 1016 m_dirModel->dirLister()->emitChanges(); 1017 1018 QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); 1019 QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); 1020 QCOMPARE(spyItemsDeleted.count(), 0); 1021 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 100) 1022 QCOMPARE(spyItemsFilteredByMime.count(), 0); 1023 #endif 1024 1025 // The order of things changed because of filtering. 1026 // Fill again, so that m_fileIndex etc. are correct again. 1027 fillModel(true); 1028 } 1029 1030 void KDirModelTest::testFilterPatterns() 1031 { 1032 QVERIFY(m_dirIndex.isValid()); 1033 1034 const int oldTopLevelRowCount = m_dirModel->rowCount(); 1035 const int oldSubdirRowCount = m_dirModel->rowCount(m_dirIndex); 1036 1037 // matching without wildcard -> no files 1038 m_dirModel->dirLister()->setNameFilter(QStringLiteral("toplevel")); 1039 QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); // no change yet 1040 QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); // no change yet 1041 m_dirModel->dirLister()->emitChanges(); 1042 1043 QCOMPARE(m_dirModel->rowCount(), 1); // the files get filtered out, subdir 1044 QCOMPARE(m_dirModel->rowCount(m_dirIndex), 2); // the files get filtered out, subsubdir and hasChildren 1045 1046 // Reset the filter 1047 m_dirModel->dirLister()->setNameFilter(QString()); 1048 m_dirModel->dirLister()->emitChanges(); 1049 1050 // Back to original state 1051 QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); 1052 QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); 1053 1054 m_dirModel->dirLister()->setNameFilter(QStringLiteral("toplevel*")); // Matching with a wildcard 1055 QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); // no change yet 1056 QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); // no change yet 1057 m_dirModel->dirLister()->emitChanges(); 1058 1059 QCOMPARE(m_dirModel->rowCount(), 4); // 3 files, one subdir with "toplevel*" in the name 1060 QCOMPARE(m_dirModel->rowCount(m_dirIndex), 2); // the files get filtered out, subsubdir and hasChildren are remaining 1061 1062 m_dirModel->dirLister()->setNameFilter(QString()); 1063 m_dirModel->dirLister()->emitChanges(); 1064 1065 QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); 1066 QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); 1067 1068 // The order of things changed because of filtering. 1069 // Fill again, so that m_fileIndex etc. are correct again. 1070 fillModel(true); 1071 } 1072 1073 void KDirModelTest::testMimeFilter() 1074 { 1075 QVERIFY(m_dirIndex.isValid()); 1076 const int oldTopLevelRowCount = m_dirModel->rowCount(); 1077 const int oldSubdirRowCount = m_dirModel->rowCount(m_dirIndex); 1078 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 100) 1079 QSignalSpy spyItemsFilteredByMime(m_dirModel->dirLister(), &KCoreDirLister::itemsFilteredByMime); 1080 #endif 1081 QSignalSpy spyItemsDeleted(m_dirModel->dirLister(), &KCoreDirLister::itemsDeleted); 1082 QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); 1083 m_dirModel->dirLister()->setMimeFilter(QStringList{QStringLiteral("application/pdf")}); 1084 QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); // no change yet 1085 QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); // no change yet 1086 m_dirModel->dirLister()->emitChanges(); 1087 1088 QCOMPARE(m_dirModel->rowCount(), 1); // 1 pdf files, no subdir anymore 1089 1090 QVERIFY(spyRowsRemoved.count() >= 1); // depends on contiguity... 1091 QVERIFY(spyItemsDeleted.count() >= 1); // once for every dir 1092 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 100) 1093 // Maybe it would make sense to have those items in itemsFilteredByMime, 1094 // but well, for the only existing use of that signal (MIME type filter plugin), 1095 // it's not really necessary, the plugin has seen those files before anyway. 1096 // The signal is mostly useful for the case of listing a dir with a MIME type filter set. 1097 // QCOMPARE(spyItemsFilteredByMime.count(), 1); 1098 // QCOMPARE(spyItemsFilteredByMime[0][0].value<KFileItemList>().count(), 4); 1099 spyItemsFilteredByMime.clear(); 1100 #endif 1101 spyItemsDeleted.clear(); 1102 1103 // Reset the filter 1104 qDebug() << "reset to no filter"; 1105 m_dirModel->dirLister()->setMimeFilter(QStringList()); 1106 m_dirModel->dirLister()->emitChanges(); 1107 1108 QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); 1109 QCOMPARE(spyItemsDeleted.count(), 0); 1110 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 100) 1111 QCOMPARE(spyItemsFilteredByMime.count(), 0); 1112 #endif 1113 1114 // The order of things changed because of filtering. 1115 // Fill again, so that m_fileIndex etc. are correct again. 1116 fillModel(true); 1117 } 1118 1119 void KDirModelTest::testShowHiddenFiles() // #174788 1120 { 1121 KDirLister *dirLister = m_dirModel->dirLister(); 1122 1123 QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); 1124 QSignalSpy spyNewItems(dirLister, &KCoreDirLister::newItems); 1125 QSignalSpy spyRowsInserted(m_dirModel, &QAbstractItemModel::rowsInserted); 1126 dirLister->setShowHiddenFiles(true); 1127 dirLister->emitChanges(); 1128 const int numberOfDotFiles = 2; 1129 QCOMPARE(spyNewItems.count(), 1); 1130 QCOMPARE(spyNewItems[0][0].value<KFileItemList>().count(), numberOfDotFiles); 1131 QCOMPARE(spyRowsInserted.count(), 1); 1132 QCOMPARE(spyRowsRemoved.count(), 0); 1133 spyNewItems.clear(); 1134 spyRowsInserted.clear(); 1135 1136 dirLister->setShowHiddenFiles(false); 1137 dirLister->emitChanges(); 1138 QCOMPARE(spyNewItems.count(), 0); 1139 QCOMPARE(spyRowsInserted.count(), 0); 1140 QCOMPARE(spyRowsRemoved.count(), 1); 1141 } 1142 1143 void KDirModelTest::testMultipleSlashes() 1144 { 1145 const QString path = m_tempDir->path() + '/'; 1146 1147 QModelIndex index = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir//testfile")); 1148 QVERIFY(index.isValid()); 1149 1150 index = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir//subsubdir//")); 1151 QVERIFY(index.isValid()); 1152 1153 index = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir///subsubdir////testfile")); 1154 QVERIFY(index.isValid()); 1155 } 1156 1157 void KDirModelTest::testUrlWithRef() // #171117 1158 { 1159 const QString path = m_tempDir->path() + '/'; 1160 KDirLister *dirLister = m_dirModel->dirLister(); 1161 QUrl url = QUrl::fromLocalFile(path); 1162 url.setFragment(QStringLiteral("ref")); 1163 QVERIFY(url.url().endsWith(QLatin1String("#ref"))); 1164 QSignalSpy spyCompleted(dirLister, qOverload<>(&KCoreDirLister::completed)); 1165 dirLister->openUrl(url, KDirLister::NoFlags); 1166 QVERIFY(spyCompleted.wait()); 1167 1168 QCOMPARE(dirLister->url().toString(), url.toString(QUrl::StripTrailingSlash)); 1169 collectKnownIndexes(); 1170 } 1171 1172 // void KDirModelTest::testFontUrlWithHost() // #160057 --> moved to kio_fonts (kfontinst/kio/autotests) 1173 1174 void KDirModelTest::testRemoteUrlWithHost() // #178416 1175 { 1176 if (!KProtocolInfo::isKnownProtocol(QStringLiteral("remote"))) { 1177 QSKIP("kio_remote not installed"); 1178 } 1179 QUrl url(QStringLiteral("remote://foo")); 1180 KDirLister *dirLister = m_dirModel->dirLister(); 1181 QSignalSpy spyCompleted(dirLister, qOverload<>(&KCoreDirLister::completed)); 1182 dirLister->openUrl(url, KDirLister::NoFlags); 1183 QVERIFY(spyCompleted.wait()); 1184 1185 QCOMPARE(dirLister->url().toString(), QString("remote://foo")); 1186 } 1187 1188 void KDirModelTest::testZipFile() // # 171721 1189 { 1190 const QString path = QFileInfo(QFINDTESTDATA("wronglocalsizes.zip")).absolutePath(); 1191 KDirLister *dirLister = m_dirModel->dirLister(); 1192 QSignalSpy spyCompleted(dirLister, qOverload<>(&KCoreDirLister::completed)); 1193 dirLister->openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); 1194 QVERIFY(spyCompleted.wait()); 1195 1196 QUrl zipUrl(QUrl::fromLocalFile(path)); 1197 zipUrl.setPath(zipUrl.path() + "/wronglocalsizes.zip"); // just a zip file lying here for other reasons 1198 1199 QVERIFY(QFile::exists(zipUrl.toLocalFile())); 1200 zipUrl.setScheme(QStringLiteral("zip")); 1201 QModelIndex index = m_dirModel->indexForUrl(zipUrl); 1202 QVERIFY(!index.isValid()); // protocol mismatch, can't find it! 1203 zipUrl.setScheme(QStringLiteral("file")); 1204 index = m_dirModel->indexForUrl(zipUrl); 1205 QVERIFY(index.isValid()); 1206 } 1207 1208 class MyDirLister : public KDirLister 1209 { 1210 public: 1211 void emitItemsDeleted(const KFileItemList &items) 1212 { 1213 Q_EMIT itemsDeleted(items); 1214 } 1215 }; 1216 1217 void KDirModelTest::testBug196695() 1218 { 1219 KFileItem rootItem(QUrl::fromLocalFile(m_tempDir->path()), QString(), KFileItem::Unknown); 1220 KFileItem childItem(QUrl::fromLocalFile(QString(m_tempDir->path() + "/toplevelfile_1")), QString(), KFileItem::Unknown); 1221 1222 KFileItemList list; 1223 // Important: the root item must not be first in the list to trigger bug 196695 1224 list << childItem << rootItem; 1225 1226 MyDirLister *dirLister = static_cast<MyDirLister *>(m_dirModel->dirLister()); 1227 dirLister->emitItemsDeleted(list); 1228 1229 fillModel(true); 1230 } 1231 1232 void KDirModelTest::testMimeData() 1233 { 1234 QModelIndex index0 = m_dirModel->index(0, 0); 1235 QVERIFY(index0.isValid()); 1236 QModelIndex index1 = m_dirModel->index(1, 0); 1237 QVERIFY(index1.isValid()); 1238 QList<QModelIndex> indexes; 1239 indexes << index0 << index1; 1240 QMimeData *mimeData = m_dirModel->mimeData(indexes); 1241 QVERIFY(mimeData); 1242 QVERIFY(mimeData->hasUrls()); 1243 const QList<QUrl> urls = mimeData->urls(); 1244 QCOMPARE(urls.count(), indexes.count()); 1245 delete mimeData; 1246 } 1247 1248 void KDirModelTest::testDotHiddenFile_data() 1249 { 1250 QTest::addColumn<QStringList>("fileContents"); 1251 QTest::addColumn<QStringList>("expectedListing"); 1252 1253 const QStringList allItems{QStringLiteral("toplevelfile_1"), 1254 QStringLiteral("toplevelfile_2"), 1255 QStringLiteral("toplevelfile_3"), 1256 specialChars(), 1257 QStringLiteral("subdir")}; 1258 QTest::newRow("empty_file") << (QStringList{}) << allItems; 1259 1260 QTest::newRow("simple_name") << (QStringList{QStringLiteral("toplevelfile_1")}) << QStringList(allItems.mid(1)); 1261 1262 QStringList allButSpecialChars = allItems; 1263 allButSpecialChars.removeAt(3); 1264 QTest::newRow("special_chars") << (QStringList{specialChars()}) << allButSpecialChars; 1265 1266 QStringList allButSubdir = allItems; 1267 allButSubdir.removeAt(4); 1268 QTest::newRow("subdir") << (QStringList{QStringLiteral("subdir")}) << allButSubdir; 1269 1270 QTest::newRow("many_lines") 1271 << (QStringList{QStringLiteral("subdir"), QStringLiteral("toplevelfile_1"), QStringLiteral("toplevelfile_3"), QStringLiteral("toplevelfile_2")}) 1272 << QStringList{specialChars()}; 1273 } 1274 1275 void KDirModelTest::testDotHiddenFile() 1276 { 1277 QFETCH(QStringList, fileContents); 1278 QFETCH(QStringList, expectedListing); 1279 1280 const QString path = m_tempDir->path() + '/'; 1281 const QString dotHiddenFile = path + ".hidden"; 1282 QTest::qWait(1000); // mtime-based cache, so we need to wait for 1 second 1283 QFile dh(dotHiddenFile); 1284 QVERIFY(dh.open(QIODevice::WriteOnly)); 1285 dh.write(fileContents.join('\n').toUtf8()); 1286 dh.close(); 1287 1288 // Do it twice: once to read from the file and once to use the cache 1289 for (int i = 0; i < 2; ++i) { 1290 fillModel(true, false); 1291 QStringList files; 1292 for (int row = 0; row < m_dirModel->rowCount(); ++row) { 1293 files.append(m_dirModel->index(row, KDirModel::Name).data().toString()); 1294 } 1295 files.sort(); 1296 expectedListing.sort(); 1297 QCOMPARE(files, expectedListing); 1298 } 1299 1300 dh.remove(); 1301 } 1302 1303 void KDirModelTest::testShowRoot() 1304 { 1305 KDirModel dirModel; 1306 const QUrl homeUrl = QUrl::fromLocalFile(QDir::homePath()); 1307 const QUrl fsRootUrl = QUrl(QStringLiteral("file:///")); 1308 1309 // openUrl("/", ShowRoot) should create a "/" item 1310 dirModel.openUrl(fsRootUrl, KDirModel::ShowRoot); 1311 QTRY_COMPARE(dirModel.rowCount(), 1); 1312 const QModelIndex rootIndex = dirModel.index(0, 0); 1313 QVERIFY(rootIndex.isValid()); 1314 QCOMPARE(rootIndex.data().toString(), QStringLiteral("/")); 1315 QVERIFY(!dirModel.parent(rootIndex).isValid()); 1316 QCOMPARE(dirModel.itemForIndex(rootIndex).url(), QUrl(QStringLiteral("file:///"))); 1317 QCOMPARE(dirModel.itemForIndex(rootIndex).name(), QStringLiteral("/")); 1318 1319 // expandToUrl should work 1320 dirModel.expandToUrl(homeUrl); 1321 QTRY_VERIFY(dirModel.indexForUrl(homeUrl).isValid()); 1322 1323 // test itemForIndex and indexForUrl 1324 QCOMPARE(dirModel.itemForIndex(QModelIndex()).url(), QUrl()); 1325 QVERIFY(!dirModel.indexForUrl(QUrl()).isValid()); 1326 const QUrl slashUrl = QUrl::fromLocalFile(QStringLiteral("/")); 1327 QCOMPARE(dirModel.indexForUrl(slashUrl), rootIndex); 1328 1329 // switching to another URL should also show a root node 1330 QSignalSpy spyRowsRemoved(&dirModel, &QAbstractItemModel::rowsRemoved); 1331 const QUrl tempUrl = QUrl::fromLocalFile(QDir::tempPath()); 1332 dirModel.openUrl(tempUrl, KDirModel::ShowRoot); 1333 QTRY_COMPARE(dirModel.rowCount(), 1); 1334 QCOMPARE(spyRowsRemoved.count(), 1); 1335 const QModelIndex newRootIndex = dirModel.index(0, 0); 1336 QVERIFY(newRootIndex.isValid()); 1337 QCOMPARE(newRootIndex.data().toString(), QFileInfo(QDir::tempPath()).fileName()); 1338 QVERIFY(!dirModel.parent(newRootIndex).isValid()); 1339 QVERIFY(!dirModel.indexForUrl(slashUrl).isValid()); 1340 QCOMPARE(dirModel.itemForIndex(newRootIndex).url(), tempUrl); 1341 } 1342 1343 void KDirModelTest::testShowRootWithTrailingSlash() 1344 { 1345 // GIVEN 1346 KDirModel dirModel; 1347 const QUrl homeUrl = QUrl::fromLocalFile(QDir::homePath() + QLatin1Char('/')); 1348 1349 // WHEN 1350 dirModel.openUrl(homeUrl, KDirModel::ShowRoot); 1351 QTRY_VERIFY(dirModel.indexForUrl(homeUrl).isValid()); 1352 } 1353 1354 void KDirModelTest::testShowRootAndExpandToUrl() 1355 { 1356 // call expandToUrl without waiting for initial listing of root node 1357 KDirModel dirModel; 1358 dirModel.openUrl(QUrl(QStringLiteral("file:///")), KDirModel::ShowRoot); 1359 const QUrl homeUrl = QUrl::fromLocalFile(QDir::homePath()); 1360 dirModel.expandToUrl(homeUrl); 1361 QTRY_VERIFY(dirModel.indexForUrl(homeUrl).isValid()); 1362 } 1363 1364 void KDirModelTest::testHasChildren_data() 1365 { 1366 QTest::addColumn<bool>("dirsOnly"); 1367 QTest::addColumn<bool>("withHidden"); 1368 1369 QTest::newRow("with_files_and_no_hidden") << false << false; 1370 QTest::newRow("dirs_only_and_no_hidden") << true << false; 1371 QTest::newRow("with_files_and_hidden") << false << true; 1372 QTest::newRow("dirs_only_with_hidden") << true << true; 1373 } 1374 1375 // Test hasChildren without first populating the dirs 1376 void KDirModelTest::testHasChildren() 1377 { 1378 QFETCH(bool, dirsOnly); 1379 QFETCH(bool, withHidden); 1380 1381 m_dirModel->dirLister()->setDirOnlyMode(dirsOnly); 1382 m_dirModel->dirLister()->setShowHiddenFiles(withHidden); 1383 fillModel(true, false); 1384 1385 QVERIFY(m_dirModel->hasChildren()); 1386 1387 auto findDir = [this](const QModelIndex &parentIndex, const QString &name) { 1388 for (int row = 0; row < m_dirModel->rowCount(parentIndex); ++row) { 1389 QModelIndex idx = m_dirModel->index(row, 0, parentIndex); 1390 if (m_dirModel->itemForIndex(idx).isDir() && m_dirModel->itemForIndex(idx).name() == name) { 1391 return idx; 1392 } 1393 } 1394 return QModelIndex(); 1395 }; 1396 1397 m_dirIndex = findDir(QModelIndex(), "subdir"); 1398 QVERIFY(m_dirIndex.isValid()); 1399 QVERIFY(m_dirModel->hasChildren(m_dirIndex)); 1400 1401 auto listDir = [this](const QModelIndex &index) { 1402 QSignalSpy completedSpy(m_dirModel->dirLister(), qOverload<>(&KDirLister::completed)); 1403 m_dirModel->fetchMore(index); 1404 return completedSpy.wait(); 1405 }; 1406 // Now list subdir/ 1407 QVERIFY(listDir(m_dirIndex)); 1408 1409 const QModelIndex subsubdirIndex = findDir(m_dirIndex, "subsubdir"); 1410 QVERIFY(subsubdirIndex.isValid()); 1411 QCOMPARE(m_dirModel->hasChildren(subsubdirIndex), !dirsOnly); 1412 1413 const QModelIndex hasChildrenDirIndex = findDir(m_dirIndex, "hasChildren"); 1414 QVERIFY(hasChildrenDirIndex.isValid()); 1415 QVERIFY(m_dirModel->hasChildren(hasChildrenDirIndex)); 1416 1417 // Now list hasChildren/ 1418 QVERIFY(listDir(hasChildrenDirIndex)); 1419 1420 QModelIndex testDirIndex = findDir(hasChildrenDirIndex, "emptyDir"); 1421 QVERIFY(testDirIndex.isValid()); 1422 QVERIFY(!m_dirModel->hasChildren(testDirIndex)); 1423 1424 testDirIndex = findDir(hasChildrenDirIndex, "hiddenfileDir"); 1425 QVERIFY(testDirIndex.isValid()); 1426 QCOMPARE(m_dirModel->hasChildren(testDirIndex), !dirsOnly && withHidden); 1427 1428 testDirIndex = findDir(hasChildrenDirIndex, "hiddenDirDir"); 1429 QVERIFY(testDirIndex.isValid()); 1430 QCOMPARE(m_dirModel->hasChildren(testDirIndex), withHidden); 1431 1432 testDirIndex = findDir(hasChildrenDirIndex, "pipeDir"); 1433 QVERIFY(testDirIndex.isValid()); 1434 QCOMPARE(m_dirModel->hasChildren(testDirIndex), !dirsOnly); 1435 1436 testDirIndex = findDir(hasChildrenDirIndex, "symlinkDir"); 1437 QVERIFY(testDirIndex.isValid()); 1438 QCOMPARE(m_dirModel->hasChildren(testDirIndex), !dirsOnly); 1439 1440 m_dirModel->dirLister()->setDirOnlyMode(false); 1441 m_dirModel->dirLister()->setShowHiddenFiles(false); 1442 } 1443 1444 void KDirModelTest::testInvalidUrl() 1445 { 1446 QSignalSpy completedSpy(m_dirModel->dirLister(), qOverload<>(&KCoreDirLister::completed)); 1447 m_dirModel->openUrl(QUrl(":/")); 1448 // currently ends up in KCoreDirLister::handleError. TODO: add error signal to KDirModel 1449 } 1450 1451 void KDirModelTest::testDeleteFile() 1452 { 1453 fillModel(true); 1454 1455 QVERIFY(m_fileIndex.isValid()); 1456 const int oldTopLevelRowCount = m_dirModel->rowCount(); 1457 const QString path = m_tempDir->path() + '/'; 1458 const QString file = path + "toplevelfile_1"; 1459 const QUrl url = QUrl::fromLocalFile(file); 1460 1461 QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); 1462 1463 KIO::DeleteJob *job = KIO::del(url, KIO::HideProgressInfo); 1464 QVERIFY(job->exec()); 1465 1466 // Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved 1467 if (spyRowsRemoved.isEmpty()) { 1468 QVERIFY(spyRowsRemoved.wait()); 1469 } 1470 1471 // If we come here, then rowsRemoved() was emitted - all good. 1472 const int topLevelRowCount = m_dirModel->rowCount(); 1473 QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 1); // one less than before 1474 QCOMPARE(spyRowsRemoved.count(), 1); 1475 QCOMPARE(spyRowsRemoved[0][1].toInt(), m_fileIndex.row()); 1476 QCOMPARE(spyRowsRemoved[0][2].toInt(), m_fileIndex.row()); 1477 1478 QModelIndex fileIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "toplevelfile_1")); 1479 QVERIFY(!fileIndex.isValid()); 1480 1481 // Recreate the file, for consistency in the next tests 1482 // So the second part of this test is a "testCreateFile" 1483 createTestFile(file); 1484 // Tricky problem - KDirLister::openUrl will emit items from cache 1485 // and then schedule an update; so just calling fillModel would 1486 // not wait enough, it would abort due to not finding toplevelfile_1 1487 // in the items from cache. This progressive-emitting behavior is fine 1488 // for GUIs but not for unit tests ;-) 1489 fillModel(true, false); 1490 fillModel(false); 1491 } 1492 1493 void KDirModelTest::testDeleteFileWhileListing() // doesn't really test that yet, the kdirwatch deleted signal comes too late 1494 { 1495 const int oldTopLevelRowCount = m_dirModel->rowCount(); 1496 const QString path = m_tempDir->path() + '/'; 1497 const QString file = path + "toplevelfile_1"; 1498 const QUrl url = QUrl::fromLocalFile(file); 1499 1500 KDirLister *dirLister = m_dirModel->dirLister(); 1501 QSignalSpy spyCompleted(dirLister, qOverload<>(&KCoreDirLister::completed)); 1502 dirLister->openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); 1503 if (!spyCompleted.isEmpty()) { 1504 QSKIP("listing completed too early"); 1505 } 1506 QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); 1507 KIO::DeleteJob *job = KIO::del(url, KIO::HideProgressInfo); 1508 QVERIFY(job->exec()); 1509 1510 if (spyCompleted.isEmpty()) { 1511 QVERIFY(spyCompleted.wait()); 1512 } 1513 QVERIFY(spyRowsRemoved.wait(1000)); 1514 1515 const int topLevelRowCount = m_dirModel->rowCount(); 1516 QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 1); // one less than before 1517 QCOMPARE(spyRowsRemoved.count(), 1); 1518 QCOMPARE(spyRowsRemoved[0][1].toInt(), m_fileIndex.row()); 1519 QCOMPARE(spyRowsRemoved[0][2].toInt(), m_fileIndex.row()); 1520 1521 QModelIndex fileIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "toplevelfile_1")); 1522 QVERIFY(!fileIndex.isValid()); 1523 1524 qDebug() << "Test done, recreating file"; 1525 1526 // Recreate the file, for consistency in the next tests 1527 // So the second part of this test is a "testCreateFile" 1528 createTestFile(file); 1529 fillModel(true, false); // see testDeleteFile 1530 fillModel(false); 1531 } 1532 1533 void KDirModelTest::testOverwriteFileWithDir() // #151851 c4 1534 { 1535 fillModel(false); 1536 const QString path = m_tempDir->path() + '/'; 1537 const QString dir = path + "subdir"; 1538 const QString file = path + "toplevelfile_1"; 1539 const int oldTopLevelRowCount = m_dirModel->rowCount(); 1540 1541 bool removalWithinTopLevel = false; 1542 bool dataChangedAtFirstLevel = false; 1543 auto rrc = connect(m_dirModel, &KDirModel::rowsRemoved, this, [&removalWithinTopLevel](const QModelIndex &index) { 1544 if (!index.isValid()) { 1545 // yes, that's what we have been waiting for 1546 removalWithinTopLevel = true; 1547 } 1548 }); 1549 auto dcc = connect(m_dirModel, &KDirModel::dataChanged, this, [&dataChangedAtFirstLevel](const QModelIndex &index) { 1550 if (index.isValid() && !index.parent().isValid()) { 1551 // a change of a node whose parent is root, yay, that's it 1552 dataChangedAtFirstLevel = true; 1553 } 1554 }); 1555 1556 KIO::Job *job = KIO::move(QUrl::fromLocalFile(dir), QUrl::fromLocalFile(file), KIO::HideProgressInfo); 1557 delete KIO::delegateExtension<KIO::AskUserActionInterface *>(job); 1558 auto *askUserHandler = new MockAskUserInterface(job->uiDelegate()); 1559 askUserHandler->m_renameResult = KIO::Result_Overwrite; 1560 QVERIFY(job->exec()); 1561 1562 QCOMPARE(askUserHandler->m_askUserRenameCalled, 1); 1563 1564 // Wait for a removal within the top level (that's for the old file going away), and also 1565 // for a dataChanged which notifies us that a file has become a directory 1566 1567 QTRY_VERIFY(removalWithinTopLevel); 1568 QTRY_VERIFY(dataChangedAtFirstLevel); 1569 1570 m_dirModel->disconnect(rrc); 1571 m_dirModel->disconnect(dcc); 1572 1573 // If we come here, then rowsRemoved() was emitted - all good. 1574 const int topLevelRowCount = m_dirModel->rowCount(); 1575 QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 1); // one less than before 1576 1577 QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(dir)).isValid()); 1578 QModelIndex newIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "toplevelfile_1")); 1579 QVERIFY(newIndex.isValid()); 1580 KFileItem newItem = m_dirModel->itemForIndex(newIndex); 1581 QVERIFY(newItem.isDir()); // yes, the file is a dir now ;-) 1582 1583 qDebug() << "========= Test done, recreating test data ========="; 1584 1585 recreateTestData(); 1586 fillModel(false); 1587 } 1588 1589 void KDirModelTest::testDeleteFiles() 1590 { 1591 const int oldTopLevelRowCount = m_dirModel->rowCount(); 1592 const QString file = m_tempDir->path() + "/toplevelfile_"; 1593 QList<QUrl> urls; 1594 urls << QUrl::fromLocalFile(file + '1') << QUrl::fromLocalFile(file + '2') << QUrl::fromLocalFile(file + '3'); 1595 1596 QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); 1597 1598 KIO::DeleteJob *job = KIO::del(urls, KIO::HideProgressInfo); 1599 QVERIFY(job->exec()); 1600 1601 int numRowsRemoved = 0; 1602 while (numRowsRemoved < 3) { 1603 QTest::qWait(20); 1604 1605 numRowsRemoved = 0; 1606 for (int sigNum = 0; sigNum < spyRowsRemoved.count(); ++sigNum) { 1607 numRowsRemoved += spyRowsRemoved[sigNum][2].toInt() - spyRowsRemoved[sigNum][1].toInt() + 1; 1608 } 1609 qDebug() << "numRowsRemoved=" << numRowsRemoved; 1610 } 1611 1612 const int topLevelRowCount = m_dirModel->rowCount(); 1613 QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 3); // three less than before 1614 1615 qDebug() << "Recreating test data"; 1616 recreateTestData(); 1617 qDebug() << "Re-filling model"; 1618 fillModel(false); 1619 } 1620 1621 // A renaming that looks more like a deletion to the model 1622 void KDirModelTest::testRenameFileToHidden() // #174721 1623 { 1624 const QUrl url = QUrl::fromLocalFile(m_tempDir->path() + "/toplevelfile_2"); 1625 const QUrl newUrl = QUrl::fromLocalFile(m_tempDir->path() + "/.toplevelfile_2"); 1626 1627 QSignalSpy spyDataChanged(m_dirModel, &QAbstractItemModel::dataChanged); 1628 QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); 1629 QSignalSpy spyRowsInserted(m_dirModel, &QAbstractItemModel::rowsInserted); 1630 1631 KIO::SimpleJob *job = KIO::rename(url, newUrl, KIO::HideProgressInfo); 1632 QVERIFY(job->exec()); 1633 1634 // Wait for the DBUS signal from KDirNotify, it's the one the triggers KDirLister 1635 if (spyRowsRemoved.isEmpty()) { 1636 QVERIFY(spyRowsRemoved.wait()); 1637 } 1638 1639 // If we come here, then rowsRemoved() was emitted - all good. 1640 QCOMPARE(spyDataChanged.count(), 0); 1641 QCOMPARE(spyRowsRemoved.count(), 1); 1642 QCOMPARE(spyRowsInserted.count(), 0); 1643 COMPARE_INDEXES(spyRowsRemoved[0][0].value<QModelIndex>(), QModelIndex()); // parent is invalid 1644 const int row = spyRowsRemoved[0][1].toInt(); 1645 QCOMPARE(row, m_secondFileIndex.row()); // only compare row 1646 1647 spyRowsRemoved.clear(); 1648 1649 // Put things back to normal, should make the file reappear 1650 job = KIO::rename(newUrl, url, KIO::HideProgressInfo); 1651 QVERIFY(job->exec()); 1652 // Wait for the DBUS signal from KDirNotify, it's the one the triggers KDirLister 1653 if (spyRowsInserted.isEmpty()) { 1654 QVERIFY(spyRowsInserted.wait()); 1655 } 1656 QCOMPARE(spyDataChanged.count(), 0); 1657 QCOMPARE(spyRowsRemoved.count(), 0); 1658 QCOMPARE(spyRowsInserted.count(), 1); 1659 const int newRow = spyRowsInserted[0][1].toInt(); 1660 m_secondFileIndex = m_dirModel->index(newRow, 0); 1661 QVERIFY(m_secondFileIndex.isValid()); 1662 QCOMPARE(m_dirModel->itemForIndex(m_secondFileIndex).url().toString(), url.toString()); 1663 } 1664 1665 void KDirModelTest::testDeleteDirectory() 1666 { 1667 const QString path = m_tempDir->path() + '/'; 1668 const QUrl url = QUrl::fromLocalFile(path + "subdir/subsubdir"); 1669 1670 QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); 1671 1672 QSignalSpy spyDirWatchDeleted(KDirWatch::self(), &KDirWatch::deleted); 1673 1674 KIO::DeleteJob *job = KIO::del(url, KIO::HideProgressInfo); 1675 QVERIFY(job->exec()); 1676 1677 // Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved 1678 if (spyRowsRemoved.isEmpty()) { 1679 QVERIFY(spyRowsRemoved.wait()); 1680 } 1681 1682 // If we come here, then rowsRemoved() was emitted - all good. 1683 QCOMPARE(spyRowsRemoved.count(), 1); 1684 1685 QModelIndex deletedDirIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir/subsubdir")); 1686 QVERIFY(!deletedDirIndex.isValid()); 1687 QModelIndex dirIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir")); 1688 QVERIFY(dirIndex.isValid()); 1689 1690 // Not emitted because DeleteJobPrivate::currentSourceStated calls stopDirScan 1691 // We don't need it anyway, we have the DBus signal 1692 // QCOMPARE(spyDirWatchDeleted.count(), 1); 1693 } 1694 1695 void KDirModelTest::testDeleteCurrentDirectory() 1696 { 1697 const int oldTopLevelRowCount = m_dirModel->rowCount(); 1698 const QString path = m_tempDir->path() + '/'; 1699 const QUrl url = QUrl::fromLocalFile(path); 1700 1701 QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); 1702 1703 KDirWatch::self()->statistics(); 1704 1705 KIO::DeleteJob *job = KIO::del(url, KIO::HideProgressInfo); 1706 QVERIFY(job->exec()); 1707 1708 // Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved 1709 if (spyRowsRemoved.isEmpty()) { 1710 QVERIFY(spyRowsRemoved.wait()); 1711 } 1712 1713 // If we come here, then rowsRemoved() was emitted - all good. 1714 QTRY_COMPARE(m_dirModel->rowCount(), 0); // empty 1715 1716 // We can get rowsRemoved for subdirs first, since kdirwatch notices that. 1717 QVERIFY(spyRowsRemoved.count() >= 1); 1718 1719 // Look for the signal(s) that had QModelIndex() as parent. 1720 int i; 1721 int numDeleted = 0; 1722 for (i = 0; i < spyRowsRemoved.count(); ++i) { 1723 const int from = spyRowsRemoved[i][1].toInt(); 1724 const int to = spyRowsRemoved[i][2].toInt(); 1725 qDebug() << spyRowsRemoved[i][0].value<QModelIndex>() << from << to; 1726 if (!spyRowsRemoved[i][0].value<QModelIndex>().isValid()) { 1727 numDeleted += (to - from) + 1; 1728 } 1729 } 1730 1731 QCOMPARE(numDeleted, oldTopLevelRowCount); 1732 1733 QModelIndex fileIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "toplevelfile_1")); 1734 QVERIFY(!fileIndex.isValid()); 1735 } 1736 1737 void KDirModelTest::testQUrlHash() 1738 { 1739 const int count = 3000; 1740 // Prepare an array of QUrls so that url constructing isn't part of the timing 1741 QVector<QUrl> urls; 1742 urls.resize(count); 1743 for (int i = 0; i < count; ++i) { 1744 urls[i] = QUrl("http://www.kde.org/path/" + QString::number(i)); 1745 } 1746 QHash<QUrl, int> qurlHash; 1747 QHash<QUrl, int> kurlHash; 1748 QElapsedTimer dt; 1749 dt.start(); 1750 for (int i = 0; i < count; ++i) { 1751 qurlHash.insert(urls[i], i); 1752 } 1753 // qDebug() << "inserting" << count << "urls into QHash using old qHash:" << dt.elapsed() << "msecs"; 1754 dt.start(); 1755 for (int i = 0; i < count; ++i) { 1756 kurlHash.insert(urls[i], i); 1757 } 1758 // qDebug() << "inserting" << count << "urls into QHash using new qHash:" << dt.elapsed() << "msecs"; 1759 // Nice results: for count=30000 I got 4515 (before) and 103 (after) 1760 1761 dt.start(); 1762 for (int i = 0; i < count; ++i) { 1763 QCOMPARE(qurlHash.value(urls[i]), i); 1764 } 1765 // qDebug() << "looking up" << count << "urls into QHash using old qHash:" << dt.elapsed() << "msecs"; 1766 dt.start(); 1767 for (int i = 0; i < count; ++i) { 1768 QCOMPARE(kurlHash.value(urls[i]), i); 1769 } 1770 // qDebug() << "looking up" << count << "urls into QHash using new qHash:" << dt.elapsed() << "msecs"; 1771 // Nice results: for count=30000 I got 4296 (before) and 63 (after) 1772 } 1773 1774 #include "moc_kdirmodeltest.cpp"