File indexing completed on 2024-12-01 09:52:39

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2012 David Faure <faure@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 <QTest>
0009 
0010 #include <KActionCollection>
0011 #include <KIO/StoredTransferJob>
0012 #include <KShell>
0013 #include <QDialog>
0014 #include <QLineEdit>
0015 #include <QMenu>
0016 #include <knameandurlinputdialog.h>
0017 #include <knewfilemenu.h>
0018 #include <kpropertiesdialog.h>
0019 
0020 #include <QPushButton>
0021 #include <QSignalSpy>
0022 #include <QStandardPaths>
0023 #include <QTemporaryDir>
0024 
0025 #ifdef Q_OS_UNIX
0026 #include <sys/stat.h>
0027 #include <sys/types.h>
0028 #endif
0029 
0030 #include <algorithm>
0031 
0032 class KNewFileMenuTest : public QObject
0033 {
0034     Q_OBJECT
0035 private Q_SLOTS:
0036     void initTestCase()
0037     {
0038         QStandardPaths::setTestModeEnabled(true);
0039 #ifdef Q_OS_UNIX
0040         m_umask = ::umask(0);
0041         ::umask(m_umask);
0042 #endif
0043         QVERIFY(m_tmpDir.isValid());
0044 
0045         // These have to be created here before KNewFileMenuSingleton is created,
0046         // otherwise they wouldn't be picked up
0047         QDir dir;
0048         m_xdgConfigDir = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
0049         QVERIFY(dir.mkpath(m_xdgConfigDir));
0050         QFile userDirs(m_xdgConfigDir + "/user-dirs.dirs");
0051         QVERIFY(userDirs.open(QIODevice::WriteOnly));
0052         const QString templDir = "XDG_TEMPLATES_DIR=\"" + m_xdgConfigDir + "/test-templates/\"\n";
0053         userDirs.write(templDir.toLocal8Bit());
0054         userDirs.close();
0055 
0056         // Different location than what KNewFileMenuPrivate::slotFillTemplates() checks by default
0057         const QString loc = m_xdgConfigDir + '/' + "test-templates/";
0058         QVERIFY(dir.mkpath(loc));
0059 
0060         QFile templ(m_xdgConfigDir + "/test-templates" + "/test-text.desktop");
0061         QVERIFY(templ.open(QIODevice::WriteOnly));
0062         const QByteArray contents =
0063             "[Desktop Entry]\n"
0064             "Name=Custom...\n"
0065             "Type=Link\n"
0066             "URL=TestTextFile.txt\n"
0067             "Icon=text-plain\n";
0068 
0069         templ.write(contents);
0070         templ.close();
0071     }
0072 
0073     void cleanupTestCase()
0074     {
0075         QFile::remove(m_xdgConfigDir + "/user-dirs.dirs");
0076         QDir(m_xdgConfigDir + "/test-templates").removeRecursively();
0077     }
0078 
0079     // Ensure that we can use storedPut() with a qrc file as input
0080     // similar to JobTest::storedPutIODeviceFile, but with a qrc file as input
0081     // (and here because jobtest doesn't link to KIO::FileWidgets, which has the qrc)
0082     void storedPutIODeviceQrcFile()
0083     {
0084         // Given a source (in a Qt resource file) and a destination file
0085         const QString src = QStringLiteral(":/kio5/newfile-templates/.source/HTMLFile.html");
0086         QVERIFY(QFile::exists(src));
0087         QFile srcFile(src);
0088         QVERIFY(srcFile.open(QIODevice::ReadOnly));
0089         const QString dest = m_tmpDir.path() + QStringLiteral("/dest");
0090         QFile::remove(dest);
0091         const QUrl destUrl = QUrl::fromLocalFile(dest);
0092 
0093         // When using storedPut with the QFile as argument
0094         KIO::StoredTransferJob *job = KIO::storedPut(&srcFile, destUrl, -1, KIO::Overwrite | KIO::HideProgressInfo);
0095 
0096         // Then the copy should succeed and the dest file exist
0097         QVERIFY2(job->exec(), qPrintable(job->errorString()));
0098         QVERIFY(QFile::exists(dest));
0099         QCOMPARE(QFileInfo(src).size(), QFileInfo(dest).size());
0100         // And the permissions should respect the umask (#359581)
0101 #ifdef Q_OS_UNIX
0102         if (m_umask & S_IWOTH) {
0103             QVERIFY2(!(QFileInfo(dest).permissions() & QFileDevice::WriteOther), qPrintable(dest));
0104         }
0105         if (m_umask & S_IWGRP) {
0106             QVERIFY(!(QFileInfo(dest).permissions() & QFileDevice::WriteGroup));
0107         }
0108 #endif
0109         QFile::remove(dest);
0110     }
0111 
0112     void test_data()
0113     {
0114         QTest::addColumn<QString>("actionText"); // the action we're clicking on
0115         QTest::addColumn<QString>("expectedDefaultFilename"); // the initial filename in the dialog
0116         QTest::addColumn<QString>("typedFilename"); // what the user is typing
0117         QTest::addColumn<QString>("expectedFilename"); // the final file name
0118 
0119         QTest::newRow("text file") << "Text File"
0120                                    << "Text File.txt"
0121                                    << "tmp_knewfilemenutest.txt"
0122                                    << "tmp_knewfilemenutest.txt";
0123         QTest::newRow("text file with jpeg extension") << "Text File"
0124                                                        << "Text File.txt"
0125                                                        << "foo.jpg"
0126                                                        << "foo.jpg"; // You get what you typed
0127         QTest::newRow("html file") << "HTML File"
0128                                    << "HTML File.html"
0129                                    << "foo.html"
0130                                    << "foo.html";
0131         QTest::newRow("url desktop file") << "Link to Location "
0132                                           << ""
0133                                           << "tmp_link.desktop"
0134                                           << "tmp_link.desktop";
0135         QTest::newRow("url desktop file no extension") << "Link to Location "
0136                                                        << ""
0137                                                        << "tmp_link1"
0138                                                        << "tmp_link1.desktop";
0139         QTest::newRow("url desktop file .pl extension") << "Link to Location "
0140                                                         << ""
0141                                                         << "tmp_link.pl"
0142                                                         << "tmp_link.pl.desktop";
0143         QTest::newRow("symlink") << "Link to File"
0144                                  << ""
0145                                  << "thelink"
0146                                  << "thelink";
0147         QTest::newRow("folder") << "Folder..."
0148                                 << "New Folder"
0149                                 << "folder1"
0150                                 << "folder1";
0151         QTest::newRow("folder_named_tilde") << "Folder..."
0152                                             << "New Folder"
0153                                             << "~"
0154                                             << "~";
0155 
0156         // ~/.qttest/share/folderTildeExpanded
0157         const QString tildeDirPath =
0158             KShell::tildeCollapse(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/folderTildeExpanded"));
0159         QVERIFY(tildeDirPath.startsWith(QLatin1Char('~')));
0160         QTest::newRow("folder_tilde_expanded") << "Folder..."
0161                                                << "New Folder" << tildeDirPath << "folderTildeExpanded";
0162 
0163         QTest::newRow("folder_default_name") << "Folder..."
0164                                              << "New Folder"
0165                                              << "New Folder"
0166                                              << "New Folder";
0167         QTest::newRow("folder_with_suggested_name") << "Folder..."
0168                                                     << "New Folder (1)"
0169                                                     << "New Folder (1)"
0170                                                     << "New Folder (1)";
0171         QTest::newRow("folder_with_suggested_name_but_user_overrides") << "Folder..."
0172                                                                        << "New Folder (2)"
0173                                                                        << "New Folder"
0174                                                                        << "";
0175         QTest::newRow("application") << "Link to Application..."
0176                                      << "Link to Application"
0177                                      << "app1"
0178                                      << "app1.desktop";
0179     }
0180 
0181     void test()
0182     {
0183         QFETCH(QString, actionText);
0184         QFETCH(QString, expectedDefaultFilename);
0185         QFETCH(QString, typedFilename);
0186         QFETCH(QString, expectedFilename);
0187 
0188         QWidget parentWidget;
0189 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(5, 100)
0190         KActionCollection coll(this, QStringLiteral("foo"));
0191         KNewFileMenu menu(&coll, QStringLiteral("the_action"), this);
0192 #else
0193         KNewFileMenu menu(this);
0194 #endif
0195         menu.setModal(false);
0196         menu.setParentWidget(&parentWidget);
0197         menu.setSelectDirWhenAlreadyExist(true);
0198         menu.setWorkingDirectory(QUrl::fromLocalFile(m_tmpDir.path()));
0199         menu.checkUpToDate();
0200 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(5, 100)
0201         QAction *action = coll.action(QStringLiteral("the_action"));
0202 #else
0203         QAction *action = &menu;
0204 #endif
0205         QVERIFY(action);
0206         QAction *textAct = nullptr;
0207         const QList<QAction *> actionsList = action->menu()->actions();
0208         for (QAction *act : actionsList) {
0209             if (act->text().contains(actionText)) {
0210                 textAct = act;
0211             }
0212         }
0213         if (!textAct) {
0214             for (const QAction *act : actionsList) {
0215                 qDebug() << act << act->text() << act->data();
0216             }
0217             const QString err = QLatin1String("action with text \"") + actionText + QLatin1String("\" not found.");
0218             QVERIFY2(textAct, qPrintable(err));
0219         }
0220         textAct->trigger();
0221 
0222         QDialog *dialog;
0223         // QTRY_ because a NameFinderJob could be running and the dialog will be shown when
0224         // it finishes.
0225         QTRY_VERIFY(dialog = parentWidget.findChild<QDialog *>());
0226 
0227         const auto buttonsList = dialog->findChildren<QPushButton *>();
0228         auto it = std::find_if(buttonsList.cbegin(), buttonsList.cend(), [](const QPushButton *button) {
0229             return button->text().contains("OK");
0230         });
0231         QVERIFY(it != buttonsList.cend());
0232         QPushButton *okButton = *it;
0233 
0234         if (KNameAndUrlInputDialog *nauiDialog = qobject_cast<KNameAndUrlInputDialog *>(dialog)) {
0235             QCOMPARE(nauiDialog->name(), expectedDefaultFilename);
0236             nauiDialog->setSuggestedName(typedFilename);
0237             nauiDialog->setSuggestedUrl(QUrl(QStringLiteral("file:///etc")));
0238         } else if (KPropertiesDialog *propsDialog = qobject_cast<KPropertiesDialog *>(dialog)) {
0239             QLineEdit *lineEdit = propsDialog->findChild<QLineEdit *>(QStringLiteral("fileNameLineEdit"));
0240             QVERIFY(lineEdit);
0241             QCOMPARE(lineEdit->text(), expectedDefaultFilename);
0242             lineEdit->setText(typedFilename);
0243         } else {
0244             QLineEdit *lineEdit = dialog->findChild<QLineEdit *>();
0245             QVERIFY(lineEdit);
0246             QCOMPARE(lineEdit->text(), expectedDefaultFilename);
0247             lineEdit->setText(typedFilename);
0248         }
0249         QUrl emittedUrl;
0250         QSignalSpy spy(&menu, &KNewFileMenu::fileCreated);
0251         QSignalSpy folderSpy(&menu, &KNewFileMenu::directoryCreated);
0252 
0253         // expectedFilename is empty in the "Folder already exists" case, the button won't
0254         // become enabled.
0255         if (!expectedFilename.isEmpty()) {
0256             // For all other cases, QTRY_ because we may be waiting for the StatJob in
0257             // KNewFileMenuPrivate::_k_delayedSlotTextChanged() to finish, the OK button
0258             // is disabled while it's checking if a folder/file with that name already exists.
0259             QTRY_VERIFY(okButton->isEnabled());
0260         }
0261 
0262         okButton->click();
0263         QString path = m_tmpDir.path() + QLatin1Char('/') + expectedFilename;
0264         if (typedFilename.contains(QLatin1String("folderTildeExpanded"))) {
0265             path = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + QLatin1String("folderTildeExpanded");
0266         }
0267         if (actionText == QLatin1String("Folder...")) {
0268             if (expectedFilename.isEmpty()) {
0269                 // This is the "Folder already exists" case; expect an error dialog
0270                 okButton->click();
0271                 path.clear();
0272             } else {
0273                 QVERIFY(folderSpy.wait(1000));
0274                 emittedUrl = folderSpy.at(0).at(0).toUrl();
0275                 QVERIFY(QFileInfo(path).isDir());
0276             }
0277         } else {
0278             if (spy.isEmpty()) {
0279                 QVERIFY(spy.wait(1000));
0280             }
0281             emittedUrl = spy.at(0).at(0).toUrl();
0282             QVERIFY(QFile::exists(path));
0283             if (actionText != QLatin1String("Link to File")) {
0284                 QFile file(path);
0285                 QVERIFY(file.open(QIODevice::ReadOnly));
0286                 const QByteArray contents = file.readAll();
0287                 if (actionText.startsWith(QLatin1String("HTML"))) {
0288                     QCOMPARE(QString::fromLatin1(contents.left(6)), QStringLiteral("<!DOCT"));
0289                 }
0290             }
0291         }
0292         QCOMPARE(emittedUrl.toLocalFile(), path);
0293     }
0294 
0295     void testParsingUserDirs()
0296     {
0297 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(5, 100)
0298         KActionCollection coll(this, QStringLiteral("foo"));
0299         KNewFileMenu menu(&coll, QStringLiteral("the_action"), this);
0300 #else
0301         KNewFileMenu menu(this);
0302 #endif
0303         menu.setWorkingDirectory(QUrl::fromLocalFile(m_tmpDir.path()));
0304         menu.checkUpToDate();
0305 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(5, 100)
0306         QAction *action = coll.action(QStringLiteral("the_action"));
0307 #else
0308         QAction *action = &menu;
0309 #endif
0310         const auto list = action->menu()->actions();
0311         auto it = std::find_if(list.cbegin(), list.cend(), [](QAction *act) {
0312             return act->text().contains("Custom");
0313         });
0314         QVERIFY(it != list.cend());
0315         // There is a separator between system-wide templates and the ones
0316         // from the user's home
0317         QVERIFY((*--it)->isSeparator());
0318     }
0319 
0320 private:
0321     QTemporaryDir m_tmpDir;
0322     QString m_xdgConfigDir;
0323 #ifdef Q_OS_UNIX
0324     mode_t m_umask;
0325 #endif
0326 };
0327 
0328 QTEST_MAIN(KNewFileMenuTest)
0329 
0330 #include "knewfilemenutest.moc"