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"