File indexing completed on 2024-03-24 15:33:33

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 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(5, 33)
0071     void testSetSelection();
0072 #endif
0073 
0074     void testSetSelectedUrl_data();
0075     void testSetSelectedUrl();
0076     void testPreserveFilenameWhileNavigating();
0077     void testEnterUrl_data();
0078     void testEnterUrl();
0079     void testSetFilterForSave_data();
0080     void testSetFilterForSave();
0081     void testExtensionForSave_data();
0082     void testExtensionForSave();
0083     void testFilterChange();
0084     void testDropFile_data();
0085     void testDropFile();
0086     void testCreateNestedNewFolders();
0087     void testTokenize_data();
0088     void testTokenize();
0089     void testTokenizeForSave_data();
0090     void testTokenizeForSave();
0091 };
0092 
0093 void KFileWidgetTest::initTestCase()
0094 {
0095     QStandardPaths::setTestModeEnabled(true);
0096 
0097     QVERIFY(QDir::homePath() != QDir::tempPath());
0098 }
0099 
0100 void KFileWidgetTest::testFilterCombo()
0101 {
0102     KFileWidget fw(QUrl(QStringLiteral("kfiledialog:///SaveDialog")), nullptr);
0103     fw.setOperationMode(KFileWidget::Saving);
0104     fw.setMode(KFile::File);
0105 
0106     fw.setFilter(
0107         QStringLiteral("*.xml *.a|Word 2003 XML (.xml)\n"
0108                        "*.odt|ODF Text Document (.odt)\n"
0109                        "*.xml *.b|DocBook (.xml)\n"
0110                        "*|Raw (*)"));
0111 
0112     // default filter is selected
0113     QCOMPARE(fw.currentFilter(), QStringLiteral("*.xml *.a"));
0114 
0115     // setUrl runs with blocked signals, so use setUrls.
0116     // auto-select ODT filter via filename
0117     fw.locationEdit()->setUrls(QStringList(QStringLiteral("test.odt")));
0118     QCOMPARE(fw.currentFilter(), QStringLiteral("*.odt"));
0119     QCOMPARE(fw.locationEdit()->urls()[0], QStringLiteral("test.odt"));
0120 
0121     // select 2nd duplicate XML filter (see bug 407642)
0122     fw.filterWidget()->setCurrentFilter("*.xml *.b|DocBook (.xml)");
0123     QCOMPARE(fw.currentFilter(), QStringLiteral("*.xml *.b"));
0124     QCOMPARE(fw.locationEdit()->urls()[0], QStringLiteral("test.xml"));
0125 
0126     // keep filter after file change with same extension
0127     fw.locationEdit()->setUrls(QStringList(QStringLiteral("test2.xml")));
0128     QCOMPARE(fw.currentFilter(), QStringLiteral("*.xml *.b"));
0129     QCOMPARE(fw.locationEdit()->urls()[0], QStringLiteral("test2.xml"));
0130 
0131     // back to the non-xml / ODT filter
0132     fw.locationEdit()->setUrls(QStringList(QStringLiteral("test.odt")));
0133     QCOMPARE(fw.currentFilter(), QStringLiteral("*.odt"));
0134     QCOMPARE(fw.locationEdit()->urls()[0], QStringLiteral("test.odt"));
0135 
0136     // auto-select 1st XML filter
0137     fw.locationEdit()->setUrls(QStringList(QStringLiteral("test.xml")));
0138     QCOMPARE(fw.currentFilter(), QStringLiteral("*.xml *.a"));
0139     QCOMPARE(fw.locationEdit()->urls()[0], QStringLiteral("test.xml"));
0140 
0141     // select Raw '*' filter
0142     fw.filterWidget()->setCurrentFilter("*|Raw (*)");
0143     QCOMPARE(fw.currentFilter(), QStringLiteral("*"));
0144     QCOMPARE(fw.locationEdit()->urls()[0], QStringLiteral("test.xml"));
0145 
0146     // keep Raw '*' filter with matching file extension
0147     fw.locationEdit()->setUrls(QStringList(QStringLiteral("test.odt")));
0148     QCOMPARE(fw.currentFilter(), QStringLiteral("*"));
0149     QCOMPARE(fw.locationEdit()->urls()[0], QStringLiteral("test.odt"));
0150 
0151     // keep Raw '*' filter with non-matching file extension
0152     fw.locationEdit()->setUrls(QStringList(QStringLiteral("test.core")));
0153     QCOMPARE(fw.currentFilter(), QStringLiteral("*"));
0154     QCOMPARE(fw.locationEdit()->urls()[0], QStringLiteral("test.core"));
0155 
0156     // select 2nd XML filter
0157     fw.filterWidget()->setCurrentFilter("*.xml *.b|DocBook (.xml)");
0158     QCOMPARE(fw.currentFilter(), QStringLiteral("*.xml *.b"));
0159     QCOMPARE(fw.locationEdit()->urls()[0], QStringLiteral("test.xml"));
0160 }
0161 
0162 void KFileWidgetTest::testFocusOnLocationEdit()
0163 {
0164     KFileWidget fw(QUrl::fromLocalFile(QDir::homePath()));
0165     fw.show();
0166     fw.activateWindow();
0167     QVERIFY(QTest::qWaitForWindowActive(&fw));
0168 
0169     QWidget *label = findLocationLabel(&fw);
0170     QVERIFY(label);
0171     QVERIFY(label->hasFocus());
0172 }
0173 
0174 void KFileWidgetTest::testFocusOnLocationEditChangeDir()
0175 {
0176     KFileWidget fw(QUrl::fromLocalFile(QDir::homePath()));
0177     fw.setUrl(QUrl::fromLocalFile(QDir::tempPath()));
0178     fw.show();
0179     fw.activateWindow();
0180     QVERIFY(QTest::qWaitForWindowActive(&fw));
0181 
0182     QWidget *label = findLocationLabel(&fw);
0183     QVERIFY(label);
0184     QVERIFY(label->hasFocus());
0185 }
0186 
0187 void KFileWidgetTest::testFocusOnLocationEditChangeDir2()
0188 {
0189     KFileWidget fw(QUrl::fromLocalFile(QDir::homePath()));
0190     fw.show();
0191     fw.activateWindow();
0192     QVERIFY(QTest::qWaitForWindowActive(&fw));
0193 
0194     fw.setUrl(QUrl::fromLocalFile(QDir::tempPath()));
0195 
0196     QWidget *label = findLocationLabel(&fw);
0197     QVERIFY(label);
0198     QVERIFY(label->hasFocus());
0199 }
0200 
0201 void KFileWidgetTest::testFocusOnDirOps()
0202 {
0203     KFileWidget fw(QUrl::fromLocalFile(QDir::homePath()));
0204     fw.show();
0205     fw.activateWindow();
0206     QVERIFY(QTest::qWaitForWindowActive(&fw));
0207 
0208     const QList<KUrlNavigator *> nav = fw.findChildren<KUrlNavigator *>();
0209     QCOMPARE(nav.count(), 1);
0210     nav[0]->setFocus();
0211 
0212     fw.setUrl(QUrl::fromLocalFile(QDir::tempPath()));
0213 
0214     const QList<KDirOperator *> ops = fw.findChildren<KDirOperator *>();
0215     QCOMPARE(ops.count(), 1);
0216     QVERIFY(ops[0]->hasFocus());
0217 }
0218 
0219 void KFileWidgetTest::testGetStartUrl()
0220 {
0221     QString recentDirClass;
0222     QString outFileName;
0223     QUrl localUrl = KFileWidget::getStartUrl(QUrl(QStringLiteral("kfiledialog:///attachmentDir")), recentDirClass, outFileName);
0224     QCOMPARE(recentDirClass, QStringLiteral(":attachmentDir"));
0225     QCOMPARE(localUrl.toLocalFile(), QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
0226     QVERIFY(outFileName.isEmpty());
0227 
0228 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(5, 96)
0229     localUrl = KFileWidget::getStartUrl(QUrl(QStringLiteral("kfiledialog:///attachments/foo.txt?global")), recentDirClass, outFileName);
0230     QCOMPARE(recentDirClass, QStringLiteral("::attachments"));
0231 #else
0232     localUrl = KFileWidget::getStartUrl(QUrl(QStringLiteral("kfiledialog:///attachments/foo.txt")), recentDirClass, outFileName);
0233     QCOMPARE(recentDirClass, QStringLiteral(":attachments"));
0234 #endif
0235     QCOMPARE(localUrl.toLocalFile(), QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
0236     QCOMPARE(outFileName, QStringLiteral("foo.txt"));
0237 }
0238 
0239 void KFileWidgetTest::testSetSelection_data()
0240 {
0241     QTest::addColumn<QString>("baseDir");
0242     QTest::addColumn<QString>("selection");
0243     QTest::addColumn<QString>("expectedBaseDir");
0244     QTest::addColumn<QString>("expectedCurrentText");
0245 
0246     const QString baseDir = QDir::homePath();
0247     // A nice filename to detect URL encoding issues
0248     const QString fileName = QStringLiteral("some:fi#le");
0249 
0250     // Bug 369216, kdialog calls setSelection(path)
0251     QTest::newRow("path") << baseDir << baseDir + QLatin1Char('/') + fileName << baseDir << fileName;
0252     QTest::newRow("differentPath") << QDir::rootPath() << baseDir + QLatin1Char('/') + fileName << baseDir << fileName;
0253     // kdeplatformfiledialoghelper.cpp calls setSelection(URL as string)
0254     QTest::newRow("url") << baseDir << QUrl::fromLocalFile(baseDir + QLatin1Char('/') + fileName).toString() << baseDir << fileName;
0255     // What if someone calls setSelection(fileName)? That breaks, hence e70f8134a2b in plasma-integration.git
0256     QTest::newRow("filename") << baseDir << fileName << baseDir << fileName;
0257 }
0258 
0259 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(5, 33)
0260 void KFileWidgetTest::testSetSelection()
0261 {
0262     // GIVEN
0263     QFETCH(QString, baseDir);
0264     QFETCH(QString, selection);
0265     QFETCH(QString, expectedBaseDir);
0266     QFETCH(QString, expectedCurrentText);
0267     const QUrl baseUrl = QUrl::fromLocalFile(baseDir).adjusted(QUrl::StripTrailingSlash);
0268     const QUrl expectedBaseUrl = QUrl::fromLocalFile(expectedBaseDir);
0269 
0270     KFileWidget fw(baseUrl);
0271 
0272     // WHEN
0273     fw.setSelection(selection); // now deprecated, this test shows why ;)
0274 
0275     // THEN
0276     QCOMPARE(fw.baseUrl().adjusted(QUrl::StripTrailingSlash), expectedBaseUrl);
0277     // if (QByteArray(QTest::currentDataTag()) == "filename") {
0278     QEXPECT_FAIL("filename", "setSelection cannot work with filenames, bad API", Continue);
0279     //}
0280     QCOMPARE(fw.locationEdit()->currentText(), expectedCurrentText);
0281 }
0282 #endif
0283 
0284 void KFileWidgetTest::testSetSelectedUrl_data()
0285 {
0286     QTest::addColumn<QString>("baseDir");
0287     QTest::addColumn<QUrl>("selectionUrl");
0288     QTest::addColumn<QString>("expectedBaseDir");
0289     QTest::addColumn<QString>("expectedCurrentText");
0290 
0291     const QString baseDir = QDir::homePath();
0292     // A nice filename to detect URL encoding issues
0293     const QString fileName = QStringLiteral("some:fi#le");
0294     const QUrl fileUrl = QUrl::fromLocalFile(baseDir + QLatin1Char('/') + fileName);
0295 
0296     QTest::newRow("path") << baseDir << fileUrl << baseDir << fileName;
0297     QTest::newRow("differentPath") << QDir::rootPath() << fileUrl << baseDir << fileName;
0298     QTest::newRow("url") << baseDir << QUrl::fromLocalFile(baseDir + QLatin1Char('/') + fileName) << baseDir << fileName;
0299 
0300     QUrl relativeUrl;
0301     relativeUrl.setPath(fileName);
0302     QTest::newRow("filename") << baseDir << relativeUrl << baseDir << fileName;
0303 }
0304 
0305 void KFileWidgetTest::testSetSelectedUrl()
0306 {
0307     // GIVEN
0308     QFETCH(QString, baseDir);
0309     QFETCH(QUrl, selectionUrl);
0310     QFETCH(QString, expectedBaseDir);
0311     QFETCH(QString, expectedCurrentText);
0312 
0313     const QUrl baseUrl = QUrl::fromLocalFile(baseDir).adjusted(QUrl::StripTrailingSlash);
0314     const QUrl expectedBaseUrl = QUrl::fromLocalFile(expectedBaseDir);
0315     KFileWidget fw(baseUrl);
0316 
0317     // WHEN
0318     fw.setSelectedUrl(selectionUrl);
0319 
0320     // THEN
0321     QCOMPARE(fw.baseUrl().adjusted(QUrl::StripTrailingSlash), expectedBaseUrl);
0322     QCOMPARE(fw.locationEdit()->currentText(), expectedCurrentText);
0323 }
0324 
0325 void KFileWidgetTest::testPreserveFilenameWhileNavigating() // bug 418711
0326 {
0327     // GIVEN
0328     const QUrl url = QUrl::fromLocalFile(QDir::homePath());
0329     KFileWidget fw(url);
0330     fw.setOperationMode(KFileWidget::Saving);
0331     fw.setMode(KFile::File);
0332     QString baseDir = QDir::homePath();
0333     if (baseDir.endsWith('/')) {
0334         baseDir.chop(1);
0335     }
0336     const QString fileName = QStringLiteral("somefi#le");
0337     const QUrl fileUrl = QUrl::fromLocalFile(baseDir + QLatin1Char('/') + fileName);
0338     fw.setSelectedUrl(fileUrl);
0339     const QUrl baseUrl = QUrl::fromLocalFile(baseDir);
0340     QCOMPARE(fw.baseUrl().adjusted(QUrl::StripTrailingSlash), baseUrl);
0341     QCOMPARE(fw.locationEdit()->currentText(), fileName);
0342 
0343     // WHEN
0344     fw.dirOperator()->cdUp();
0345 
0346     // THEN
0347     QCOMPARE(fw.baseUrl().adjusted(QUrl::StripTrailingSlash), baseUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash));
0348     QCOMPARE(fw.locationEdit()->currentText(), fileName); // unchanged
0349 }
0350 
0351 void KFileWidgetTest::testEnterUrl_data()
0352 {
0353     QTest::addColumn<QUrl>("expectedUrl");
0354 
0355     // Check if the root urls are well transformed into themself, otherwise
0356     // when going up from file:///home/ it will become file:///home/user
0357     QTest::newRow("file") << QUrl::fromLocalFile("/");
0358     QTest::newRow("trash") << QUrl("trash:/");
0359     QTest::newRow("sftp") << QUrl("sftp://127.0.0.1/");
0360 }
0361 
0362 void KFileWidgetTest::testEnterUrl()
0363 {
0364     // GIVEN
0365     QFETCH(QUrl, expectedUrl);
0366 
0367     // WHEN
0368     // These lines are copied from src/filewidgets/kfilewidget.cpp
0369     // void KFileWidgetPrivate::_k_enterUrl(const QUrl &url)
0370     QUrl u(expectedUrl);
0371     Utils::appendSlashToPath(u);
0372     // THEN
0373     QVERIFY(u.isValid());
0374     QCOMPARE(u, expectedUrl);
0375 }
0376 
0377 void KFileWidgetTest::testSetFilterForSave_data()
0378 {
0379     QTest::addColumn<QString>("fileName");
0380     QTest::addColumn<QString>("filter");
0381     QTest::addColumn<QString>("expectedCurrentText");
0382     QTest::addColumn<QString>("expectedSelectedFileName");
0383 
0384     const QString filter = QStringLiteral("*.txt|Text files\n*.HTML|HTML files");
0385 
0386     QTest::newRow("some.txt") << "some.txt" << filter << QStringLiteral("some.txt") << QStringLiteral("some.txt");
0387 
0388     // If an application provides a name without extension, then the
0389     // displayed name will not receive an extension. It will however be
0390     // appended when the dialog is closed.
0391     QTest::newRow("extensionless name") << "some" << filter << QStringLiteral("some") << QStringLiteral("some.txt");
0392 
0393     // If the file literally exists, then no new extension will be appended.
0394     QTest::newRow("existing file") << "README" << filter << QStringLiteral("README") << QStringLiteral("README");
0395 
0396     // XXX perhaps the "extension" should not be modified when it does not
0397     // match any of the existing types? Should "some.2019.txt" be expected?
0398     QTest::newRow("some.2019") << "some.2019" << filter << QStringLiteral("some.txt") << QStringLiteral("some.txt");
0399 
0400     // XXX be smarter and do not change the extension if one of the other
0401     // filters match. Should "some.html" be expected?
0402     QTest::newRow("some.html") << "some.html" << filter << QStringLiteral("some.txt") << QStringLiteral("some.txt");
0403 }
0404 
0405 void KFileWidgetTest::testSetFilterForSave()
0406 {
0407     QFETCH(QString, fileName);
0408     QFETCH(QString, filter);
0409     QFETCH(QString, expectedCurrentText);
0410     QFETCH(QString, expectedSelectedFileName);
0411 
0412     // Use a temporary directory since the presence of existing files
0413     // influences whether an extension is automatically appended.
0414     QTemporaryDir tempDir;
0415     const QUrl dirUrl = QUrl::fromLocalFile(tempDir.path());
0416     const QUrl fileUrl = QUrl::fromLocalFile(tempDir.filePath(fileName));
0417     const QUrl expectedFileUrl = QUrl::fromLocalFile(tempDir.filePath(expectedSelectedFileName));
0418     createTestFile(tempDir.filePath("README"));
0419 
0420     KFileWidget fw(dirUrl);
0421     fw.setOperationMode(KFileWidget::Saving);
0422     fw.setSelectedUrl(fileUrl);
0423     // Calling setFilter has side-effects and changes the file name.
0424     fw.setFilter(filter);
0425 
0426     // Verify the expected populated name.
0427     QCOMPARE(fw.baseUrl().adjusted(QUrl::StripTrailingSlash), dirUrl);
0428     QCOMPARE(fw.locationEdit()->currentText(), expectedCurrentText);
0429 
0430     // QFileDialog ends up calling KDEPlatformFileDialog::selectedFiles()
0431     // which calls KFileWidget::selectedUrls().
0432     // Accept the filename to ensure that a filename is selected.
0433     connect(&fw, &KFileWidget::accepted, &fw, &KFileWidget::accept);
0434     QTest::keyClick(fw.locationEdit(), Qt::Key_Return);
0435     QList<QUrl> urls = fw.selectedUrls();
0436     QCOMPARE(urls.size(), 1);
0437     QCOMPARE(urls[0], expectedFileUrl);
0438 }
0439 
0440 void KFileWidgetTest::testExtensionForSave_data()
0441 {
0442     QTest::addColumn<QString>("fileName");
0443     QTest::addColumn<QString>("filter");
0444     QTest::addColumn<QString>("expectedCurrentText");
0445     QTest::addColumn<QString>("expectedSelectedFileName");
0446 
0447     const QString filter = QStringLiteral("*.txt *.text|Text files\n*.HTML|HTML files");
0448 
0449     QTest::newRow("some.txt") << "some.txt" << filter << QStringLiteral("some.txt") << QStringLiteral("some.txt");
0450 
0451     // If an application provides a name without extension, then the
0452     // displayed name will not receive an extension. It will however be
0453     // appended when the dialog is closed.
0454     QTest::newRow("extensionless name") << "some" << filter << QStringLiteral("some") << QStringLiteral("some.txt");
0455     QTest::newRow("extensionless name") << "some.with_dot" << filter << QStringLiteral("some.with_dot") << QStringLiteral("some.with_dot.txt");
0456     QTest::newRow("extensionless name") << "some.with.dots" << filter << QStringLiteral("some.with.dots") << QStringLiteral("some.with.dots.txt");
0457 
0458     // If the file literally exists, then no new extension will be appended.
0459     QTest::newRow("existing file") << "README" << filter << QStringLiteral("README") << QStringLiteral("README");
0460 }
0461 
0462 void KFileWidgetTest::testExtensionForSave()
0463 {
0464     QFETCH(QString, fileName);
0465     QFETCH(QString, filter);
0466     QFETCH(QString, expectedCurrentText);
0467     QFETCH(QString, expectedSelectedFileName);
0468 
0469     // Use a temporary directory since the presence of existing files
0470     // influences whether an extension is automatically appended.
0471     QTemporaryDir tempDir;
0472     const QUrl dirUrl = QUrl::fromLocalFile(tempDir.path());
0473     const QUrl fileUrl = QUrl::fromLocalFile(tempDir.filePath(fileName));
0474     const QUrl expectedFileUrl = QUrl::fromLocalFile(tempDir.filePath(expectedSelectedFileName));
0475     createTestFile(tempDir.filePath("README"));
0476 
0477     KFileWidget fw(dirUrl);
0478     fw.setOperationMode(KFileWidget::Saving);
0479     // Calling setFilter has side-effects and changes the file name.
0480     // The difference to testSetFilterForSave is that the filter is already set before the fileUrl
0481     // is set, and will not be changed after.
0482     fw.setFilter(filter);
0483     fw.setSelectedUrl(fileUrl);
0484 
0485     // Verify the expected populated name.
0486     QCOMPARE(fw.baseUrl().adjusted(QUrl::StripTrailingSlash), dirUrl);
0487     QCOMPARE(fw.locationEdit()->currentText(), expectedCurrentText);
0488 
0489     // QFileDialog ends up calling KDEPlatformFileDialog::selectedFiles()
0490     // which calls KFileWidget::selectedUrls().
0491     // Accept the filename to ensure that a filename is selected.
0492     connect(&fw, &KFileWidget::accepted, &fw, &KFileWidget::accept);
0493     QTest::keyClick(fw.locationEdit(), Qt::Key_Return);
0494     QList<QUrl> urls = fw.selectedUrls();
0495     QCOMPARE(urls.size(), 1);
0496     QCOMPARE(urls[0], expectedFileUrl);
0497 }
0498 
0499 void KFileWidgetTest::testFilterChange()
0500 {
0501     QTemporaryDir tempDir;
0502     createTestFile(tempDir.filePath("some.c"));
0503     bool created = QDir(tempDir.path()).mkdir("directory");
0504     Q_ASSERT(created);
0505 
0506     KFileWidget fw(QUrl::fromLocalFile(tempDir.path()));
0507     fw.setOperationMode(KFileWidget::Saving);
0508     fw.setSelectedUrl(QUrl::fromLocalFile(tempDir.filePath("some.txt")));
0509     fw.setFilter("*.txt|Txt\n*.c|C");
0510 
0511     // Initial filename.
0512     QCOMPARE(fw.locationEdit()->currentText(), QStringLiteral("some.txt"));
0513     QCOMPARE(fw.filterWidget()->currentFilter(), QStringLiteral("*.txt"));
0514 
0515     // Select type with an existing file.
0516     fw.filterWidget()->setCurrentFilter("*.c|C");
0517     QCOMPARE(fw.locationEdit()->currentText(), QStringLiteral("some.c"));
0518     QCOMPARE(fw.filterWidget()->currentFilter(), QStringLiteral("*.c"));
0519 
0520     // Do not update extension if the current selection is a directory.
0521     fw.setSelectedUrl(QUrl::fromLocalFile(tempDir.filePath("directory")));
0522     fw.filterWidget()->setCurrentFilter("*.txt|Txt");
0523     QCOMPARE(fw.locationEdit()->currentText(), QStringLiteral("directory"));
0524     QCOMPARE(fw.filterWidget()->currentFilter(), QStringLiteral("*.txt"));
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"