File indexing completed on 2024-03-24 03:57:44

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