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"