/* This file is part of the KIO framework tests SPDX-FileCopyrightText: 2016 Albert Astals Cid SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include "kfilewidget.h" #include #include #include #include #include #include #include #include "../utils_p.h" #include "kiotesthelper.h" // createTestFile #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_LOGGING_CATEGORY(KIO_KFILEWIDGETS_FW) Q_LOGGING_CATEGORY(KIO_KFILEWIDGETS_FW, "kf.kio.kfilewidgets.kfilewidget", QtInfoMsg) static QWidget *findLocationLabel(QWidget *parent) { const QList labels = parent->findChildren(); for (QLabel *label : labels) { if (label->text() == i18n("&Name:") || label->text() == i18n("Name:")) { return label->buddy(); } } Q_ASSERT(false); return nullptr; } /** * Unit test for KFileWidget */ class KFileWidgetTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testFilterCombo(); void testFocusOnLocationEdit(); void testFocusOnLocationEditChangeDir(); void testFocusOnLocationEditChangeDir2(); void testFocusOnDirOps(); void testGetStartUrl(); void testSetSelection_data(); void testSetSelectedUrl_data(); void testSetSelectedUrl(); void testPreserveFilenameWhileNavigating(); void testEnterUrl_data(); void testEnterUrl(); void testSetFilterForSave_data(); void testSetFilterForSave(); void testExtensionForSave_data(); void testExtensionForSave(); void testFilterChange(); void testDropFile_data(); void testDropFile(); void testCreateNestedNewFolders(); void testTokenize_data(); void testTokenize(); void testTokenizeForSave_data(); void testTokenizeForSave(); }; void KFileWidgetTest::initTestCase() { QStandardPaths::setTestModeEnabled(true); QVERIFY(QDir::homePath() != QDir::tempPath()); } void KFileWidgetTest::testFilterCombo() { KFileWidget fw(QUrl(QStringLiteral("kfiledialog:///SaveDialog")), nullptr); fw.setOperationMode(KFileWidget::Saving); fw.setMode(KFile::File); const KFileFilter wordFilter = KFileFilter::fromFilterString("*.xml *.a|Word 2003 XML (.xml)").first(); const KFileFilter odtFilter = KFileFilter::fromFilterString("*.odt|ODF Text Document (.odt)").first(); const KFileFilter docBookFilter = KFileFilter::fromFilterString("*.xml *.b|DocBook (.xml)").first(); const KFileFilter rawFilter = KFileFilter::fromFilterString("*|Raw (*)").first(); fw.setFilters({wordFilter, odtFilter, docBookFilter, rawFilter}); // default filter is selected QCOMPARE(fw.currentFilter(), wordFilter); // setUrl runs with blocked signals, so use setUrls. // auto-select ODT filter via filename fw.locationEdit()->setUrls(QStringList(QStringLiteral("test.odt"))); QCOMPARE(fw.currentFilter(), odtFilter); QCOMPARE(fw.locationEdit()->urls()[0], QStringLiteral("test.odt")); // select 2nd duplicate XML filter (see bug 407642) fw.filterWidget()->setCurrentFilter(docBookFilter); QCOMPARE(fw.currentFilter(), docBookFilter); // when editing the filter, there is delay to avoid refreshing the KDirOperator after each keypress QTest::qWait(350); QCOMPARE(fw.locationEdit()->urls()[0], QStringLiteral("test.xml")); // keep filter after file change with same extension fw.locationEdit()->setUrls(QStringList(QStringLiteral("test2.xml"))); QCOMPARE(fw.currentFilter(), docBookFilter); QCOMPARE(fw.locationEdit()->urls()[0], QStringLiteral("test2.xml")); // back to the non-xml / ODT filter fw.locationEdit()->setUrls(QStringList(QStringLiteral("test.odt"))); QCOMPARE(fw.currentFilter(), odtFilter); QCOMPARE(fw.locationEdit()->urls()[0], QStringLiteral("test.odt")); // auto-select 1st XML filter fw.locationEdit()->setUrls(QStringList(QStringLiteral("test.xml"))); QCOMPARE(fw.currentFilter(), wordFilter); QCOMPARE(fw.locationEdit()->urls()[0], QStringLiteral("test.xml")); // select Raw '*' filter fw.filterWidget()->setCurrentFilter(rawFilter); QCOMPARE(fw.currentFilter(), rawFilter); QCOMPARE(fw.locationEdit()->urls()[0], QStringLiteral("test.xml")); // keep Raw '*' filter with matching file extension fw.locationEdit()->setUrls(QStringList(QStringLiteral("test.odt"))); QCOMPARE(fw.currentFilter(), rawFilter); QCOMPARE(fw.locationEdit()->urls()[0], QStringLiteral("test.odt")); // keep Raw '*' filter with non-matching file extension fw.locationEdit()->setUrls(QStringList(QStringLiteral("test.core"))); QCOMPARE(fw.currentFilter(), rawFilter); QCOMPARE(fw.locationEdit()->urls()[0], QStringLiteral("test.core")); // select 2nd XML filter fw.filterWidget()->setCurrentFilter(docBookFilter); QCOMPARE(fw.currentFilter(), docBookFilter); // when editing the filter, there is delay to avoid refreshing the KDirOperator after each keypress QTest::qWait(350); QCOMPARE(fw.locationEdit()->urls()[0], QStringLiteral("test.xml")); } void KFileWidgetTest::testFocusOnLocationEdit() { if (KWindowSystem::isPlatformWayland()) { QSKIP("X11 only, activation issue"); return; } KFileWidget fw(QUrl::fromLocalFile(QDir::homePath())); fw.show(); fw.activateWindow(); QVERIFY(QTest::qWaitForWindowActive(&fw)); QWidget *label = findLocationLabel(&fw); QVERIFY(label); QVERIFY(label->hasFocus()); } void KFileWidgetTest::testFocusOnLocationEditChangeDir() { if (KWindowSystem::isPlatformWayland()) { QSKIP("X11 only, activation issue"); return; } KFileWidget fw(QUrl::fromLocalFile(QDir::homePath())); fw.setUrl(QUrl::fromLocalFile(QDir::tempPath())); fw.show(); fw.activateWindow(); QVERIFY(QTest::qWaitForWindowActive(&fw)); QWidget *label = findLocationLabel(&fw); QVERIFY(label); QVERIFY(label->hasFocus()); } void KFileWidgetTest::testFocusOnLocationEditChangeDir2() { if (KWindowSystem::isPlatformWayland()) { QSKIP("X11 only, activation issue"); return; } KFileWidget fw(QUrl::fromLocalFile(QDir::homePath())); fw.show(); fw.activateWindow(); QVERIFY(QTest::qWaitForWindowActive(&fw)); fw.setUrl(QUrl::fromLocalFile(QDir::tempPath())); QWidget *label = findLocationLabel(&fw); QVERIFY(label); QVERIFY(label->hasFocus()); } void KFileWidgetTest::testFocusOnDirOps() { if (KWindowSystem::isPlatformWayland()) { QSKIP("X11 only, activation issue"); return; } KFileWidget fw(QUrl::fromLocalFile(QDir::homePath())); fw.show(); fw.activateWindow(); QVERIFY(QTest::qWaitForWindowActive(&fw)); const QList nav = fw.findChildren(); QCOMPARE(nav.count(), 1); nav[0]->setFocus(); fw.setUrl(QUrl::fromLocalFile(QDir::tempPath())); const QList ops = fw.findChildren(); QCOMPARE(ops.count(), 1); QVERIFY(ops[0]->hasFocus()); } void KFileWidgetTest::testGetStartUrl() { QString recentDirClass; QString outFileName; QUrl localUrl = KFileWidget::getStartUrl(QUrl(QStringLiteral("kfiledialog:///attachmentDir")), recentDirClass, outFileName); QCOMPARE(recentDirClass, QStringLiteral(":attachmentDir")); QCOMPARE(localUrl.toLocalFile(), QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); QVERIFY(outFileName.isEmpty()); localUrl = KFileWidget::getStartUrl(QUrl(QStringLiteral("kfiledialog:///attachments/foo.txt")), recentDirClass, outFileName); QCOMPARE(recentDirClass, QStringLiteral(":attachments")); QCOMPARE(localUrl.toLocalFile(), QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); QCOMPARE(outFileName, QStringLiteral("foo.txt")); } void KFileWidgetTest::testSetSelection_data() { QTest::addColumn("baseDir"); QTest::addColumn("selection"); QTest::addColumn("expectedBaseDir"); QTest::addColumn("expectedCurrentText"); const QString baseDir = QDir::homePath(); // A nice filename to detect URL encoding issues const QString fileName = QStringLiteral("some:fi#le"); // Bug 369216, kdialog calls setSelection(path) QTest::newRow("path") << baseDir << baseDir + QLatin1Char('/') + fileName << baseDir << fileName; QTest::newRow("differentPath") << QDir::rootPath() << baseDir + QLatin1Char('/') + fileName << baseDir << fileName; // kdeplatformfiledialoghelper.cpp calls setSelection(URL as string) QTest::newRow("url") << baseDir << QUrl::fromLocalFile(baseDir + QLatin1Char('/') + fileName).toString() << baseDir << fileName; // What if someone calls setSelection(fileName)? That breaks, hence e70f8134a2b in plasma-integration.git QTest::newRow("filename") << baseDir << fileName << baseDir << fileName; } void KFileWidgetTest::testSetSelectedUrl_data() { QTest::addColumn("baseDir"); QTest::addColumn("selectionUrl"); QTest::addColumn("expectedBaseDir"); QTest::addColumn("expectedCurrentText"); const QString baseDir = QDir::homePath(); // A nice filename to detect URL encoding issues const QString fileName = QStringLiteral("some:fi#le"); const QUrl fileUrl = QUrl::fromLocalFile(baseDir + QLatin1Char('/') + fileName); QTest::newRow("path") << baseDir << fileUrl << baseDir << fileName; QTest::newRow("differentPath") << QDir::rootPath() << fileUrl << baseDir << fileName; QTest::newRow("url") << baseDir << QUrl::fromLocalFile(baseDir + QLatin1Char('/') + fileName) << baseDir << fileName; QUrl relativeUrl; relativeUrl.setPath(fileName); QTest::newRow("filename") << baseDir << relativeUrl << baseDir << fileName; } void KFileWidgetTest::testSetSelectedUrl() { // GIVEN QFETCH(QString, baseDir); QFETCH(QUrl, selectionUrl); QFETCH(QString, expectedBaseDir); QFETCH(QString, expectedCurrentText); const QUrl baseUrl = QUrl::fromLocalFile(baseDir).adjusted(QUrl::StripTrailingSlash); const QUrl expectedBaseUrl = QUrl::fromLocalFile(expectedBaseDir); KFileWidget fw(baseUrl); // WHEN fw.setSelectedUrl(selectionUrl); // THEN QCOMPARE(fw.baseUrl().adjusted(QUrl::StripTrailingSlash), expectedBaseUrl); QCOMPARE(fw.locationEdit()->currentText(), expectedCurrentText); } void KFileWidgetTest::testPreserveFilenameWhileNavigating() // bug 418711 { // GIVEN const QUrl url = QUrl::fromLocalFile(QDir::homePath()); KFileWidget fw(url); fw.setOperationMode(KFileWidget::Saving); fw.setMode(KFile::File); QString baseDir = QDir::homePath(); if (baseDir.endsWith('/')) { baseDir.chop(1); } const QString fileName = QStringLiteral("somefi#le"); const QUrl fileUrl = QUrl::fromLocalFile(baseDir + QLatin1Char('/') + fileName); fw.setSelectedUrl(fileUrl); const QUrl baseUrl = QUrl::fromLocalFile(baseDir); QCOMPARE(fw.baseUrl().adjusted(QUrl::StripTrailingSlash), baseUrl); QCOMPARE(fw.locationEdit()->currentText(), fileName); // WHEN fw.dirOperator()->cdUp(); // THEN QCOMPARE(fw.baseUrl().adjusted(QUrl::StripTrailingSlash), baseUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash)); QCOMPARE(fw.locationEdit()->currentText(), fileName); // unchanged } void KFileWidgetTest::testEnterUrl_data() { QTest::addColumn("expectedUrl"); // Check if the root urls are well transformed into themself, otherwise // when going up from file:///home/ it will become file:///home/user QTest::newRow("file") << QUrl::fromLocalFile("/"); QTest::newRow("trash") << QUrl("trash:/"); QTest::newRow("sftp") << QUrl("sftp://127.0.0.1/"); } void KFileWidgetTest::testEnterUrl() { // GIVEN QFETCH(QUrl, expectedUrl); // WHEN // These lines are copied from src/filewidgets/kfilewidget.cpp // void KFileWidgetPrivate::_k_enterUrl(const QUrl &url) QUrl u(expectedUrl); Utils::appendSlashToPath(u); // THEN QVERIFY(u.isValid()); QCOMPARE(u, expectedUrl); } void KFileWidgetTest::testSetFilterForSave_data() { QTest::addColumn("fileName"); QTest::addColumn("filter"); QTest::addColumn("expectedCurrentText"); QTest::addColumn("expectedSelectedFileName"); const QString filter = QStringLiteral("*.txt|Text files\n*.HTML|HTML files"); QTest::newRow("some.txt") << "some.txt" << filter << QStringLiteral("some.txt") << QStringLiteral("some.txt"); // If an application provides a name without extension, then the // displayed name will not receive an extension. It will however be // appended when the dialog is closed. QTest::newRow("extensionless name") << "some" << filter << QStringLiteral("some") << QStringLiteral("some.txt"); // If the file literally exists, then no new extension will be appended. QTest::newRow("existing file") << "README" << filter << QStringLiteral("README") << QStringLiteral("README"); // XXX perhaps the "extension" should not be modified when it does not // match any of the existing types? Should "some.2019.txt" be expected? QTest::newRow("some.2019") << "some.2019" << filter << QStringLiteral("some.txt") << QStringLiteral("some.txt"); // XXX be smarter and do not change the extension if one of the other // filters match. Should "some.html" be expected? QTest::newRow("some.html") << "some.html" << filter << QStringLiteral("some.txt") << QStringLiteral("some.txt"); } void KFileWidgetTest::testSetFilterForSave() { QFETCH(QString, fileName); QFETCH(QString, filter); QFETCH(QString, expectedCurrentText); QFETCH(QString, expectedSelectedFileName); // Use a temporary directory since the presence of existing files // influences whether an extension is automatically appended. QTemporaryDir tempDir; const QUrl dirUrl = QUrl::fromLocalFile(tempDir.path()); const QUrl fileUrl = QUrl::fromLocalFile(tempDir.filePath(fileName)); const QUrl expectedFileUrl = QUrl::fromLocalFile(tempDir.filePath(expectedSelectedFileName)); createTestFile(tempDir.filePath("README")); KFileWidget fw(dirUrl); fw.setOperationMode(KFileWidget::Saving); fw.setSelectedUrl(fileUrl); // Calling setFilter has side-effects and changes the file name. fw.setFilters(KFileFilter::fromFilterString(filter)); // Verify the expected populated name. QCOMPARE(fw.baseUrl().adjusted(QUrl::StripTrailingSlash), dirUrl); QCOMPARE(fw.locationEdit()->currentText(), expectedCurrentText); // QFileDialog ends up calling KDEPlatformFileDialog::selectedFiles() // which calls KFileWidget::selectedUrls(). // Accept the filename to ensure that a filename is selected. connect(&fw, &KFileWidget::accepted, &fw, &KFileWidget::accept); QTest::keyClick(fw.locationEdit(), Qt::Key_Return); QList urls = fw.selectedUrls(); QCOMPARE(urls.size(), 1); QCOMPARE(urls[0], expectedFileUrl); } void KFileWidgetTest::testExtensionForSave_data() { QTest::addColumn("fileName"); QTest::addColumn("filter"); QTest::addColumn("expectedCurrentText"); QTest::addColumn("expectedSelectedFileName"); const QString filter = QStringLiteral("*.txt *.text|Text files\n*.HTML|HTML files"); QTest::newRow("some.txt") << "some.txt" << filter << QStringLiteral("some.txt") << QStringLiteral("some.txt"); // If an application provides a name without extension, then the // displayed name will not receive an extension. It will however be // appended when the dialog is closed. QTest::newRow("extensionless name") << "some" << filter << QStringLiteral("some") << QStringLiteral("some.txt"); QTest::newRow("extensionless name") << "some.with_dot" << filter << QStringLiteral("some.with_dot") << QStringLiteral("some.with_dot.txt"); QTest::newRow("extensionless name") << "some.with.dots" << filter << QStringLiteral("some.with.dots") << QStringLiteral("some.with.dots.txt"); // If the file literally exists, then no new extension will be appended. QTest::newRow("existing file") << "README" << filter << QStringLiteral("README") << QStringLiteral("README"); } void KFileWidgetTest::testExtensionForSave() { QFETCH(QString, fileName); QFETCH(QString, filter); QFETCH(QString, expectedCurrentText); QFETCH(QString, expectedSelectedFileName); // Use a temporary directory since the presence of existing files // influences whether an extension is automatically appended. QTemporaryDir tempDir; const QUrl dirUrl = QUrl::fromLocalFile(tempDir.path()); const QUrl fileUrl = QUrl::fromLocalFile(tempDir.filePath(fileName)); const QUrl expectedFileUrl = QUrl::fromLocalFile(tempDir.filePath(expectedSelectedFileName)); createTestFile(tempDir.filePath("README")); KFileWidget fw(dirUrl); fw.setOperationMode(KFileWidget::Saving); // Calling setFilter has side-effects and changes the file name. // The difference to testSetFilterForSave is that the filter is already set before the fileUrl // is set, and will not be changed after. fw.setFilters(KFileFilter::fromFilterString(filter)); fw.setSelectedUrl(fileUrl); // Verify the expected populated name. QCOMPARE(fw.baseUrl().adjusted(QUrl::StripTrailingSlash), dirUrl); QCOMPARE(fw.locationEdit()->currentText(), expectedCurrentText); // QFileDialog ends up calling KDEPlatformFileDialog::selectedFiles() // which calls KFileWidget::selectedUrls(). // Accept the filename to ensure that a filename is selected. connect(&fw, &KFileWidget::accepted, &fw, &KFileWidget::accept); QTest::keyClick(fw.locationEdit(), Qt::Key_Return); QList urls = fw.selectedUrls(); QCOMPARE(urls.size(), 1); QCOMPARE(urls[0], expectedFileUrl); } void KFileWidgetTest::testFilterChange() { QTemporaryDir tempDir; createTestFile(tempDir.filePath("some.c")); bool created = QDir(tempDir.path()).mkdir("directory"); Q_ASSERT(created); KFileWidget fw(QUrl::fromLocalFile(tempDir.path())); fw.setOperationMode(KFileWidget::Saving); fw.setSelectedUrl(QUrl::fromLocalFile(tempDir.filePath("some.txt"))); const QList filters = KFileFilter::fromFilterString("*.txt|Txt\n*.c|C"); fw.setFilters(filters); // Initial filename. QCOMPARE(fw.locationEdit()->currentText(), QStringLiteral("some.txt")); QCOMPARE(fw.filterWidget()->currentFilter(), filters[0]); // Select type with an existing file. fw.filterWidget()->setCurrentFilter(filters[1]); // when editing the filter, there is delay to avoid refreshing the KDirOperator after each keypress QTest::qWait(350); QCOMPARE(fw.locationEdit()->currentText(), QStringLiteral("some.c")); QCOMPARE(fw.filterWidget()->currentFilter(), filters[1]); // Do not update extension if the current selection is a directory. fw.setSelectedUrl(QUrl::fromLocalFile(tempDir.filePath("directory"))); fw.filterWidget()->setCurrentFilter(filters[0]); QCOMPARE(fw.locationEdit()->currentText(), QStringLiteral("directory")); QCOMPARE(fw.filterWidget()->currentFilter(), filters[0]); // The user types something into the combobox. fw.filterWidget()->setCurrentText("qml"); QSignalSpy filterChangedSpy(&fw, &KFileWidget::filterChanged); filterChangedSpy.wait(); QVERIFY(filterChangedSpy.count()); // Plain text is automatically upgraded to wildcard syntax QCOMPARE(fw.dirOperator()->nameFilter(), "*qml*"); // But existing wildcards are left intact fw.filterWidget()->setCurrentText("*.md"); filterChangedSpy.wait(); QVERIFY(filterChangedSpy.count()); QCOMPARE(fw.dirOperator()->nameFilter(), "*.md"); fw.filterWidget()->setCurrentText("[ab]c"); filterChangedSpy.wait(); QVERIFY(filterChangedSpy.count()); QCOMPARE(fw.dirOperator()->nameFilter(), "[ab]c"); fw.filterWidget()->setCurrentText("b?c"); filterChangedSpy.wait(); QVERIFY(filterChangedSpy.count()); QCOMPARE(fw.dirOperator()->nameFilter(), "b?c"); } void KFileWidgetTest::testDropFile_data() { QTest::addColumn("dir"); QTest::addColumn("fileName"); QTest::addColumn("expectedCurrentText"); QTest::newRow("some.txt") << "" << "some.txt" << "some.txt"; QTest::newRow("subdir/some.txt") << "subdir" << "subdir/some.txt" << "some.txt"; } void KFileWidgetTest::testDropFile() { QFETCH(QString, dir); QFETCH(QString, fileName); QFETCH(QString, expectedCurrentText); // Use a temporary directory since the presence of existing files // influences whether an extension is automatically appended. QTemporaryDir tempDir; QUrl dirUrl = QUrl::fromLocalFile(tempDir.path()); const QUrl fileUrl = QUrl::fromLocalFile(tempDir.filePath(fileName)); if (!dir.isEmpty()) { createTestDirectory(tempDir.filePath(dir)); dirUrl = QUrl::fromLocalFile(tempDir.filePath(dir)); } createTestFile(tempDir.filePath(fileName)); KFileWidget fileWidget(QUrl::fromLocalFile(tempDir.path())); fileWidget.setOperationMode(KFileWidget::Saving); fileWidget.setMode(KFile::File); fileWidget.show(); QMimeData *mimeData = new QMimeData(); mimeData->setUrls(QList() << fileUrl); KDirLister *dirLister = fileWidget.dirOperator()->dirLister(); QSignalSpy spy(dirLister, qOverload<>(&KCoreDirLister::completed)); QAbstractItemView *view = fileWidget.dirOperator()->view(); QVERIFY(view); QDragEnterEvent event1(QPoint(), Qt::DropAction::MoveAction, mimeData, Qt::MouseButton::LeftButton, Qt::KeyboardModifier::NoModifier); QVERIFY(qApp->sendEvent(view->viewport(), &event1)); // Fake drop QDropEvent event(QPoint(), Qt::DropAction::MoveAction, mimeData, Qt::MouseButton::LeftButton, Qt::KeyboardModifier::NoModifier); QVERIFY(qApp->sendEvent(view->viewport(), &event)); if (!dir.isEmpty()) { // once we drop a file the dirlister scans the dir // wait for the completed signal from the dirlister QVERIFY(spy.wait()); } // Verify the expected populated name. QCOMPARE(fileWidget.baseUrl().adjusted(QUrl::StripTrailingSlash), dirUrl); QCOMPARE(fileWidget.locationEdit()->currentText(), expectedCurrentText); // QFileDialog ends up calling KDEPlatformFileDialog::selectedFiles() // which calls KFileWidget::selectedUrls(). // Accept the filename to ensure that a filename is selected. connect(&fileWidget, &KFileWidget::accepted, &fileWidget, &KFileWidget::accept); QTest::keyClick(fileWidget.locationEdit(), Qt::Key_Return); const QList urls = fileWidget.selectedUrls(); QCOMPARE(urls.size(), 1); QCOMPARE(urls[0], fileUrl); } void KFileWidgetTest::testCreateNestedNewFolders() { // when creating multiple nested new folders in the "save as" dialog, where folders are // created and entered, kdirlister would hit an assert (in reinsert()), bug 408801 QTemporaryDir tempDir; QVERIFY(tempDir.isValid()); const QString dir = tempDir.path(); const QUrl url = QUrl::fromLocalFile(dir); KFileWidget fw(url); fw.setOperationMode(KFileWidget::Saving); fw.setMode(KFile::File); QString currentPath = dir; // create the nested folders for (int i = 1; i < 6; ++i) { fw.dirOperator()->mkdir(); QDialog *dialog; // QTRY_ because a NameFinderJob could be running and the dialog will be shown when // it finishes. QTRY_VERIFY(dialog = fw.findChild()); QLineEdit *lineEdit = dialog->findChild(); QVERIFY(lineEdit); const QString name = QStringLiteral("folder%1").arg(i); lineEdit->setText(name); // simulate the time the user will take to type the new folder name QTest::qWait(1000); dialog->accept(); currentPath += QLatin1Char('/') + name; // Wait till the filewidget changes to the new folder QTRY_COMPARE(fw.baseUrl().adjusted(QUrl::StripTrailingSlash).toLocalFile(), currentPath); } } void KFileWidgetTest::testTokenize_data() { // Real filename (as in how they are stored in the fs) QTest::addColumn("fileNames"); // Escaped value of the text-box in the dialog QTest::addColumn("expectedCurrentText"); QTest::newRow("simple") << QStringList{"test2"} << QString("test2"); // When a single file with space is selected, it is _not_ quoted ... QTest::newRow("space-single-file") << QStringList{"test space"} << QString("test space"); // However, when multiple files are selected, they are quoted QTest::newRow("space-multi-file") << QStringList{"test space", "test2"} << QString("\"test space\" \"test2\""); // All quotes in names should be escaped, however since this is a single // file, the whole name will not be escaped. QTest::newRow("quote-single-file") << QStringList{"test\"quote"} << QString("test\\\"quote"); // Escape multiple files. Files should also be wrapped in "" // Note that we are also testing quote at the end of the name QTest::newRow("quote-multi-file") << QStringList{"test\"quote", "test2-quote\"", "test"} << QString("\"test\\\"quote\" \"test2-quote\\\"\" \"test\""); // Ok, enough with quotes... lets do some backslashes // Backslash literals in file names - Unix only case QTest::newRow("backslash-single-file") << QStringList{"test\\backslash"} << QString("test\\\\backslash"); QTest::newRow("backslash-multi-file") << QStringList{"test\\back\\slash", "test"} << QString("\"test\\\\back\\\\slash\" \"test\""); QTest::newRow("double-backslash-multi-file") << QStringList{"test\\\\back\\slash", "test"} << QString("\"test\\\\\\\\back\\\\slash\" \"test\""); QTest::newRow("double-backslash-end") << QStringList{"test\\\\"} << QString("test\\\\\\\\"); QTest::newRow("single-backslash-end") << QStringList{"some thing", "test\\"} << QString("\"some thing\" \"test\\\\\""); QTest::newRow("sharp") << QStringList{"some#thing"} << QString("some#thing"); // Filenames beginning with ':'; QDir::isAbsolutePath() always returns true // in that case, #322837 QTest::newRow("file-beginning-with-colon") << QStringList{":test2"} << QString{":test2"}; QTest::newRow("multiple-files-beginning-with-colon") << QStringList{":test space", ":test2"} << QString{"\":test space\" \":test2\""}; // # 473228 QTest::newRow("file-beginning-with-something-that-looks-like-a-url-scheme") << QStringList{"Hello: foo.txt"} << QString{"Hello: foo.txt"}; QTest::newRow("file-beginning-with-something-that-looks-like-a-file-url-scheme") << QStringList{"file: /foo.txt"} << QString{"file: /foo.txt"}; QTemporaryDir otherTempDir; otherTempDir.setAutoRemove(false); const auto testFile1Path = otherTempDir.filePath("test-1"); createTestFile(testFile1Path); const auto testFile2Path = otherTempDir.filePath("test-2"); createTestFile(testFile2Path); QTest::newRow("absolute-url-not-in-dir") << QStringList{"file://" + testFile1Path} << QString{"file://" + testFile1Path}; QTest::newRow("absolute-urls-not-in-dir") << QStringList{"file://" + testFile1Path, "file://" + testFile2Path} << QString{"\"file://" + testFile1Path + "\" \"file://" + testFile2Path + "\""}; auto expectedtestFile1Path = testFile1Path; expectedtestFile1Path = expectedtestFile1Path.remove(0, 1); auto expectedtestFile2Path = testFile2Path; expectedtestFile2Path = expectedtestFile2Path.remove(0, 1); QTest::newRow("absolute-url-not-in-dir-no-scheme") << QStringList{testFile1Path} << QString{testFile1Path}; QTest::newRow("absolute-urls-not-in-dir-no-scheme") << QStringList{testFile1Path, testFile2Path} << QString{"\"" + testFile1Path + "\" \"" + testFile2Path + "\""}; QTest::newRow("absolute-urls-not-in-dir-scheme-mixed") << QStringList{testFile1Path, "file://" + testFile2Path} << QString{"\"" + testFile1Path + "\" \"file://" + testFile2Path + "\""}; } void KFileWidgetTest::testTokenize() { // We will use setSelectedUrls([QUrl]) here in order to check correct // filename escaping. Afterwards we will accept() the dialog to confirm // correct result QFETCH(QStringList, fileNames); QFETCH(QString, expectedCurrentText); QTemporaryDir tempDir; const QString tempDirPath = tempDir.path(); const QUrl tempDirUrl = QUrl::fromLocalFile(tempDirPath); QList fileUrls; for (const auto &fileName : fileNames) { auto localUrl = QUrl(fileName); if (!localUrl.path().startsWith(QLatin1Char('/'))) { const QString filePath = tempDirPath + QLatin1Char('/') + fileName; localUrl = QUrl::fromLocalFile(filePath); } fileUrls.append(localUrl); qCDebug(KIO_KFILEWIDGETS_FW) << fileName << " => " << localUrl; } KFileWidget fw(tempDirUrl); fw.setOperationMode(KFileWidget::Opening); fw.setMode(KFile::Files); fw.setSelectedUrls(fileUrls); // Verify the expected populated name. QCOMPARE(fw.baseUrl().adjusted(QUrl::StripTrailingSlash), tempDirUrl); QCOMPARE(fw.locationEdit()->currentText(), expectedCurrentText); // QFileDialog ends up calling KDEPlatformFileDialog::selectedFiles() // which calls KFileWidget::selectedUrls(). // Accept the filename to ensure that a filename is selected. connect(&fw, &KFileWidget::accepted, &fw, &KFileWidget::accept); QTest::keyClick(fw.locationEdit(), Qt::Key_Return); const QList urls = fw.selectedUrls(); // We must have the same size as requested files QCOMPARE(urls.size(), fileNames.size()); for (auto &localUrl : fileUrls) { if (localUrl.scheme().isEmpty()) { localUrl.setScheme("file"); } } QCOMPARE(urls, fileUrls); } void KFileWidgetTest::testTokenizeForSave_data() { // Real filename (as in how they are stored in the fs) QTest::addColumn("fileName"); // Escaped value of the text-box in the dialog // Escaped cwd: Because setSelectedUrl is called in this // test, it actually sets the CWD to the dirname and only // keeps the filename displayed in the test QTest::addColumn("expectedSubFolder"); QTest::addColumn("expectedCurrentText"); QTest::newRow("save-simple") << QString{"test2"} << QString() << QString("test2"); // When a single file with space is selected, it is _not_ quoted ... QTest::newRow("save-space") << QString{"test space"} << QString() << QString("test space"); // All quotes in names should be escaped, however since this is a single // file, the whole name will not be escaped. QTest::newRow("save-quote") << QString{"test\"quote"} << QString() << QString("test\\\"quote"); // Ok, enough with quotes... lets do some backslashes // Backslash literals in file names - Unix only case QTest::newRow("save-backslash") << QString{"test\\backslash"} << QString() << QString("test\\\\backslash"); QTest::newRow("save-double-backslash") << QString{"test\\\\back\\slash"} << QString() << QString("test\\\\\\\\back\\\\slash"); QTest::newRow("save-double-backslash-end") << QString{"test\\\\"} << QString() << QString("test\\\\\\\\"); QTest::newRow("save-single-backslash-end") << QString{"test\\"} << QString() << QString("test\\\\"); QTest::newRow("save-sharp") << QString{"some#thing"} << QString() << QString("some#thing"); // Filenames beginning with ':'; QDir::isAbsolutePath() always returns true // in that case, #322837 QTest::newRow("save-file-beginning-with-colon") << QString{":test2"} << QString() << QString{":test2"}; // # 473228 QTest::newRow("save-save-file-beginning-with-something-that-looks-like-a-url-scheme") << QString{"Hello: foo.txt"} << QString() << QString{"Hello: foo.txt"}; QTemporaryDir otherTempDir; otherTempDir.setAutoRemove(false); const auto testFile1Path = otherTempDir.filePath("test-1"); createTestFile(testFile1Path); QTest::newRow("save-absolute-url-not-in-dir") << QString{"file://" + testFile1Path} << otherTempDir.path() << QString{"test-1"}; auto expectedtestFile1Path = testFile1Path; expectedtestFile1Path = expectedtestFile1Path.remove(0, 1); QTest::newRow("save-absolute-url-not-in-dir-no-scheme") << QString{testFile1Path} << otherTempDir.path() << QString{"test-1"}; } void KFileWidgetTest::testTokenizeForSave() { // We will use setSelectedUrls([QUrl]) here in order to check correct // filename escaping. Afterwards we will accept() the dialog to confirm // correct result // This test is similar to testTokenize but focuses on single-file // "save" operation. This follows a different code-path internally // and calls setSelectedUrl instead of setSelectedUrls. QFETCH(QString, fileName); QFETCH(QString, expectedSubFolder); QFETCH(QString, expectedCurrentText); QTemporaryDir tempDir; const QString tempDirPath = tempDir.path(); const QUrl tempDirUrl = QUrl::fromLocalFile(tempDirPath); auto fileUrl = QUrl(fileName); if (!fileUrl.path().startsWith(QLatin1Char('/'))) { const QString filePath = tempDirPath + QLatin1Char('/') + fileName; fileUrl = QUrl::fromLocalFile(filePath); } if (fileUrl.scheme().isEmpty()) { fileUrl.setScheme("file"); } qCDebug(KIO_KFILEWIDGETS_FW) << fileName << " => " << fileUrl; KFileWidget fw(tempDirUrl); fw.setOperationMode(KFileWidget::Saving); fw.setMode(KFile::File); fw.setSelectedUrl(fileUrl); // Verify the expected populated name. if (expectedSubFolder != "") { const QUrl expectedBaseUrl = tempDirUrl.resolved(QUrl::fromLocalFile(expectedSubFolder)); QCOMPARE(fw.baseUrl().adjusted(QUrl::StripTrailingSlash), expectedBaseUrl); } else { QCOMPARE(fw.baseUrl().adjusted(QUrl::StripTrailingSlash), tempDirUrl); } QCOMPARE(fw.locationEdit()->currentText(), expectedCurrentText); // QFileDialog ends up calling KDEPlatformFileDialog::selectedFiles() // which calls KFileWidget::selectedUrls(). // Accept the filename to ensure that a filename is selected. connect(&fw, &KFileWidget::accepted, &fw, &KFileWidget::accept); QTest::keyClick(fw.locationEdit(), Qt::Key_Return); const QList urls = fw.selectedUrls(); // We always only have one URL here QCOMPARE(urls.size(), 1); QCOMPARE(urls[0], fileUrl); } QTEST_MAIN(KFileWidgetTest) #include "kfilewidgettest.moc"