File indexing completed on 2024-04-21 03:54:54
0001 /* 0002 This file is part of the KIO framework tests 0003 SPDX-FileCopyrightText: 2016 Albert Astals Cid <aacid@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0006 */ 0007 0008 #include "kfilewidget.h" 0009 0010 #include <QLabel> 0011 #include <QLoggingCategory> 0012 #include <QSignalSpy> 0013 #include <QStandardPaths> 0014 #include <QTemporaryDir> 0015 #include <QTest> 0016 #include <QUrl> 0017 0018 #include "../utils_p.h" 0019 #include "kiotesthelper.h" // createTestFile 0020 #include <KDirLister> 0021 #include <KFileFilterCombo> 0022 #include <KUrlComboBox> 0023 #include <kdiroperator.h> 0024 #include <kurlnavigator.h> 0025 0026 #include <KLocalizedString> 0027 0028 #include <QAbstractItemView> 0029 #include <QDialog> 0030 #include <QDropEvent> 0031 #include <QLineEdit> 0032 #include <QList> 0033 #include <QMimeData> 0034 #include <QStringList> 0035 #include <QStringLiteral> 0036 #include <QUrl> 0037 0038 Q_DECLARE_LOGGING_CATEGORY(KIO_KFILEWIDGETS_FW) 0039 Q_LOGGING_CATEGORY(KIO_KFILEWIDGETS_FW, "kf.kio.kfilewidgets.kfilewidget", QtInfoMsg) 0040 0041 static QWidget *findLocationLabel(QWidget *parent) 0042 { 0043 const QList<QLabel *> labels = parent->findChildren<QLabel *>(); 0044 for (QLabel *label : labels) { 0045 if (label->text() == i18n("&Name:") || label->text() == i18n("Name:")) { 0046 return label->buddy(); 0047 } 0048 } 0049 Q_ASSERT(false); 0050 return nullptr; 0051 } 0052 0053 /** 0054 * Unit test for KFileWidget 0055 */ 0056 class KFileWidgetTest : public QObject 0057 { 0058 Q_OBJECT 0059 0060 private Q_SLOTS: 0061 void initTestCase(); 0062 void testFilterCombo(); 0063 void testFocusOnLocationEdit(); 0064 void testFocusOnLocationEditChangeDir(); 0065 void testFocusOnLocationEditChangeDir2(); 0066 void testFocusOnDirOps(); 0067 void testGetStartUrl(); 0068 void testSetSelection_data(); 0069 0070 void testSetSelectedUrl_data(); 0071 void testSetSelectedUrl(); 0072 void testPreserveFilenameWhileNavigating(); 0073 void testEnterUrl_data(); 0074 void testEnterUrl(); 0075 void testSetFilterForSave_data(); 0076 void testSetFilterForSave(); 0077 void testExtensionForSave_data(); 0078 void testExtensionForSave(); 0079 void testFilterChange(); 0080 void testDropFile_data(); 0081 void testDropFile(); 0082 void testCreateNestedNewFolders(); 0083 void testTokenize_data(); 0084 void testTokenize(); 0085 void testTokenizeForSave_data(); 0086 void testTokenizeForSave(); 0087 }; 0088 0089 void KFileWidgetTest::initTestCase() 0090 { 0091 QStandardPaths::setTestModeEnabled(true); 0092 0093 QVERIFY(QDir::homePath() != QDir::tempPath()); 0094 } 0095 0096 void KFileWidgetTest::testFilterCombo() 0097 { 0098 KFileWidget fw(QUrl(QStringLiteral("kfiledialog:///SaveDialog")), nullptr); 0099 fw.setOperationMode(KFileWidget::Saving); 0100 fw.setMode(KFile::File); 0101 0102 const KFileFilter wordFilter = KFileFilter::fromFilterString("*.xml *.a|Word 2003 XML (.xml)").first(); 0103 const KFileFilter odtFilter = KFileFilter::fromFilterString("*.odt|ODF Text Document (.odt)").first(); 0104 const KFileFilter docBookFilter = KFileFilter::fromFilterString("*.xml *.b|DocBook (.xml)").first(); 0105 const KFileFilter rawFilter = KFileFilter::fromFilterString("*|Raw (*)").first(); 0106 0107 fw.setFilters({wordFilter, odtFilter, docBookFilter, rawFilter}); 0108 0109 // default filter is selected 0110 QCOMPARE(fw.currentFilter(), wordFilter); 0111 0112 // setUrl runs with blocked signals, so use setUrls. 0113 // auto-select ODT filter via filename 0114 fw.locationEdit()->setUrls(QStringList(QStringLiteral("test.odt"))); 0115 QCOMPARE(fw.currentFilter(), odtFilter); 0116 QCOMPARE(fw.locationEdit()->urls()[0], QStringLiteral("test.odt")); 0117 0118 // select 2nd duplicate XML filter (see bug 407642) 0119 fw.filterWidget()->setCurrentFilter(docBookFilter); 0120 QCOMPARE(fw.currentFilter(), docBookFilter); 0121 // when editing the filter, there is delay to avoid refreshing the KDirOperator after each keypress 0122 QTest::qWait(350); 0123 QCOMPARE(fw.locationEdit()->urls()[0], QStringLiteral("test.xml")); 0124 0125 // keep filter after file change with same extension 0126 fw.locationEdit()->setUrls(QStringList(QStringLiteral("test2.xml"))); 0127 QCOMPARE(fw.currentFilter(), docBookFilter); 0128 QCOMPARE(fw.locationEdit()->urls()[0], QStringLiteral("test2.xml")); 0129 0130 // back to the non-xml / ODT filter 0131 fw.locationEdit()->setUrls(QStringList(QStringLiteral("test.odt"))); 0132 QCOMPARE(fw.currentFilter(), odtFilter); 0133 QCOMPARE(fw.locationEdit()->urls()[0], QStringLiteral("test.odt")); 0134 0135 // auto-select 1st XML filter 0136 fw.locationEdit()->setUrls(QStringList(QStringLiteral("test.xml"))); 0137 QCOMPARE(fw.currentFilter(), wordFilter); 0138 QCOMPARE(fw.locationEdit()->urls()[0], QStringLiteral("test.xml")); 0139 0140 // select Raw '*' filter 0141 fw.filterWidget()->setCurrentFilter(rawFilter); 0142 QCOMPARE(fw.currentFilter(), rawFilter); 0143 QCOMPARE(fw.locationEdit()->urls()[0], QStringLiteral("test.xml")); 0144 0145 // keep Raw '*' filter with matching file extension 0146 fw.locationEdit()->setUrls(QStringList(QStringLiteral("test.odt"))); 0147 QCOMPARE(fw.currentFilter(), rawFilter); 0148 QCOMPARE(fw.locationEdit()->urls()[0], QStringLiteral("test.odt")); 0149 0150 // keep Raw '*' filter with non-matching file extension 0151 fw.locationEdit()->setUrls(QStringList(QStringLiteral("test.core"))); 0152 QCOMPARE(fw.currentFilter(), rawFilter); 0153 QCOMPARE(fw.locationEdit()->urls()[0], QStringLiteral("test.core")); 0154 0155 // select 2nd XML filter 0156 fw.filterWidget()->setCurrentFilter(docBookFilter); 0157 QCOMPARE(fw.currentFilter(), docBookFilter); 0158 // when editing the filter, there is delay to avoid refreshing the KDirOperator after each keypress 0159 QTest::qWait(350); 0160 QCOMPARE(fw.locationEdit()->urls()[0], QStringLiteral("test.xml")); 0161 } 0162 0163 void KFileWidgetTest::testFocusOnLocationEdit() 0164 { 0165 KFileWidget fw(QUrl::fromLocalFile(QDir::homePath())); 0166 fw.show(); 0167 fw.activateWindow(); 0168 QVERIFY(QTest::qWaitForWindowActive(&fw)); 0169 0170 QWidget *label = findLocationLabel(&fw); 0171 QVERIFY(label); 0172 QVERIFY(label->hasFocus()); 0173 } 0174 0175 void KFileWidgetTest::testFocusOnLocationEditChangeDir() 0176 { 0177 KFileWidget fw(QUrl::fromLocalFile(QDir::homePath())); 0178 fw.setUrl(QUrl::fromLocalFile(QDir::tempPath())); 0179 fw.show(); 0180 fw.activateWindow(); 0181 QVERIFY(QTest::qWaitForWindowActive(&fw)); 0182 0183 QWidget *label = findLocationLabel(&fw); 0184 QVERIFY(label); 0185 QVERIFY(label->hasFocus()); 0186 } 0187 0188 void KFileWidgetTest::testFocusOnLocationEditChangeDir2() 0189 { 0190 KFileWidget fw(QUrl::fromLocalFile(QDir::homePath())); 0191 fw.show(); 0192 fw.activateWindow(); 0193 QVERIFY(QTest::qWaitForWindowActive(&fw)); 0194 0195 fw.setUrl(QUrl::fromLocalFile(QDir::tempPath())); 0196 0197 QWidget *label = findLocationLabel(&fw); 0198 QVERIFY(label); 0199 QVERIFY(label->hasFocus()); 0200 } 0201 0202 void KFileWidgetTest::testFocusOnDirOps() 0203 { 0204 KFileWidget fw(QUrl::fromLocalFile(QDir::homePath())); 0205 fw.show(); 0206 fw.activateWindow(); 0207 QVERIFY(QTest::qWaitForWindowActive(&fw)); 0208 0209 const QList<KUrlNavigator *> nav = fw.findChildren<KUrlNavigator *>(); 0210 QCOMPARE(nav.count(), 1); 0211 nav[0]->setFocus(); 0212 0213 fw.setUrl(QUrl::fromLocalFile(QDir::tempPath())); 0214 0215 const QList<KDirOperator *> ops = fw.findChildren<KDirOperator *>(); 0216 QCOMPARE(ops.count(), 1); 0217 QVERIFY(ops[0]->hasFocus()); 0218 } 0219 0220 void KFileWidgetTest::testGetStartUrl() 0221 { 0222 QString recentDirClass; 0223 QString outFileName; 0224 QUrl localUrl = KFileWidget::getStartUrl(QUrl(QStringLiteral("kfiledialog:///attachmentDir")), recentDirClass, outFileName); 0225 QCOMPARE(recentDirClass, QStringLiteral(":attachmentDir")); 0226 QCOMPARE(localUrl.toLocalFile(), QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); 0227 QVERIFY(outFileName.isEmpty()); 0228 0229 localUrl = KFileWidget::getStartUrl(QUrl(QStringLiteral("kfiledialog:///attachments/foo.txt")), recentDirClass, outFileName); 0230 QCOMPARE(recentDirClass, QStringLiteral(":attachments")); 0231 QCOMPARE(localUrl.toLocalFile(), QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); 0232 QCOMPARE(outFileName, QStringLiteral("foo.txt")); 0233 } 0234 0235 void KFileWidgetTest::testSetSelection_data() 0236 { 0237 QTest::addColumn<QString>("baseDir"); 0238 QTest::addColumn<QString>("selection"); 0239 QTest::addColumn<QString>("expectedBaseDir"); 0240 QTest::addColumn<QString>("expectedCurrentText"); 0241 0242 const QString baseDir = QDir::homePath(); 0243 // A nice filename to detect URL encoding issues 0244 const QString fileName = QStringLiteral("some:fi#le"); 0245 0246 // Bug 369216, kdialog calls setSelection(path) 0247 QTest::newRow("path") << baseDir << baseDir + QLatin1Char('/') + fileName << baseDir << fileName; 0248 QTest::newRow("differentPath") << QDir::rootPath() << baseDir + QLatin1Char('/') + fileName << baseDir << fileName; 0249 // kdeplatformfiledialoghelper.cpp calls setSelection(URL as string) 0250 QTest::newRow("url") << baseDir << QUrl::fromLocalFile(baseDir + QLatin1Char('/') + fileName).toString() << baseDir << fileName; 0251 // What if someone calls setSelection(fileName)? That breaks, hence e70f8134a2b in plasma-integration.git 0252 QTest::newRow("filename") << baseDir << fileName << baseDir << fileName; 0253 } 0254 0255 void KFileWidgetTest::testSetSelectedUrl_data() 0256 { 0257 QTest::addColumn<QString>("baseDir"); 0258 QTest::addColumn<QUrl>("selectionUrl"); 0259 QTest::addColumn<QString>("expectedBaseDir"); 0260 QTest::addColumn<QString>("expectedCurrentText"); 0261 0262 const QString baseDir = QDir::homePath(); 0263 // A nice filename to detect URL encoding issues 0264 const QString fileName = QStringLiteral("some:fi#le"); 0265 const QUrl fileUrl = QUrl::fromLocalFile(baseDir + QLatin1Char('/') + fileName); 0266 0267 QTest::newRow("path") << baseDir << fileUrl << baseDir << fileName; 0268 QTest::newRow("differentPath") << QDir::rootPath() << fileUrl << baseDir << fileName; 0269 QTest::newRow("url") << baseDir << QUrl::fromLocalFile(baseDir + QLatin1Char('/') + fileName) << baseDir << fileName; 0270 0271 QUrl relativeUrl; 0272 relativeUrl.setPath(fileName); 0273 QTest::newRow("filename") << baseDir << relativeUrl << baseDir << fileName; 0274 } 0275 0276 void KFileWidgetTest::testSetSelectedUrl() 0277 { 0278 // GIVEN 0279 QFETCH(QString, baseDir); 0280 QFETCH(QUrl, selectionUrl); 0281 QFETCH(QString, expectedBaseDir); 0282 QFETCH(QString, expectedCurrentText); 0283 0284 const QUrl baseUrl = QUrl::fromLocalFile(baseDir).adjusted(QUrl::StripTrailingSlash); 0285 const QUrl expectedBaseUrl = QUrl::fromLocalFile(expectedBaseDir); 0286 KFileWidget fw(baseUrl); 0287 0288 // WHEN 0289 fw.setSelectedUrl(selectionUrl); 0290 0291 // THEN 0292 QCOMPARE(fw.baseUrl().adjusted(QUrl::StripTrailingSlash), expectedBaseUrl); 0293 QCOMPARE(fw.locationEdit()->currentText(), expectedCurrentText); 0294 } 0295 0296 void KFileWidgetTest::testPreserveFilenameWhileNavigating() // bug 418711 0297 { 0298 // GIVEN 0299 const QUrl url = QUrl::fromLocalFile(QDir::homePath()); 0300 KFileWidget fw(url); 0301 fw.setOperationMode(KFileWidget::Saving); 0302 fw.setMode(KFile::File); 0303 QString baseDir = QDir::homePath(); 0304 if (baseDir.endsWith('/')) { 0305 baseDir.chop(1); 0306 } 0307 const QString fileName = QStringLiteral("somefi#le"); 0308 const QUrl fileUrl = QUrl::fromLocalFile(baseDir + QLatin1Char('/') + fileName); 0309 fw.setSelectedUrl(fileUrl); 0310 const QUrl baseUrl = QUrl::fromLocalFile(baseDir); 0311 QCOMPARE(fw.baseUrl().adjusted(QUrl::StripTrailingSlash), baseUrl); 0312 QCOMPARE(fw.locationEdit()->currentText(), fileName); 0313 0314 // WHEN 0315 fw.dirOperator()->cdUp(); 0316 0317 // THEN 0318 QCOMPARE(fw.baseUrl().adjusted(QUrl::StripTrailingSlash), baseUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash)); 0319 QCOMPARE(fw.locationEdit()->currentText(), fileName); // unchanged 0320 } 0321 0322 void KFileWidgetTest::testEnterUrl_data() 0323 { 0324 QTest::addColumn<QUrl>("expectedUrl"); 0325 0326 // Check if the root urls are well transformed into themself, otherwise 0327 // when going up from file:///home/ it will become file:///home/user 0328 QTest::newRow("file") << QUrl::fromLocalFile("/"); 0329 QTest::newRow("trash") << QUrl("trash:/"); 0330 QTest::newRow("sftp") << QUrl("sftp://127.0.0.1/"); 0331 } 0332 0333 void KFileWidgetTest::testEnterUrl() 0334 { 0335 // GIVEN 0336 QFETCH(QUrl, expectedUrl); 0337 0338 // WHEN 0339 // These lines are copied from src/filewidgets/kfilewidget.cpp 0340 // void KFileWidgetPrivate::_k_enterUrl(const QUrl &url) 0341 QUrl u(expectedUrl); 0342 Utils::appendSlashToPath(u); 0343 // THEN 0344 QVERIFY(u.isValid()); 0345 QCOMPARE(u, expectedUrl); 0346 } 0347 0348 void KFileWidgetTest::testSetFilterForSave_data() 0349 { 0350 QTest::addColumn<QString>("fileName"); 0351 QTest::addColumn<QString>("filter"); 0352 QTest::addColumn<QString>("expectedCurrentText"); 0353 QTest::addColumn<QString>("expectedSelectedFileName"); 0354 0355 const QString filter = QStringLiteral("*.txt|Text files\n*.HTML|HTML files"); 0356 0357 QTest::newRow("some.txt") << "some.txt" << filter << QStringLiteral("some.txt") << QStringLiteral("some.txt"); 0358 0359 // If an application provides a name without extension, then the 0360 // displayed name will not receive an extension. It will however be 0361 // appended when the dialog is closed. 0362 QTest::newRow("extensionless name") << "some" << filter << QStringLiteral("some") << QStringLiteral("some.txt"); 0363 0364 // If the file literally exists, then no new extension will be appended. 0365 QTest::newRow("existing file") << "README" << filter << QStringLiteral("README") << QStringLiteral("README"); 0366 0367 // XXX perhaps the "extension" should not be modified when it does not 0368 // match any of the existing types? Should "some.2019.txt" be expected? 0369 QTest::newRow("some.2019") << "some.2019" << filter << QStringLiteral("some.txt") << QStringLiteral("some.txt"); 0370 0371 // XXX be smarter and do not change the extension if one of the other 0372 // filters match. Should "some.html" be expected? 0373 QTest::newRow("some.html") << "some.html" << filter << QStringLiteral("some.txt") << QStringLiteral("some.txt"); 0374 } 0375 0376 void KFileWidgetTest::testSetFilterForSave() 0377 { 0378 QFETCH(QString, fileName); 0379 QFETCH(QString, filter); 0380 QFETCH(QString, expectedCurrentText); 0381 QFETCH(QString, expectedSelectedFileName); 0382 0383 // Use a temporary directory since the presence of existing files 0384 // influences whether an extension is automatically appended. 0385 QTemporaryDir tempDir; 0386 const QUrl dirUrl = QUrl::fromLocalFile(tempDir.path()); 0387 const QUrl fileUrl = QUrl::fromLocalFile(tempDir.filePath(fileName)); 0388 const QUrl expectedFileUrl = QUrl::fromLocalFile(tempDir.filePath(expectedSelectedFileName)); 0389 createTestFile(tempDir.filePath("README")); 0390 0391 KFileWidget fw(dirUrl); 0392 fw.setOperationMode(KFileWidget::Saving); 0393 fw.setSelectedUrl(fileUrl); 0394 // Calling setFilter has side-effects and changes the file name. 0395 fw.setFilters(KFileFilter::fromFilterString(filter)); 0396 0397 // Verify the expected populated name. 0398 QCOMPARE(fw.baseUrl().adjusted(QUrl::StripTrailingSlash), dirUrl); 0399 QCOMPARE(fw.locationEdit()->currentText(), expectedCurrentText); 0400 0401 // QFileDialog ends up calling KDEPlatformFileDialog::selectedFiles() 0402 // which calls KFileWidget::selectedUrls(). 0403 // Accept the filename to ensure that a filename is selected. 0404 connect(&fw, &KFileWidget::accepted, &fw, &KFileWidget::accept); 0405 QTest::keyClick(fw.locationEdit(), Qt::Key_Return); 0406 QList<QUrl> urls = fw.selectedUrls(); 0407 QCOMPARE(urls.size(), 1); 0408 QCOMPARE(urls[0], expectedFileUrl); 0409 } 0410 0411 void KFileWidgetTest::testExtensionForSave_data() 0412 { 0413 QTest::addColumn<QString>("fileName"); 0414 QTest::addColumn<QString>("filter"); 0415 QTest::addColumn<QString>("expectedCurrentText"); 0416 QTest::addColumn<QString>("expectedSelectedFileName"); 0417 0418 const QString filter = QStringLiteral("*.txt *.text|Text files\n*.HTML|HTML files"); 0419 0420 QTest::newRow("some.txt") << "some.txt" << filter << QStringLiteral("some.txt") << QStringLiteral("some.txt"); 0421 0422 // If an application provides a name without extension, then the 0423 // displayed name will not receive an extension. It will however be 0424 // appended when the dialog is closed. 0425 QTest::newRow("extensionless name") << "some" << filter << QStringLiteral("some") << QStringLiteral("some.txt"); 0426 QTest::newRow("extensionless name") << "some.with_dot" << filter << QStringLiteral("some.with_dot") << QStringLiteral("some.with_dot.txt"); 0427 QTest::newRow("extensionless name") << "some.with.dots" << filter << QStringLiteral("some.with.dots") << QStringLiteral("some.with.dots.txt"); 0428 0429 // If the file literally exists, then no new extension will be appended. 0430 QTest::newRow("existing file") << "README" << filter << QStringLiteral("README") << QStringLiteral("README"); 0431 } 0432 0433 void KFileWidgetTest::testExtensionForSave() 0434 { 0435 QFETCH(QString, fileName); 0436 QFETCH(QString, filter); 0437 QFETCH(QString, expectedCurrentText); 0438 QFETCH(QString, expectedSelectedFileName); 0439 0440 // Use a temporary directory since the presence of existing files 0441 // influences whether an extension is automatically appended. 0442 QTemporaryDir tempDir; 0443 const QUrl dirUrl = QUrl::fromLocalFile(tempDir.path()); 0444 const QUrl fileUrl = QUrl::fromLocalFile(tempDir.filePath(fileName)); 0445 const QUrl expectedFileUrl = QUrl::fromLocalFile(tempDir.filePath(expectedSelectedFileName)); 0446 createTestFile(tempDir.filePath("README")); 0447 0448 KFileWidget fw(dirUrl); 0449 fw.setOperationMode(KFileWidget::Saving); 0450 // Calling setFilter has side-effects and changes the file name. 0451 // The difference to testSetFilterForSave is that the filter is already set before the fileUrl 0452 // is set, and will not be changed after. 0453 fw.setFilters(KFileFilter::fromFilterString(filter)); 0454 fw.setSelectedUrl(fileUrl); 0455 0456 // Verify the expected populated name. 0457 QCOMPARE(fw.baseUrl().adjusted(QUrl::StripTrailingSlash), dirUrl); 0458 QCOMPARE(fw.locationEdit()->currentText(), expectedCurrentText); 0459 0460 // QFileDialog ends up calling KDEPlatformFileDialog::selectedFiles() 0461 // which calls KFileWidget::selectedUrls(). 0462 // Accept the filename to ensure that a filename is selected. 0463 connect(&fw, &KFileWidget::accepted, &fw, &KFileWidget::accept); 0464 QTest::keyClick(fw.locationEdit(), Qt::Key_Return); 0465 QList<QUrl> urls = fw.selectedUrls(); 0466 QCOMPARE(urls.size(), 1); 0467 QCOMPARE(urls[0], expectedFileUrl); 0468 } 0469 0470 void KFileWidgetTest::testFilterChange() 0471 { 0472 QTemporaryDir tempDir; 0473 createTestFile(tempDir.filePath("some.c")); 0474 bool created = QDir(tempDir.path()).mkdir("directory"); 0475 Q_ASSERT(created); 0476 0477 KFileWidget fw(QUrl::fromLocalFile(tempDir.path())); 0478 fw.setOperationMode(KFileWidget::Saving); 0479 fw.setSelectedUrl(QUrl::fromLocalFile(tempDir.filePath("some.txt"))); 0480 const QList<KFileFilter> filters = KFileFilter::fromFilterString("*.txt|Txt\n*.c|C"); 0481 fw.setFilters(filters); 0482 0483 // Initial filename. 0484 QCOMPARE(fw.locationEdit()->currentText(), QStringLiteral("some.txt")); 0485 QCOMPARE(fw.filterWidget()->currentFilter(), filters[0]); 0486 0487 // Select type with an existing file. 0488 fw.filterWidget()->setCurrentFilter(filters[1]); 0489 // when editing the filter, there is delay to avoid refreshing the KDirOperator after each keypress 0490 QTest::qWait(350); 0491 QCOMPARE(fw.locationEdit()->currentText(), QStringLiteral("some.c")); 0492 QCOMPARE(fw.filterWidget()->currentFilter(), filters[1]); 0493 0494 // Do not update extension if the current selection is a directory. 0495 fw.setSelectedUrl(QUrl::fromLocalFile(tempDir.filePath("directory"))); 0496 fw.filterWidget()->setCurrentFilter(filters[0]); 0497 QCOMPARE(fw.locationEdit()->currentText(), QStringLiteral("directory")); 0498 QCOMPARE(fw.filterWidget()->currentFilter(), filters[0]); 0499 0500 // The user types something into the combobox. 0501 fw.filterWidget()->setCurrentText("qml"); 0502 0503 QSignalSpy filterChangedSpy(&fw, &KFileWidget::filterChanged); 0504 filterChangedSpy.wait(); 0505 QVERIFY(filterChangedSpy.count()); 0506 0507 // Plain text is automatically upgraded to wildcard syntax 0508 QCOMPARE(fw.dirOperator()->nameFilter(), "*qml*"); 0509 0510 // But existing wildcards are left intact 0511 fw.filterWidget()->setCurrentText("*.md"); 0512 filterChangedSpy.wait(); 0513 QVERIFY(filterChangedSpy.count()); 0514 QCOMPARE(fw.dirOperator()->nameFilter(), "*.md"); 0515 0516 fw.filterWidget()->setCurrentText("[ab]c"); 0517 filterChangedSpy.wait(); 0518 QVERIFY(filterChangedSpy.count()); 0519 QCOMPARE(fw.dirOperator()->nameFilter(), "[ab]c"); 0520 0521 fw.filterWidget()->setCurrentText("b?c"); 0522 filterChangedSpy.wait(); 0523 QVERIFY(filterChangedSpy.count()); 0524 QCOMPARE(fw.dirOperator()->nameFilter(), "b?c"); 0525 } 0526 0527 void KFileWidgetTest::testDropFile_data() 0528 { 0529 QTest::addColumn<QString>("dir"); 0530 QTest::addColumn<QString>("fileName"); 0531 QTest::addColumn<QString>("expectedCurrentText"); 0532 0533 QTest::newRow("some.txt") << "" 0534 << "some.txt" 0535 << "some.txt"; 0536 0537 QTest::newRow("subdir/some.txt") << "subdir" 0538 << "subdir/some.txt" 0539 << "some.txt"; 0540 } 0541 0542 void KFileWidgetTest::testDropFile() 0543 { 0544 QFETCH(QString, dir); 0545 QFETCH(QString, fileName); 0546 QFETCH(QString, expectedCurrentText); 0547 0548 // Use a temporary directory since the presence of existing files 0549 // influences whether an extension is automatically appended. 0550 QTemporaryDir tempDir; 0551 QUrl dirUrl = QUrl::fromLocalFile(tempDir.path()); 0552 const QUrl fileUrl = QUrl::fromLocalFile(tempDir.filePath(fileName)); 0553 if (!dir.isEmpty()) { 0554 createTestDirectory(tempDir.filePath(dir)); 0555 dirUrl = QUrl::fromLocalFile(tempDir.filePath(dir)); 0556 } 0557 createTestFile(tempDir.filePath(fileName)); 0558 0559 KFileWidget fileWidget(QUrl::fromLocalFile(tempDir.path())); 0560 fileWidget.setOperationMode(KFileWidget::Saving); 0561 fileWidget.setMode(KFile::File); 0562 fileWidget.show(); 0563 0564 QMimeData *mimeData = new QMimeData(); 0565 mimeData->setUrls(QList<QUrl>() << fileUrl); 0566 0567 KDirLister *dirLister = fileWidget.dirOperator()->dirLister(); 0568 QSignalSpy spy(dirLister, qOverload<>(&KCoreDirLister::completed)); 0569 0570 QAbstractItemView *view = fileWidget.dirOperator()->view(); 0571 QVERIFY(view); 0572 0573 QDragEnterEvent event1(QPoint(), Qt::DropAction::MoveAction, mimeData, Qt::MouseButton::LeftButton, Qt::KeyboardModifier::NoModifier); 0574 QVERIFY(qApp->sendEvent(view->viewport(), &event1)); 0575 0576 // Fake drop 0577 QDropEvent event(QPoint(), Qt::DropAction::MoveAction, mimeData, Qt::MouseButton::LeftButton, Qt::KeyboardModifier::NoModifier); 0578 QVERIFY(qApp->sendEvent(view->viewport(), &event)); 0579 0580 if (!dir.isEmpty()) { 0581 // once we drop a file the dirlister scans the dir 0582 // wait for the completed signal from the dirlister 0583 QVERIFY(spy.wait()); 0584 } 0585 0586 // Verify the expected populated name. 0587 QCOMPARE(fileWidget.baseUrl().adjusted(QUrl::StripTrailingSlash), dirUrl); 0588 QCOMPARE(fileWidget.locationEdit()->currentText(), expectedCurrentText); 0589 0590 // QFileDialog ends up calling KDEPlatformFileDialog::selectedFiles() 0591 // which calls KFileWidget::selectedUrls(). 0592 // Accept the filename to ensure that a filename is selected. 0593 connect(&fileWidget, &KFileWidget::accepted, &fileWidget, &KFileWidget::accept); 0594 QTest::keyClick(fileWidget.locationEdit(), Qt::Key_Return); 0595 const QList<QUrl> urls = fileWidget.selectedUrls(); 0596 QCOMPARE(urls.size(), 1); 0597 QCOMPARE(urls[0], fileUrl); 0598 } 0599 0600 void KFileWidgetTest::testCreateNestedNewFolders() 0601 { 0602 // when creating multiple nested new folders in the "save as" dialog, where folders are 0603 // created and entered, kdirlister would hit an assert (in reinsert()), bug 408801 0604 QTemporaryDir tempDir; 0605 QVERIFY(tempDir.isValid()); 0606 const QString dir = tempDir.path(); 0607 const QUrl url = QUrl::fromLocalFile(dir); 0608 KFileWidget fw(url); 0609 fw.setOperationMode(KFileWidget::Saving); 0610 fw.setMode(KFile::File); 0611 0612 QString currentPath = dir; 0613 // create the nested folders 0614 for (int i = 1; i < 6; ++i) { 0615 fw.dirOperator()->mkdir(); 0616 QDialog *dialog; 0617 // QTRY_ because a NameFinderJob could be running and the dialog will be shown when 0618 // it finishes. 0619 QTRY_VERIFY(dialog = fw.findChild<QDialog *>()); 0620 QLineEdit *lineEdit = dialog->findChild<QLineEdit *>(); 0621 QVERIFY(lineEdit); 0622 const QString name = QStringLiteral("folder%1").arg(i); 0623 lineEdit->setText(name); 0624 // simulate the time the user will take to type the new folder name 0625 QTest::qWait(1000); 0626 0627 dialog->accept(); 0628 0629 currentPath += QLatin1Char('/') + name; 0630 // Wait till the filewidget changes to the new folder 0631 QTRY_COMPARE(fw.baseUrl().adjusted(QUrl::StripTrailingSlash).toLocalFile(), currentPath); 0632 } 0633 } 0634 0635 void KFileWidgetTest::testTokenize_data() 0636 { 0637 // Real filename (as in how they are stored in the fs) 0638 QTest::addColumn<QStringList>("fileNames"); 0639 // Escaped value of the text-box in the dialog 0640 QTest::addColumn<QString>("expectedCurrentText"); 0641 0642 QTest::newRow("simple") << QStringList{"test2"} << QString("test2"); 0643 0644 // When a single file with space is selected, it is _not_ quoted ... 0645 QTest::newRow("space-single-file") << QStringList{"test space"} << QString("test space"); 0646 0647 // However, when multiple files are selected, they are quoted 0648 QTest::newRow("space-multi-file") << QStringList{"test space", "test2"} << QString("\"test space\" \"test2\""); 0649 0650 // All quotes in names should be escaped, however since this is a single 0651 // file, the whole name will not be escaped. 0652 QTest::newRow("quote-single-file") << QStringList{"test\"quote"} << QString("test\\\"quote"); 0653 0654 // Escape multiple files. Files should also be wrapped in "" 0655 // Note that we are also testing quote at the end of the name 0656 QTest::newRow("quote-multi-file") << QStringList{"test\"quote", "test2-quote\"", "test"} << QString("\"test\\\"quote\" \"test2-quote\\\"\" \"test\""); 0657 0658 // Ok, enough with quotes... lets do some backslashes 0659 // Backslash literals in file names - Unix only case 0660 QTest::newRow("backslash-single-file") << QStringList{"test\\backslash"} << QString("test\\\\backslash"); 0661 0662 QTest::newRow("backslash-multi-file") << QStringList{"test\\back\\slash", "test"} << QString("\"test\\\\back\\\\slash\" \"test\""); 0663 0664 QTest::newRow("double-backslash-multi-file") << QStringList{"test\\\\back\\slash", "test"} << QString("\"test\\\\\\\\back\\\\slash\" \"test\""); 0665 0666 QTest::newRow("double-backslash-end") << QStringList{"test\\\\"} << QString("test\\\\\\\\"); 0667 0668 QTest::newRow("single-backslash-end") << QStringList{"some thing", "test\\"} << QString("\"some thing\" \"test\\\\\""); 0669 0670 QTest::newRow("sharp") << QStringList{"some#thing"} << QString("some#thing"); 0671 0672 // Filenames beginning with ':'; QDir::isAbsolutePath() always returns true 0673 // in that case, #322837 0674 QTest::newRow("file-beginning-with-colon") << QStringList{":test2"} << QString{":test2"}; 0675 0676 QTest::newRow("multiple-files-beginning-with-colon") << QStringList{":test space", ":test2"} << QString{"\":test space\" \":test2\""}; 0677 0678 // # 473228 0679 QTest::newRow("file-beginning-with-something-that-looks-like-a-url-scheme") << QStringList{"Hello: foo.txt"} << QString{"Hello: foo.txt"}; 0680 QTest::newRow("file-beginning-with-something-that-looks-like-a-file-url-scheme") << QStringList{"file: /foo.txt"} << QString{"file: /foo.txt"}; 0681 0682 QTemporaryDir otherTempDir; 0683 otherTempDir.setAutoRemove(false); 0684 const auto testFile1Path = otherTempDir.filePath("test-1"); 0685 createTestFile(testFile1Path); 0686 const auto testFile2Path = otherTempDir.filePath("test-2"); 0687 createTestFile(testFile2Path); 0688 0689 QTest::newRow("absolute-url-not-in-dir") << QStringList{"file://" + testFile1Path} << QString{"file://" + testFile1Path}; 0690 QTest::newRow("absolute-urls-not-in-dir") << QStringList{"file://" + testFile1Path, "file://" + testFile2Path} 0691 << QString{"\"file://" + testFile1Path + "\" \"file://" + testFile2Path + "\""}; 0692 0693 auto expectedtestFile1Path = testFile1Path; 0694 expectedtestFile1Path = expectedtestFile1Path.remove(0, 1); 0695 auto expectedtestFile2Path = testFile2Path; 0696 expectedtestFile2Path = expectedtestFile2Path.remove(0, 1); 0697 0698 QTest::newRow("absolute-url-not-in-dir-no-scheme") << QStringList{testFile1Path} << QString{testFile1Path}; 0699 QTest::newRow("absolute-urls-not-in-dir-no-scheme") 0700 << QStringList{testFile1Path, testFile2Path} << QString{"\"" + testFile1Path + "\" \"" + testFile2Path + "\""}; 0701 0702 QTest::newRow("absolute-urls-not-in-dir-scheme-mixed") 0703 << QStringList{testFile1Path, "file://" + testFile2Path} << QString{"\"" + testFile1Path + "\" \"file://" + testFile2Path + "\""}; 0704 } 0705 0706 void KFileWidgetTest::testTokenize() 0707 { 0708 // We will use setSelectedUrls([QUrl]) here in order to check correct 0709 // filename escaping. Afterwards we will accept() the dialog to confirm 0710 // correct result 0711 QFETCH(QStringList, fileNames); 0712 QFETCH(QString, expectedCurrentText); 0713 0714 QTemporaryDir tempDir; 0715 const QString tempDirPath = tempDir.path(); 0716 const QUrl tempDirUrl = QUrl::fromLocalFile(tempDirPath); 0717 QList<QUrl> fileUrls; 0718 for (const auto &fileName : fileNames) { 0719 auto localUrl = QUrl(fileName); 0720 if (!localUrl.path().startsWith(QLatin1Char('/'))) { 0721 const QString filePath = tempDirPath + QLatin1Char('/') + fileName; 0722 localUrl = QUrl::fromLocalFile(filePath); 0723 } 0724 fileUrls.append(localUrl); 0725 qCDebug(KIO_KFILEWIDGETS_FW) << fileName << " => " << localUrl; 0726 } 0727 0728 KFileWidget fw(tempDirUrl); 0729 fw.setOperationMode(KFileWidget::Opening); 0730 fw.setMode(KFile::Files); 0731 fw.setSelectedUrls(fileUrls); 0732 0733 // Verify the expected populated name. 0734 QCOMPARE(fw.baseUrl().adjusted(QUrl::StripTrailingSlash), tempDirUrl); 0735 QCOMPARE(fw.locationEdit()->currentText(), expectedCurrentText); 0736 0737 // QFileDialog ends up calling KDEPlatformFileDialog::selectedFiles() 0738 // which calls KFileWidget::selectedUrls(). 0739 // Accept the filename to ensure that a filename is selected. 0740 connect(&fw, &KFileWidget::accepted, &fw, &KFileWidget::accept); 0741 QTest::keyClick(fw.locationEdit(), Qt::Key_Return); 0742 const QList<QUrl> urls = fw.selectedUrls(); 0743 0744 // We must have the same size as requested files 0745 QCOMPARE(urls.size(), fileNames.size()); 0746 0747 for (auto &localUrl : fileUrls) { 0748 if (localUrl.scheme().isEmpty()) { 0749 localUrl.setScheme("file"); 0750 } 0751 } 0752 QCOMPARE(urls, fileUrls); 0753 } 0754 0755 void KFileWidgetTest::testTokenizeForSave_data() 0756 { 0757 // Real filename (as in how they are stored in the fs) 0758 QTest::addColumn<QString>("fileName"); 0759 // Escaped value of the text-box in the dialog 0760 // Escaped cwd: Because setSelectedUrl is called in this 0761 // test, it actually sets the CWD to the dirname and only 0762 // keeps the filename displayed in the test 0763 QTest::addColumn<QString>("expectedSubFolder"); 0764 QTest::addColumn<QString>("expectedCurrentText"); 0765 0766 QTest::newRow("save-simple") << QString{"test2"} << QString() << QString("test2"); 0767 0768 // When a single file with space is selected, it is _not_ quoted ... 0769 QTest::newRow("save-space") << QString{"test space"} << QString() << QString("test space"); 0770 0771 // All quotes in names should be escaped, however since this is a single 0772 // file, the whole name will not be escaped. 0773 QTest::newRow("save-quote") << QString{"test\"quote"} << QString() << QString("test\\\"quote"); 0774 0775 // Ok, enough with quotes... lets do some backslashes 0776 // Backslash literals in file names - Unix only case 0777 QTest::newRow("save-backslash") << QString{"test\\backslash"} << QString() << QString("test\\\\backslash"); 0778 0779 QTest::newRow("save-double-backslash") << QString{"test\\\\back\\slash"} << QString() << QString("test\\\\\\\\back\\\\slash"); 0780 0781 QTest::newRow("save-double-backslash-end") << QString{"test\\\\"} << QString() << QString("test\\\\\\\\"); 0782 0783 QTest::newRow("save-single-backslash-end") << QString{"test\\"} << QString() << QString("test\\\\"); 0784 0785 QTest::newRow("save-sharp") << QString{"some#thing"} << QString() << QString("some#thing"); 0786 0787 // Filenames beginning with ':'; QDir::isAbsolutePath() always returns true 0788 // in that case, #322837 0789 QTest::newRow("save-file-beginning-with-colon") << QString{":test2"} << QString() << QString{":test2"}; 0790 0791 // # 473228 0792 QTest::newRow("save-save-file-beginning-with-something-that-looks-like-a-url-scheme") 0793 << QString{"Hello: foo.txt"} << QString() << QString{"Hello: foo.txt"}; 0794 0795 QTemporaryDir otherTempDir; 0796 otherTempDir.setAutoRemove(false); 0797 const auto testFile1Path = otherTempDir.filePath("test-1"); 0798 createTestFile(testFile1Path); 0799 0800 QTest::newRow("save-absolute-url-not-in-dir") << QString{"file://" + testFile1Path} << otherTempDir.path() << QString{"test-1"}; 0801 0802 auto expectedtestFile1Path = testFile1Path; 0803 expectedtestFile1Path = expectedtestFile1Path.remove(0, 1); 0804 0805 QTest::newRow("save-absolute-url-not-in-dir-no-scheme") << QString{testFile1Path} << otherTempDir.path() << QString{"test-1"}; 0806 } 0807 0808 void KFileWidgetTest::testTokenizeForSave() 0809 { 0810 // We will use setSelectedUrls([QUrl]) here in order to check correct 0811 // filename escaping. Afterwards we will accept() the dialog to confirm 0812 // correct result 0813 0814 // This test is similar to testTokenize but focuses on single-file 0815 // "save" operation. This follows a different code-path internally 0816 // and calls setSelectedUrl instead of setSelectedUrls. 0817 0818 QFETCH(QString, fileName); 0819 QFETCH(QString, expectedSubFolder); 0820 QFETCH(QString, expectedCurrentText); 0821 0822 QTemporaryDir tempDir; 0823 const QString tempDirPath = tempDir.path(); 0824 const QUrl tempDirUrl = QUrl::fromLocalFile(tempDirPath); 0825 auto fileUrl = QUrl(fileName); 0826 if (!fileUrl.path().startsWith(QLatin1Char('/'))) { 0827 const QString filePath = tempDirPath + QLatin1Char('/') + fileName; 0828 fileUrl = QUrl::fromLocalFile(filePath); 0829 } 0830 if (fileUrl.scheme().isEmpty()) { 0831 fileUrl.setScheme("file"); 0832 } 0833 qCDebug(KIO_KFILEWIDGETS_FW) << fileName << " => " << fileUrl; 0834 0835 KFileWidget fw(tempDirUrl); 0836 fw.setOperationMode(KFileWidget::Saving); 0837 fw.setMode(KFile::File); 0838 fw.setSelectedUrl(fileUrl); 0839 0840 // Verify the expected populated name. 0841 if (expectedSubFolder != "") { 0842 const QUrl expectedBaseUrl = tempDirUrl.resolved(QUrl::fromLocalFile(expectedSubFolder)); 0843 QCOMPARE(fw.baseUrl().adjusted(QUrl::StripTrailingSlash), expectedBaseUrl); 0844 } else { 0845 QCOMPARE(fw.baseUrl().adjusted(QUrl::StripTrailingSlash), tempDirUrl); 0846 } 0847 QCOMPARE(fw.locationEdit()->currentText(), expectedCurrentText); 0848 0849 // QFileDialog ends up calling KDEPlatformFileDialog::selectedFiles() 0850 // which calls KFileWidget::selectedUrls(). 0851 // Accept the filename to ensure that a filename is selected. 0852 connect(&fw, &KFileWidget::accepted, &fw, &KFileWidget::accept); 0853 QTest::keyClick(fw.locationEdit(), Qt::Key_Return); 0854 const QList<QUrl> urls = fw.selectedUrls(); 0855 0856 // We always only have one URL here 0857 QCOMPARE(urls.size(), 1); 0858 QCOMPARE(urls[0], fileUrl); 0859 } 0860 0861 QTEST_MAIN(KFileWidgetTest) 0862 0863 #include "kfilewidgettest.moc"