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"