File indexing completed on 2023-09-24 11:40:48
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 QSignalSpy spyItemsFilteredByMime(m_dirModel->dirLister(), &KCoreDirLister::itemsFilteredByMime); 0967 QSignalSpy spyItemsDeleted(m_dirModel->dirLister(), &KCoreDirLister::itemsDeleted); 0968 QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); 0969 m_dirModel->dirLister()->setNameFilter(QStringLiteral("toplevel*")); 0970 QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); // no change yet 0971 QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); // no change yet 0972 m_dirModel->dirLister()->emitChanges(); 0973 0974 QCOMPARE(m_dirModel->rowCount(), 4); // 3 toplevel* files, one subdir 0975 QCOMPARE(m_dirModel->rowCount(m_dirIndex), 2); // the files get filtered out, subsubdir and hasChildren are remaining 0976 0977 // In the subdir, we can get rowsRemoved signals like (1,2) or (0,0)+(2,2), 0978 // depending on the order of the files in the model. 0979 // So QCOMPARE(spyRowsRemoved.count(), 3) is fragile, we rather need 0980 // to sum up the removed rows per parent directory. 0981 QMap<QString, int> rowsRemovedPerDir; 0982 for (int i = 0; i < spyRowsRemoved.count(); ++i) { 0983 const QVariantList args = spyRowsRemoved[i]; 0984 const QModelIndex parentIdx = args[0].value<QModelIndex>(); 0985 QString dirName; 0986 if (parentIdx.isValid()) { 0987 const KFileItem item = m_dirModel->itemForIndex(parentIdx); 0988 dirName = item.name(); 0989 } else { 0990 dirName = QStringLiteral("root"); 0991 } 0992 rowsRemovedPerDir[dirName] += args[2].toInt() - args[1].toInt() + 1; 0993 // qDebug() << parentIdx << args[1].toInt() << args[2].toInt(); 0994 } 0995 QCOMPARE(rowsRemovedPerDir.count(), 3); // once for every dir 0996 QCOMPARE(rowsRemovedPerDir.value("root"), 1); // one from toplevel ('special chars') 0997 QCOMPARE(rowsRemovedPerDir.value("subdir"), 2); // two from subdir 0998 QCOMPARE(rowsRemovedPerDir.value("subsubdir"), 1); // one from subsubdir 0999 QCOMPARE(spyItemsDeleted.count(), 3); // once for every dir 1000 QCOMPARE(spyItemsDeleted[0][0].value<KFileItemList>().count(), 1); // one from toplevel ('special chars') 1001 QCOMPARE(spyItemsDeleted[1][0].value<KFileItemList>().count(), 2); // two from subdir 1002 QCOMPARE(spyItemsDeleted[2][0].value<KFileItemList>().count(), 1); // one from subsubdir 1003 QCOMPARE(spyItemsFilteredByMime.count(), 0); 1004 spyItemsDeleted.clear(); 1005 spyItemsFilteredByMime.clear(); 1006 1007 // Reset the filter 1008 qDebug() << "reset to no filter"; 1009 m_dirModel->dirLister()->setNameFilter(QString()); 1010 m_dirModel->dirLister()->emitChanges(); 1011 1012 QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); 1013 QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); 1014 QCOMPARE(spyItemsDeleted.count(), 0); 1015 QCOMPARE(spyItemsFilteredByMime.count(), 0); 1016 1017 // The order of things changed because of filtering. 1018 // Fill again, so that m_fileIndex etc. are correct again. 1019 fillModel(true); 1020 } 1021 1022 void KDirModelTest::testFilterPatterns() 1023 { 1024 QVERIFY(m_dirIndex.isValid()); 1025 1026 const int oldTopLevelRowCount = m_dirModel->rowCount(); 1027 const int oldSubdirRowCount = m_dirModel->rowCount(m_dirIndex); 1028 1029 // matching without wildcard -> no files 1030 m_dirModel->dirLister()->setNameFilter(QStringLiteral("toplevel")); 1031 QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); // no change yet 1032 QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); // no change yet 1033 m_dirModel->dirLister()->emitChanges(); 1034 1035 QCOMPARE(m_dirModel->rowCount(), 1); // the files get filtered out, subdir 1036 QCOMPARE(m_dirModel->rowCount(m_dirIndex), 2); // the files get filtered out, subsubdir and hasChildren 1037 1038 // Reset the filter 1039 m_dirModel->dirLister()->setNameFilter(QString()); 1040 m_dirModel->dirLister()->emitChanges(); 1041 1042 // Back to original state 1043 QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); 1044 QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); 1045 1046 m_dirModel->dirLister()->setNameFilter(QStringLiteral("toplevel*")); // Matching with a wildcard 1047 QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); // no change yet 1048 QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); // no change yet 1049 m_dirModel->dirLister()->emitChanges(); 1050 1051 QCOMPARE(m_dirModel->rowCount(), 4); // 3 files, one subdir with "toplevel*" in the name 1052 QCOMPARE(m_dirModel->rowCount(m_dirIndex), 2); // the files get filtered out, subsubdir and hasChildren are remaining 1053 1054 m_dirModel->dirLister()->setNameFilter(QString()); 1055 m_dirModel->dirLister()->emitChanges(); 1056 1057 QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); 1058 QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); 1059 1060 // The order of things changed because of filtering. 1061 // Fill again, so that m_fileIndex etc. are correct again. 1062 fillModel(true); 1063 } 1064 1065 void KDirModelTest::testMimeFilter() 1066 { 1067 QVERIFY(m_dirIndex.isValid()); 1068 const int oldTopLevelRowCount = m_dirModel->rowCount(); 1069 const int oldSubdirRowCount = m_dirModel->rowCount(m_dirIndex); 1070 QSignalSpy spyItemsFilteredByMime(m_dirModel->dirLister(), &KCoreDirLister::itemsFilteredByMime); 1071 QSignalSpy spyItemsDeleted(m_dirModel->dirLister(), &KCoreDirLister::itemsDeleted); 1072 QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); 1073 m_dirModel->dirLister()->setMimeFilter({QStringLiteral("application/pdf")}); 1074 QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); // no change yet 1075 QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); // no change yet 1076 m_dirModel->dirLister()->emitChanges(); 1077 1078 QCOMPARE(m_dirModel->rowCount(), 1); // 1 pdf files, no subdir anymore 1079 1080 QVERIFY(spyRowsRemoved.count() >= 1); // depends on contiguity... 1081 QVERIFY(spyItemsDeleted.count() >= 1); // once for every dir 1082 QCOMPARE(spyItemsFilteredByMime.count(), 3); 1083 spyItemsDeleted.clear(); 1084 spyItemsFilteredByMime.clear(); 1085 1086 // Reset the filter 1087 m_dirModel->dirLister()->setMimeFilter({}); 1088 m_dirModel->dirLister()->emitChanges(); 1089 1090 QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); 1091 QCOMPARE(spyItemsDeleted.count(), 0); 1092 QCOMPARE(spyItemsFilteredByMime.count(), 0); 1093 1094 // The order of things changed because of filtering. 1095 // Fill again, so that m_fileIndex etc. are correct again. 1096 fillModel(true); 1097 } 1098 1099 void KDirModelTest::testMimeExcludeFilter() 1100 { 1101 QVERIFY(m_dirIndex.isValid()); 1102 const int oldTopLevelRowCount = m_dirModel->rowCount(); 1103 const int oldSubdirRowCount = m_dirModel->rowCount(m_dirIndex); 1104 QSignalSpy spyItemsFilteredByMime(m_dirModel->dirLister(), &KCoreDirLister::itemsFilteredByMime); 1105 QSignalSpy spyItemsDeleted(m_dirModel->dirLister(), &KCoreDirLister::itemsDeleted); 1106 QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); 1107 m_dirModel->dirLister()->setMimeExcludeFilter({QStringLiteral("application/pdf")}); 1108 QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); // no change yet 1109 QCOMPARE(m_dirModel->rowCount(m_dirIndex), oldSubdirRowCount); // no change yet 1110 m_dirModel->dirLister()->emitChanges(); 1111 1112 QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount - 1); // no pdf files anymore 1113 1114 QCOMPARE(spyRowsRemoved.count(), 1); // one pdf file removed ... 1115 QCOMPARE(spyItemsDeleted.count(), 1); 1116 QCOMPARE(spyItemsFilteredByMime.count(), 1); 1117 QCOMPARE(spyItemsFilteredByMime[0][0].value<KFileItemList>().count(), 1); 1118 spyItemsFilteredByMime.clear(); 1119 spyItemsDeleted.clear(); 1120 1121 // Reset the exclude filter 1122 m_dirModel->dirLister()->setMimeExcludeFilter({}); 1123 m_dirModel->dirLister()->emitChanges(); 1124 1125 QCOMPARE(m_dirModel->rowCount(), oldTopLevelRowCount); 1126 QCOMPARE(spyItemsDeleted.count(), 0); 1127 QCOMPARE(spyItemsFilteredByMime.count(), 0); 1128 1129 // The order of things changed because of filtering. 1130 // Fill again, so that m_fileIndex etc. are correct again. 1131 fillModel(true); 1132 } 1133 1134 void KDirModelTest::testShowHiddenFiles() // #174788 1135 { 1136 KDirLister *dirLister = m_dirModel->dirLister(); 1137 1138 QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); 1139 QSignalSpy spyNewItems(dirLister, &KCoreDirLister::newItems); 1140 QSignalSpy spyRowsInserted(m_dirModel, &QAbstractItemModel::rowsInserted); 1141 dirLister->setShowHiddenFiles(true); 1142 dirLister->emitChanges(); 1143 const int numberOfDotFiles = 2; 1144 QCOMPARE(spyNewItems.count(), 1); 1145 QCOMPARE(spyNewItems[0][0].value<KFileItemList>().count(), numberOfDotFiles); 1146 QCOMPARE(spyRowsInserted.count(), 1); 1147 QCOMPARE(spyRowsRemoved.count(), 0); 1148 spyNewItems.clear(); 1149 spyRowsInserted.clear(); 1150 1151 dirLister->setShowHiddenFiles(false); 1152 dirLister->emitChanges(); 1153 QCOMPARE(spyNewItems.count(), 0); 1154 QCOMPARE(spyRowsInserted.count(), 0); 1155 QCOMPARE(spyRowsRemoved.count(), 1); 1156 } 1157 1158 void KDirModelTest::testMultipleSlashes() 1159 { 1160 const QString path = m_tempDir->path() + '/'; 1161 1162 QModelIndex index = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir//testfile")); 1163 QVERIFY(index.isValid()); 1164 1165 index = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir//subsubdir//")); 1166 QVERIFY(index.isValid()); 1167 1168 index = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir///subsubdir////testfile")); 1169 QVERIFY(index.isValid()); 1170 } 1171 1172 void KDirModelTest::testUrlWithRef() // #171117 1173 { 1174 const QString path = m_tempDir->path() + '/'; 1175 KDirLister *dirLister = m_dirModel->dirLister(); 1176 QUrl url = QUrl::fromLocalFile(path); 1177 url.setFragment(QStringLiteral("ref")); 1178 QVERIFY(url.url().endsWith(QLatin1String("#ref"))); 1179 QSignalSpy spyCompleted(dirLister, qOverload<>(&KCoreDirLister::completed)); 1180 dirLister->openUrl(url, KDirLister::NoFlags); 1181 QVERIFY(spyCompleted.wait()); 1182 1183 QCOMPARE(dirLister->url().toString(), url.toString(QUrl::StripTrailingSlash)); 1184 collectKnownIndexes(); 1185 } 1186 1187 // void KDirModelTest::testFontUrlWithHost() // #160057 --> moved to kio_fonts (kfontinst/kio/autotests) 1188 1189 void KDirModelTest::testRemoteUrlWithHost() // #178416 1190 { 1191 if (!KProtocolInfo::isKnownProtocol(QStringLiteral("remote"))) { 1192 QSKIP("kio_remote not installed"); 1193 } 1194 QUrl url(QStringLiteral("remote://foo")); 1195 KDirLister *dirLister = m_dirModel->dirLister(); 1196 QSignalSpy spyCompleted(dirLister, qOverload<>(&KCoreDirLister::completed)); 1197 dirLister->openUrl(url, KDirLister::NoFlags); 1198 QVERIFY(spyCompleted.wait()); 1199 1200 QCOMPARE(dirLister->url().toString(), QString("remote://foo")); 1201 } 1202 1203 void KDirModelTest::testZipFile() // # 171721 1204 { 1205 const QString path = QFileInfo(QFINDTESTDATA("wronglocalsizes.zip")).absolutePath(); 1206 KDirLister *dirLister = m_dirModel->dirLister(); 1207 QSignalSpy spyCompleted(dirLister, qOverload<>(&KCoreDirLister::completed)); 1208 dirLister->openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); 1209 QVERIFY(spyCompleted.wait()); 1210 1211 QUrl zipUrl(QUrl::fromLocalFile(path)); 1212 zipUrl.setPath(zipUrl.path() + "/wronglocalsizes.zip"); // just a zip file lying here for other reasons 1213 1214 QVERIFY(QFile::exists(zipUrl.toLocalFile())); 1215 zipUrl.setScheme(QStringLiteral("zip")); 1216 QModelIndex index = m_dirModel->indexForUrl(zipUrl); 1217 QVERIFY(!index.isValid()); // protocol mismatch, can't find it! 1218 zipUrl.setScheme(QStringLiteral("file")); 1219 index = m_dirModel->indexForUrl(zipUrl); 1220 QVERIFY(index.isValid()); 1221 } 1222 1223 class MyDirLister : public KDirLister 1224 { 1225 public: 1226 void emitItemsDeleted(const KFileItemList &items) 1227 { 1228 Q_EMIT itemsDeleted(items); 1229 } 1230 }; 1231 1232 void KDirModelTest::testBug196695() 1233 { 1234 KFileItem rootItem(QUrl::fromLocalFile(m_tempDir->path()), QString(), KFileItem::Unknown); 1235 KFileItem childItem(QUrl::fromLocalFile(QString(m_tempDir->path() + "/toplevelfile_1")), QString(), KFileItem::Unknown); 1236 1237 KFileItemList list; 1238 // Important: the root item must not be first in the list to trigger bug 196695 1239 list << childItem << rootItem; 1240 1241 MyDirLister *dirLister = static_cast<MyDirLister *>(m_dirModel->dirLister()); 1242 dirLister->emitItemsDeleted(list); 1243 1244 fillModel(true); 1245 } 1246 1247 void KDirModelTest::testMimeData() 1248 { 1249 QModelIndex index0 = m_dirModel->index(0, 0); 1250 QVERIFY(index0.isValid()); 1251 QModelIndex index1 = m_dirModel->index(1, 0); 1252 QVERIFY(index1.isValid()); 1253 QList<QModelIndex> indexes; 1254 indexes << index0 << index1; 1255 QMimeData *mimeData = m_dirModel->mimeData(indexes); 1256 QVERIFY(mimeData); 1257 QVERIFY(mimeData->hasUrls()); 1258 const QList<QUrl> urls = mimeData->urls(); 1259 QCOMPARE(urls.count(), indexes.count()); 1260 delete mimeData; 1261 } 1262 1263 void KDirModelTest::testDotHiddenFile_data() 1264 { 1265 QTest::addColumn<QStringList>("fileContents"); 1266 QTest::addColumn<QStringList>("expectedListing"); 1267 1268 const QStringList allItems{QStringLiteral("toplevelfile_1"), 1269 QStringLiteral("toplevelfile_2"), 1270 QStringLiteral("toplevelfile_3"), 1271 specialChars(), 1272 QStringLiteral("subdir")}; 1273 QTest::newRow("empty_file") << (QStringList{}) << allItems; 1274 1275 QTest::newRow("simple_name") << (QStringList{QStringLiteral("toplevelfile_1")}) << QStringList(allItems.mid(1)); 1276 1277 QStringList allButSpecialChars = allItems; 1278 allButSpecialChars.removeAt(3); 1279 QTest::newRow("special_chars") << (QStringList{specialChars()}) << allButSpecialChars; 1280 1281 QStringList allButSubdir = allItems; 1282 allButSubdir.removeAt(4); 1283 QTest::newRow("subdir") << (QStringList{QStringLiteral("subdir")}) << allButSubdir; 1284 1285 QTest::newRow("many_lines") 1286 << (QStringList{QStringLiteral("subdir"), QStringLiteral("toplevelfile_1"), QStringLiteral("toplevelfile_3"), QStringLiteral("toplevelfile_2")}) 1287 << QStringList{specialChars()}; 1288 } 1289 1290 void KDirModelTest::testDotHiddenFile() 1291 { 1292 QFETCH(QStringList, fileContents); 1293 QFETCH(QStringList, expectedListing); 1294 1295 const QString path = m_tempDir->path() + '/'; 1296 const QString dotHiddenFile = path + ".hidden"; 1297 QTest::qWait(1000); // mtime-based cache, so we need to wait for 1 second 1298 QFile dh(dotHiddenFile); 1299 QVERIFY(dh.open(QIODevice::WriteOnly)); 1300 dh.write(fileContents.join('\n').toUtf8()); 1301 dh.close(); 1302 1303 // Do it twice: once to read from the file and once to use the cache 1304 for (int i = 0; i < 2; ++i) { 1305 fillModel(true, false); 1306 QStringList files; 1307 for (int row = 0; row < m_dirModel->rowCount(); ++row) { 1308 files.append(m_dirModel->index(row, KDirModel::Name).data().toString()); 1309 } 1310 files.sort(); 1311 expectedListing.sort(); 1312 QCOMPARE(files, expectedListing); 1313 } 1314 1315 dh.remove(); 1316 } 1317 1318 void KDirModelTest::testShowRoot() 1319 { 1320 KDirModel dirModel; 1321 const QUrl homeUrl = QUrl::fromLocalFile(QDir::homePath()); 1322 const QUrl fsRootUrl = QUrl(QStringLiteral("file:///")); 1323 1324 // openUrl("/", ShowRoot) should create a "/" item 1325 dirModel.openUrl(fsRootUrl, KDirModel::ShowRoot); 1326 QTRY_COMPARE(dirModel.rowCount(), 1); 1327 const QModelIndex rootIndex = dirModel.index(0, 0); 1328 QVERIFY(rootIndex.isValid()); 1329 QCOMPARE(rootIndex.data().toString(), QStringLiteral("/")); 1330 QVERIFY(!dirModel.parent(rootIndex).isValid()); 1331 QCOMPARE(dirModel.itemForIndex(rootIndex).url(), QUrl(QStringLiteral("file:///"))); 1332 QCOMPARE(dirModel.itemForIndex(rootIndex).name(), QStringLiteral("/")); 1333 1334 // expandToUrl should work 1335 dirModel.expandToUrl(homeUrl); 1336 QTRY_VERIFY(dirModel.indexForUrl(homeUrl).isValid()); 1337 1338 // test itemForIndex and indexForUrl 1339 QCOMPARE(dirModel.itemForIndex(QModelIndex()).url(), QUrl()); 1340 QVERIFY(!dirModel.indexForUrl(QUrl()).isValid()); 1341 const QUrl slashUrl = QUrl::fromLocalFile(QStringLiteral("/")); 1342 QCOMPARE(dirModel.indexForUrl(slashUrl), rootIndex); 1343 1344 // switching to another URL should also show a root node 1345 QSignalSpy spyRowsRemoved(&dirModel, &QAbstractItemModel::rowsRemoved); 1346 const QUrl tempUrl = QUrl::fromLocalFile(QDir::tempPath()); 1347 dirModel.openUrl(tempUrl, KDirModel::ShowRoot); 1348 QTRY_COMPARE(dirModel.rowCount(), 1); 1349 QCOMPARE(spyRowsRemoved.count(), 1); 1350 const QModelIndex newRootIndex = dirModel.index(0, 0); 1351 QVERIFY(newRootIndex.isValid()); 1352 QCOMPARE(newRootIndex.data().toString(), QFileInfo(QDir::tempPath()).fileName()); 1353 QVERIFY(!dirModel.parent(newRootIndex).isValid()); 1354 QVERIFY(!dirModel.indexForUrl(slashUrl).isValid()); 1355 QCOMPARE(dirModel.itemForIndex(newRootIndex).url(), tempUrl); 1356 } 1357 1358 void KDirModelTest::testShowRootWithTrailingSlash() 1359 { 1360 // GIVEN 1361 KDirModel dirModel; 1362 const QUrl homeUrl = QUrl::fromLocalFile(QDir::homePath() + QLatin1Char('/')); 1363 1364 // WHEN 1365 dirModel.openUrl(homeUrl, KDirModel::ShowRoot); 1366 QTRY_VERIFY(dirModel.indexForUrl(homeUrl).isValid()); 1367 } 1368 1369 void KDirModelTest::testShowRootAndExpandToUrl() 1370 { 1371 // call expandToUrl without waiting for initial listing of root node 1372 KDirModel dirModel; 1373 dirModel.openUrl(QUrl(QStringLiteral("file:///")), KDirModel::ShowRoot); 1374 const QUrl homeUrl = QUrl::fromLocalFile(QDir::homePath()); 1375 dirModel.expandToUrl(homeUrl); 1376 QTRY_VERIFY(dirModel.indexForUrl(homeUrl).isValid()); 1377 } 1378 1379 void KDirModelTest::testHasChildren_data() 1380 { 1381 QTest::addColumn<bool>("dirsOnly"); 1382 QTest::addColumn<bool>("withHidden"); 1383 1384 QTest::newRow("with_files_and_no_hidden") << false << false; 1385 QTest::newRow("dirs_only_and_no_hidden") << true << false; 1386 QTest::newRow("with_files_and_hidden") << false << true; 1387 QTest::newRow("dirs_only_with_hidden") << true << true; 1388 } 1389 1390 // Test hasChildren without first populating the dirs 1391 void KDirModelTest::testHasChildren() 1392 { 1393 QFETCH(bool, dirsOnly); 1394 QFETCH(bool, withHidden); 1395 1396 m_dirModel->dirLister()->setDirOnlyMode(dirsOnly); 1397 m_dirModel->dirLister()->setShowHiddenFiles(withHidden); 1398 fillModel(true, false); 1399 1400 QVERIFY(m_dirModel->hasChildren()); 1401 1402 auto findDir = [this](const QModelIndex &parentIndex, const QString &name) { 1403 for (int row = 0; row < m_dirModel->rowCount(parentIndex); ++row) { 1404 QModelIndex idx = m_dirModel->index(row, 0, parentIndex); 1405 if (m_dirModel->itemForIndex(idx).isDir() && m_dirModel->itemForIndex(idx).name() == name) { 1406 return idx; 1407 } 1408 } 1409 return QModelIndex(); 1410 }; 1411 1412 m_dirIndex = findDir(QModelIndex(), "subdir"); 1413 QVERIFY(m_dirIndex.isValid()); 1414 QVERIFY(m_dirModel->hasChildren(m_dirIndex)); 1415 1416 auto listDir = [this](const QModelIndex &index) { 1417 QSignalSpy completedSpy(m_dirModel->dirLister(), qOverload<>(&KDirLister::completed)); 1418 m_dirModel->fetchMore(index); 1419 return completedSpy.wait(); 1420 }; 1421 // Now list subdir/ 1422 QVERIFY(listDir(m_dirIndex)); 1423 1424 const QModelIndex subsubdirIndex = findDir(m_dirIndex, "subsubdir"); 1425 QVERIFY(subsubdirIndex.isValid()); 1426 QCOMPARE(m_dirModel->hasChildren(subsubdirIndex), !dirsOnly); 1427 1428 const QModelIndex hasChildrenDirIndex = findDir(m_dirIndex, "hasChildren"); 1429 QVERIFY(hasChildrenDirIndex.isValid()); 1430 QVERIFY(m_dirModel->hasChildren(hasChildrenDirIndex)); 1431 1432 // Now list hasChildren/ 1433 QVERIFY(listDir(hasChildrenDirIndex)); 1434 1435 QModelIndex testDirIndex = findDir(hasChildrenDirIndex, "emptyDir"); 1436 QVERIFY(testDirIndex.isValid()); 1437 QVERIFY(!m_dirModel->hasChildren(testDirIndex)); 1438 1439 testDirIndex = findDir(hasChildrenDirIndex, "hiddenfileDir"); 1440 QVERIFY(testDirIndex.isValid()); 1441 QCOMPARE(m_dirModel->hasChildren(testDirIndex), !dirsOnly && withHidden); 1442 1443 testDirIndex = findDir(hasChildrenDirIndex, "hiddenDirDir"); 1444 QVERIFY(testDirIndex.isValid()); 1445 QCOMPARE(m_dirModel->hasChildren(testDirIndex), withHidden); 1446 1447 testDirIndex = findDir(hasChildrenDirIndex, "pipeDir"); 1448 QVERIFY(testDirIndex.isValid()); 1449 QCOMPARE(m_dirModel->hasChildren(testDirIndex), !dirsOnly); 1450 1451 testDirIndex = findDir(hasChildrenDirIndex, "symlinkDir"); 1452 QVERIFY(testDirIndex.isValid()); 1453 QCOMPARE(m_dirModel->hasChildren(testDirIndex), !dirsOnly); 1454 1455 m_dirModel->dirLister()->setDirOnlyMode(false); 1456 m_dirModel->dirLister()->setShowHiddenFiles(false); 1457 } 1458 1459 void KDirModelTest::testInvalidUrl() 1460 { 1461 QSignalSpy completedSpy(m_dirModel->dirLister(), qOverload<>(&KCoreDirLister::completed)); 1462 m_dirModel->openUrl(QUrl(":/")); 1463 // currently ends up in KCoreDirLister::handleError. TODO: add error signal to KDirModel 1464 } 1465 1466 void KDirModelTest::testDeleteFile() 1467 { 1468 fillModel(true); 1469 1470 QVERIFY(m_fileIndex.isValid()); 1471 const int oldTopLevelRowCount = m_dirModel->rowCount(); 1472 const QString path = m_tempDir->path() + '/'; 1473 const QString file = path + "toplevelfile_1"; 1474 const QUrl url = QUrl::fromLocalFile(file); 1475 1476 QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); 1477 1478 KIO::DeleteJob *job = KIO::del(url, KIO::HideProgressInfo); 1479 QVERIFY(job->exec()); 1480 1481 // Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved 1482 if (spyRowsRemoved.isEmpty()) { 1483 QVERIFY(spyRowsRemoved.wait()); 1484 } 1485 1486 // If we come here, then rowsRemoved() was emitted - all good. 1487 const int topLevelRowCount = m_dirModel->rowCount(); 1488 QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 1); // one less than before 1489 QCOMPARE(spyRowsRemoved.count(), 1); 1490 QCOMPARE(spyRowsRemoved[0][1].toInt(), m_fileIndex.row()); 1491 QCOMPARE(spyRowsRemoved[0][2].toInt(), m_fileIndex.row()); 1492 1493 QModelIndex fileIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "toplevelfile_1")); 1494 QVERIFY(!fileIndex.isValid()); 1495 1496 // Recreate the file, for consistency in the next tests 1497 // So the second part of this test is a "testCreateFile" 1498 createTestFile(file); 1499 // Tricky problem - KDirLister::openUrl will emit items from cache 1500 // and then schedule an update; so just calling fillModel would 1501 // not wait enough, it would abort due to not finding toplevelfile_1 1502 // in the items from cache. This progressive-emitting behavior is fine 1503 // for GUIs but not for unit tests ;-) 1504 fillModel(true, false); 1505 fillModel(false); 1506 } 1507 1508 void KDirModelTest::testDeleteFileWhileListing() // doesn't really test that yet, the kdirwatch deleted signal comes too late 1509 { 1510 const int oldTopLevelRowCount = m_dirModel->rowCount(); 1511 const QString path = m_tempDir->path() + '/'; 1512 const QString file = path + "toplevelfile_1"; 1513 const QUrl url = QUrl::fromLocalFile(file); 1514 1515 KDirLister *dirLister = m_dirModel->dirLister(); 1516 QSignalSpy spyCompleted(dirLister, qOverload<>(&KCoreDirLister::completed)); 1517 dirLister->openUrl(QUrl::fromLocalFile(path), KDirLister::NoFlags); 1518 if (!spyCompleted.isEmpty()) { 1519 QSKIP("listing completed too early"); 1520 } 1521 QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); 1522 KIO::DeleteJob *job = KIO::del(url, KIO::HideProgressInfo); 1523 QVERIFY(job->exec()); 1524 1525 if (spyCompleted.isEmpty()) { 1526 QVERIFY(spyCompleted.wait()); 1527 } 1528 QVERIFY(spyRowsRemoved.wait(1000)); 1529 1530 const int topLevelRowCount = m_dirModel->rowCount(); 1531 QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 1); // one less than before 1532 QCOMPARE(spyRowsRemoved.count(), 1); 1533 QCOMPARE(spyRowsRemoved[0][1].toInt(), m_fileIndex.row()); 1534 QCOMPARE(spyRowsRemoved[0][2].toInt(), m_fileIndex.row()); 1535 1536 QModelIndex fileIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "toplevelfile_1")); 1537 QVERIFY(!fileIndex.isValid()); 1538 1539 qDebug() << "Test done, recreating file"; 1540 1541 // Recreate the file, for consistency in the next tests 1542 // So the second part of this test is a "testCreateFile" 1543 createTestFile(file); 1544 fillModel(true, false); // see testDeleteFile 1545 fillModel(false); 1546 } 1547 1548 void KDirModelTest::testOverwriteFileWithDir() // #151851 c4 1549 { 1550 fillModel(false); 1551 const QString path = m_tempDir->path() + '/'; 1552 const QString dir = path + "subdir"; 1553 const QString file = path + "toplevelfile_1"; 1554 const int oldTopLevelRowCount = m_dirModel->rowCount(); 1555 1556 bool removalWithinTopLevel = false; 1557 bool dataChangedAtFirstLevel = false; 1558 auto rrc = connect(m_dirModel, &KDirModel::rowsRemoved, this, [&removalWithinTopLevel](const QModelIndex &index) { 1559 if (!index.isValid()) { 1560 // yes, that's what we have been waiting for 1561 removalWithinTopLevel = true; 1562 } 1563 }); 1564 auto dcc = connect(m_dirModel, &KDirModel::dataChanged, this, [&dataChangedAtFirstLevel](const QModelIndex &index) { 1565 if (index.isValid() && !index.parent().isValid()) { 1566 // a change of a node whose parent is root, yay, that's it 1567 dataChangedAtFirstLevel = true; 1568 } 1569 }); 1570 1571 KIO::Job *job = KIO::move(QUrl::fromLocalFile(dir), QUrl::fromLocalFile(file), KIO::HideProgressInfo); 1572 delete KIO::delegateExtension<KIO::AskUserActionInterface *>(job); 1573 auto *askUserHandler = new MockAskUserInterface(job->uiDelegate()); 1574 askUserHandler->m_renameResult = KIO::Result_Overwrite; 1575 QVERIFY(job->exec()); 1576 1577 QCOMPARE(askUserHandler->m_askUserRenameCalled, 1); 1578 1579 // Wait for a removal within the top level (that's for the old file going away), and also 1580 // for a dataChanged which notifies us that a file has become a directory 1581 1582 QTRY_VERIFY(removalWithinTopLevel); 1583 QTRY_VERIFY(dataChangedAtFirstLevel); 1584 1585 m_dirModel->disconnect(rrc); 1586 m_dirModel->disconnect(dcc); 1587 1588 // If we come here, then rowsRemoved() was emitted - all good. 1589 const int topLevelRowCount = m_dirModel->rowCount(); 1590 QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 1); // one less than before 1591 1592 QVERIFY(!m_dirModel->indexForUrl(QUrl::fromLocalFile(dir)).isValid()); 1593 QModelIndex newIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "toplevelfile_1")); 1594 QVERIFY(newIndex.isValid()); 1595 KFileItem newItem = m_dirModel->itemForIndex(newIndex); 1596 QVERIFY(newItem.isDir()); // yes, the file is a dir now ;-) 1597 1598 qDebug() << "========= Test done, recreating test data ========="; 1599 1600 recreateTestData(); 1601 fillModel(false); 1602 } 1603 1604 void KDirModelTest::testDeleteFiles() 1605 { 1606 const int oldTopLevelRowCount = m_dirModel->rowCount(); 1607 const QString file = m_tempDir->path() + "/toplevelfile_"; 1608 QList<QUrl> urls; 1609 urls << QUrl::fromLocalFile(file + '1') << QUrl::fromLocalFile(file + '2') << QUrl::fromLocalFile(file + '3'); 1610 1611 QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); 1612 1613 KIO::DeleteJob *job = KIO::del(urls, KIO::HideProgressInfo); 1614 QVERIFY(job->exec()); 1615 1616 int numRowsRemoved = 0; 1617 while (numRowsRemoved < 3) { 1618 QTest::qWait(20); 1619 1620 numRowsRemoved = 0; 1621 for (int sigNum = 0; sigNum < spyRowsRemoved.count(); ++sigNum) { 1622 numRowsRemoved += spyRowsRemoved[sigNum][2].toInt() - spyRowsRemoved[sigNum][1].toInt() + 1; 1623 } 1624 qDebug() << "numRowsRemoved=" << numRowsRemoved; 1625 } 1626 1627 const int topLevelRowCount = m_dirModel->rowCount(); 1628 QCOMPARE(topLevelRowCount, oldTopLevelRowCount - 3); // three less than before 1629 1630 qDebug() << "Recreating test data"; 1631 recreateTestData(); 1632 qDebug() << "Re-filling model"; 1633 fillModel(false); 1634 } 1635 1636 // A renaming that looks more like a deletion to the model 1637 void KDirModelTest::testRenameFileToHidden() // #174721 1638 { 1639 const QUrl url = QUrl::fromLocalFile(m_tempDir->path() + "/toplevelfile_2"); 1640 const QUrl newUrl = QUrl::fromLocalFile(m_tempDir->path() + "/.toplevelfile_2"); 1641 1642 QSignalSpy spyDataChanged(m_dirModel, &QAbstractItemModel::dataChanged); 1643 QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); 1644 QSignalSpy spyRowsInserted(m_dirModel, &QAbstractItemModel::rowsInserted); 1645 1646 KIO::SimpleJob *job = KIO::rename(url, newUrl, KIO::HideProgressInfo); 1647 QVERIFY(job->exec()); 1648 1649 // Wait for the DBUS signal from KDirNotify, it's the one the triggers KDirLister 1650 if (spyRowsRemoved.isEmpty()) { 1651 QVERIFY(spyRowsRemoved.wait()); 1652 } 1653 1654 // If we come here, then rowsRemoved() was emitted - all good. 1655 QCOMPARE(spyDataChanged.count(), 0); 1656 QCOMPARE(spyRowsRemoved.count(), 1); 1657 QCOMPARE(spyRowsInserted.count(), 0); 1658 COMPARE_INDEXES(spyRowsRemoved[0][0].value<QModelIndex>(), QModelIndex()); // parent is invalid 1659 const int row = spyRowsRemoved[0][1].toInt(); 1660 QCOMPARE(row, m_secondFileIndex.row()); // only compare row 1661 1662 spyRowsRemoved.clear(); 1663 1664 // Put things back to normal, should make the file reappear 1665 job = KIO::rename(newUrl, url, KIO::HideProgressInfo); 1666 QVERIFY(job->exec()); 1667 // Wait for the DBUS signal from KDirNotify, it's the one the triggers KDirLister 1668 if (spyRowsInserted.isEmpty()) { 1669 QVERIFY(spyRowsInserted.wait()); 1670 } 1671 QCOMPARE(spyDataChanged.count(), 0); 1672 QCOMPARE(spyRowsRemoved.count(), 0); 1673 QCOMPARE(spyRowsInserted.count(), 1); 1674 const int newRow = spyRowsInserted[0][1].toInt(); 1675 m_secondFileIndex = m_dirModel->index(newRow, 0); 1676 QVERIFY(m_secondFileIndex.isValid()); 1677 QCOMPARE(m_dirModel->itemForIndex(m_secondFileIndex).url().toString(), url.toString()); 1678 } 1679 1680 void KDirModelTest::testDeleteDirectory() 1681 { 1682 const QString path = m_tempDir->path() + '/'; 1683 const QUrl url = QUrl::fromLocalFile(path + "subdir/subsubdir"); 1684 1685 QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); 1686 1687 QSignalSpy spyDirWatchDeleted(KDirWatch::self(), &KDirWatch::deleted); 1688 1689 KIO::DeleteJob *job = KIO::del(url, KIO::HideProgressInfo); 1690 QVERIFY(job->exec()); 1691 1692 // Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved 1693 if (spyRowsRemoved.isEmpty()) { 1694 QVERIFY(spyRowsRemoved.wait()); 1695 } 1696 1697 // If we come here, then rowsRemoved() was emitted - all good. 1698 QCOMPARE(spyRowsRemoved.count(), 1); 1699 1700 QModelIndex deletedDirIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir/subsubdir")); 1701 QVERIFY(!deletedDirIndex.isValid()); 1702 QModelIndex dirIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "subdir")); 1703 QVERIFY(dirIndex.isValid()); 1704 1705 // Not emitted because DeleteJobPrivate::currentSourceStated calls stopDirScan 1706 // We don't need it anyway, we have the DBus signal 1707 // QCOMPARE(spyDirWatchDeleted.count(), 1); 1708 } 1709 1710 void KDirModelTest::testDeleteCurrentDirectory() 1711 { 1712 const int oldTopLevelRowCount = m_dirModel->rowCount(); 1713 const QString path = m_tempDir->path() + '/'; 1714 const QUrl url = QUrl::fromLocalFile(path); 1715 1716 QSignalSpy spyRowsRemoved(m_dirModel, &QAbstractItemModel::rowsRemoved); 1717 1718 KIO::DeleteJob *job = KIO::del(url, KIO::HideProgressInfo); 1719 QVERIFY(job->exec()); 1720 1721 // Wait for the DBUS signal from KDirNotify, it's the one the triggers rowsRemoved 1722 if (spyRowsRemoved.isEmpty()) { 1723 QVERIFY(spyRowsRemoved.wait()); 1724 } 1725 1726 // If we come here, then rowsRemoved() was emitted - all good. 1727 QTRY_COMPARE(m_dirModel->rowCount(), 0); // empty 1728 1729 // We can get rowsRemoved for subdirs first, since kdirwatch notices that. 1730 QVERIFY(spyRowsRemoved.count() >= 1); 1731 1732 // Look for the signal(s) that had QModelIndex() as parent. 1733 int i; 1734 int numDeleted = 0; 1735 for (i = 0; i < spyRowsRemoved.count(); ++i) { 1736 const int from = spyRowsRemoved[i][1].toInt(); 1737 const int to = spyRowsRemoved[i][2].toInt(); 1738 qDebug() << spyRowsRemoved[i][0].value<QModelIndex>() << from << to; 1739 if (!spyRowsRemoved[i][0].value<QModelIndex>().isValid()) { 1740 numDeleted += (to - from) + 1; 1741 } 1742 } 1743 1744 QCOMPARE(numDeleted, oldTopLevelRowCount); 1745 1746 QModelIndex fileIndex = m_dirModel->indexForUrl(QUrl::fromLocalFile(path + "toplevelfile_1")); 1747 QVERIFY(!fileIndex.isValid()); 1748 } 1749 1750 void KDirModelTest::testQUrlHash() 1751 { 1752 const int count = 3000; 1753 // Prepare an array of QUrls so that url constructing isn't part of the timing 1754 QList<QUrl> urls; 1755 urls.resize(count); 1756 for (int i = 0; i < count; ++i) { 1757 urls[i] = QUrl("http://www.kde.org/path/" + QString::number(i)); 1758 } 1759 QHash<QUrl, int> qurlHash; 1760 QHash<QUrl, int> kurlHash; 1761 QElapsedTimer dt; 1762 dt.start(); 1763 for (int i = 0; i < count; ++i) { 1764 qurlHash.insert(urls[i], i); 1765 } 1766 // qDebug() << "inserting" << count << "urls into QHash using old qHash:" << dt.elapsed() << "msecs"; 1767 dt.start(); 1768 for (int i = 0; i < count; ++i) { 1769 kurlHash.insert(urls[i], i); 1770 } 1771 // qDebug() << "inserting" << count << "urls into QHash using new qHash:" << dt.elapsed() << "msecs"; 1772 // Nice results: for count=30000 I got 4515 (before) and 103 (after) 1773 1774 dt.start(); 1775 for (int i = 0; i < count; ++i) { 1776 QCOMPARE(qurlHash.value(urls[i]), i); 1777 } 1778 // qDebug() << "looking up" << count << "urls into QHash using old qHash:" << dt.elapsed() << "msecs"; 1779 dt.start(); 1780 for (int i = 0; i < count; ++i) { 1781 QCOMPARE(kurlHash.value(urls[i]), i); 1782 } 1783 // qDebug() << "looking up" << count << "urls into QHash using new qHash:" << dt.elapsed() << "msecs"; 1784 // Nice results: for count=30000 I got 4296 (before) and 63 (after) 1785 } 1786 1787 #include "moc_kdirmodeltest.cpp"