Warning, file /frameworks/kservice/autotests/kmimeassociationstest.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2008 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 "kmimeassociations_p.h" 0009 #include "ksycoca_p.h" 0010 #include "setupxdgdirs.h" 0011 #include <KConfigGroup> 0012 #include <KDesktopFile> 0013 #include <QDebug> 0014 #include <QDir> 0015 #include <QMimeDatabase> 0016 #include <QMimeType> 0017 #include <QSignalSpy> 0018 #include <QTemporaryDir> 0019 #include <QTemporaryFile> 0020 #include <QTest> 0021 #include <kapplicationtrader.h> 0022 #include <kbuildsycoca_p.h> 0023 #include <kservicefactory_p.h> 0024 #include <ksycoca.h> 0025 0026 // We need a factory that returns the same KService::Ptr every time it's asked for a given service. 0027 // Otherwise the changes to the service's serviceTypes by KMimeAssociationsTest have no effect 0028 class FakeServiceFactory : public KServiceFactory 0029 { 0030 public: 0031 FakeServiceFactory(KSycoca *db) 0032 : KServiceFactory(db) 0033 { 0034 } 0035 ~FakeServiceFactory() override; 0036 0037 KService::Ptr findServiceByMenuId(const QString &name) override 0038 { 0039 // qDebug() << name; 0040 KService::Ptr result = m_cache.value(name); 0041 if (!result) { 0042 result = KServiceFactory::findServiceByMenuId(name); 0043 m_cache.insert(name, result); 0044 } 0045 // qDebug() << name << result.data(); 0046 return result; 0047 } 0048 KService::Ptr findServiceByDesktopPath(const QString &name) override 0049 { 0050 KService::Ptr result = m_cache.value(name); // yeah, same cache, I don't care :) 0051 if (!result) { 0052 result = KServiceFactory::findServiceByDesktopPath(name); 0053 m_cache.insert(name, result); 0054 } 0055 return result; 0056 } 0057 0058 private: 0059 QMap<QString, KService::Ptr> m_cache; 0060 }; 0061 0062 // Helper method for all the trader tests, comes from kmimetypetest.cpp 0063 static bool offerListHasService(const KService::List &offers, const QString &entryPath, bool expected /* if set, show error if not found */) 0064 { 0065 // ksycoca resolves to canonical paths, so do it here as well 0066 const QString realPath = QFileInfo(entryPath).canonicalFilePath(); 0067 Q_ASSERT(!realPath.isEmpty()); 0068 0069 bool found = false; 0070 for (const KService::Ptr &serv : offers) { 0071 if (serv->entryPath() == realPath) { 0072 if (found) { // should be there only once 0073 qWarning("ERROR: %s was found twice in the list", qPrintable(realPath)); 0074 return false; // make test fail 0075 } 0076 found = true; 0077 } 0078 } 0079 if (!found && expected) { 0080 qWarning() << "ERROR:" << realPath << "not found in offer list. Here's the full list:"; 0081 for (const KService::Ptr &serv : offers) { 0082 qDebug() << serv->entryPath(); 0083 } 0084 } 0085 return found; 0086 } 0087 0088 static void writeAppDesktopFile(const QString &path, const QStringList &mimeTypes, int initialPreference = 1) 0089 { 0090 KDesktopFile file(path); 0091 KConfigGroup group = file.desktopGroup(); 0092 group.writeEntry("Name", "FakeApplication"); 0093 group.writeEntry("Type", "Application"); 0094 group.writeEntry("Exec", "ls"); 0095 group.writeEntry("OnlyShowIn", "KDE;UDE"); 0096 group.writeEntry("NotShowIn", "GNOME"); 0097 group.writeEntry("InitialPreference", initialPreference); 0098 group.writeXdgListEntry("MimeType", mimeTypes); 0099 } 0100 0101 static void writeNonKDEAppDesktopFile(const QString &path, const QStringList &mimeTypes) // bug 427469 0102 { 0103 KDesktopFile file(path); 0104 KConfigGroup group = file.desktopGroup(); 0105 group.writeEntry("Name", "FakeApplication"); 0106 group.writeEntry("Type", "Application"); 0107 group.writeEntry("Exec", "ls"); 0108 group.writeEntry("NotShowIn", "KDE"); 0109 group.writeXdgListEntry("MimeType", mimeTypes); 0110 } 0111 0112 /** 0113 * This unit test verifies the parsing of mimeapps.list files, both directly 0114 * and via kbuildsycoca (and making trader queries). 0115 */ 0116 class KMimeAssociationsTest : public QObject 0117 { 0118 Q_OBJECT 0119 private Q_SLOTS: 0120 void initTestCase() 0121 { 0122 setupXdgDirs(); 0123 QStandardPaths::setTestModeEnabled(true); 0124 // The Plasma bit makes no sense, but this is just to test that this is treated as a colon-separated list 0125 qputenv("XDG_CURRENT_DESKTOP", "KDE:Plasma"); 0126 0127 m_localConfig = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/'); 0128 QDir(m_localConfig).removeRecursively(); 0129 QVERIFY(QDir().mkpath(m_localConfig)); 0130 m_localApps = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + QLatin1Char('/'); 0131 QDir(m_localApps).removeRecursively(); 0132 QVERIFY(QDir().mkpath(m_localApps)); 0133 QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1Char('/'); 0134 QDir(cacheDir).removeRecursively(); 0135 0136 // Create fake application (associated with text/plain in mimeapps.list) 0137 fakeTextApplication = m_localApps + QLatin1String{"faketextapplication.desktop"}; 0138 writeAppDesktopFile(fakeTextApplication, QStringList() << QStringLiteral("text/plain")); 0139 0140 // Create fake application (associated with text/plain in mimeapps.list) 0141 fakeTextApplicationPrefixed = m_localApps + QLatin1String{"fakepfx/faketextapplicationpfx.desktop"}; 0142 writeAppDesktopFile(fakeTextApplicationPrefixed, QStringList() << QStringLiteral("text/plain")); 0143 0144 // A fake "default" application for text/plain (high initial preference, but not in mimeapps.list) 0145 fakeDefaultTextApplication = m_localApps + QLatin1String{"fakedefaulttextapplication.desktop"}; 0146 writeAppDesktopFile(fakeDefaultTextApplication, QStringList() << QStringLiteral("text/plain"), 9); 0147 0148 // An app (like emacs) listing explicitly the derived mimetype (c-src); not in mimeapps.list 0149 // This interacted badly with mimeapps.list listing another app for text/plain, but the 0150 // lookup found this app first, due to c-src. The fix: ignoring derived mimetypes when 0151 // the base mimetype is already listed. 0152 // 0153 // Also include aliases (msword), to check they don't cancel each other out. 0154 fakeCSrcApplication = m_localApps + QLatin1String{"fakecsrcmswordapplication.desktop"}; 0155 writeAppDesktopFile(fakeCSrcApplication, 0156 QStringList() << QStringLiteral("text/plain") << QStringLiteral("text/c-src") << QStringLiteral("application/vnd.ms-word") 0157 << QStringLiteral("application/msword"), 0158 8); 0159 0160 fakeJpegApplication = m_localApps + QLatin1String{"fakejpegapplication.desktop"}; 0161 writeAppDesktopFile(fakeJpegApplication, QStringList() << QStringLiteral("image/jpeg")); 0162 0163 fakeArkApplication = m_localApps + QLatin1String{"fakearkapplication.desktop"}; 0164 writeAppDesktopFile(fakeArkApplication, QStringList() << QStringLiteral("application/zip")); 0165 0166 fakeHtmlApplication = m_localApps + QLatin1String{"fakehtmlapplication.desktop"}; 0167 writeAppDesktopFile(fakeHtmlApplication, QStringList() << QStringLiteral("text/html")); 0168 0169 fakeHtmlApplicationPrefixed = m_localApps + QLatin1String{"fakepfx/fakehtmlapplicationpfx.desktop"}; 0170 writeAppDesktopFile(fakeHtmlApplicationPrefixed, QStringList() << QStringLiteral("text/html")); 0171 0172 fakeOktetaApplication = m_localApps + QLatin1String{"fakeoktetaapplication.desktop"}; 0173 writeAppDesktopFile(fakeOktetaApplication, QStringList() << QStringLiteral("application/octet-stream")); 0174 0175 const QString fakeGnomeRoller = m_localApps + QLatin1String{"fake.org.gnome.FileRoller.desktop"}; 0176 writeNonKDEAppDesktopFile(fakeGnomeRoller, QStringList() << QStringLiteral("application/x-7z-compressed")); 0177 0178 const QString fakeNautilus = m_localApps + QLatin1String{"fake.org.gnome.Nautilus.desktop"}; 0179 writeNonKDEAppDesktopFile(fakeNautilus, QStringList() << QStringLiteral("application/x-7z-compressed")); 0180 0181 // Update ksycoca in ~/.qttest after creating the above 0182 runKBuildSycoca(); 0183 0184 // Create factory on the heap and don't delete it. This must happen after 0185 // Sycoca is built, in case it did not exist before. 0186 // It registers to KSycoca, which deletes it at end of program execution. 0187 KServiceFactory *factory = new FakeServiceFactory(KSycoca::self()); 0188 KSycocaPrivate::self()->m_serviceFactory = factory; 0189 QCOMPARE(KSycocaPrivate::self()->serviceFactory(), factory); 0190 0191 // For debugging: print all services and their storageId 0192 #if 0 0193 const KService::List lst = KService::allServices(); 0194 QVERIFY(!lst.isEmpty()); 0195 for (const KService::Ptr &serv : lst) { 0196 qDebug() << serv->entryPath() << serv->storageId() /*<< serv->desktopEntryName()*/; 0197 } 0198 #endif 0199 0200 KService::Ptr fakeApplicationService = KService::serviceByStorageId(QStringLiteral("faketextapplication.desktop")); 0201 QVERIFY(fakeApplicationService); 0202 0203 m_mimeAppsFileContents = 0204 "[Added Associations]\n" 0205 "image/jpeg=fakejpegapplication.desktop;\n" 0206 "text/html=fakehtmlapplication.desktop;fakehtmlapplicationpfx.desktop;\n" 0207 "text/plain=fakepfx-faketextapplicationpfx.desktop;gvim.desktop;wine.desktop;idontexist.desktop;\n" 0208 // test alias resolution 0209 "application/x-pdf=fakejpegapplication.desktop;\n" 0210 // test x-scheme-handler (#358159) (missing trailing ';' as per xdg-mime bug...) 0211 "x-scheme-handler/mailto=faketextapplication.desktop\n" 0212 // test association with octet-stream (#425154) 0213 "application/octet-stream=fakeoktetaapplication.desktop\n" 0214 // test a non-kde app (#427469) 0215 "application/x-7z-compressed=fake.org.gnome.FileRoller.desktop;\n" 0216 "[Added KParts/ReadOnlyPart Associations]\n" 0217 "text/plain=katepart.desktop;\n" 0218 "[Removed Associations]\n" 0219 "image/jpeg=firefox.desktop;\n" 0220 "text/html=gvim.desktop;abiword.desktop;\n" 0221 "[Default Applications]\n" 0222 "text/plain=faketextapplication.desktop;second-faketextapplicationpfx.desktop\n"; 0223 // Expected results 0224 preferredApps[QStringLiteral("image/jpeg")] << QStringLiteral("fakejpegapplication.desktop"); 0225 preferredApps[QStringLiteral("application/pdf")] << QStringLiteral("fakejpegapplication.desktop"); 0226 preferredApps[QStringLiteral("text/plain")] << QStringLiteral("faketextapplication.desktop") << QStringLiteral("second-faketextapplicationpfx.desktop") 0227 << QStringLiteral("fakepfx-faketextapplicationpfx.desktop") << QStringLiteral("gvim.desktop"); 0228 preferredApps[QStringLiteral("text/x-csrc")] << QStringLiteral("faketextapplication.desktop") 0229 << QStringLiteral("fakepfx-faketextapplicationpfx.desktop") << QStringLiteral("gvim.desktop"); 0230 preferredApps[QStringLiteral("text/html")] << QStringLiteral("fakehtmlapplication.desktop") << QStringLiteral("fakepfx-fakehtmlapplicationpfx.desktop"); 0231 preferredApps[QStringLiteral("application/msword")] << QStringLiteral("fakecsrcmswordapplication.desktop"); 0232 preferredApps[QStringLiteral("x-scheme-handler/mailto")] << QStringLiteral("faketextapplication.desktop"); 0233 preferredApps[QStringLiteral("text/x-python")] << QStringLiteral("faketextapplication.desktop"); 0234 preferredApps[QStringLiteral("application/x-7z-compressed")] << QStringLiteral("fake.org.gnome.FileRoller.desktop"); 0235 removedApps[QStringLiteral("application/x-7z-compressed")] << QStringLiteral("fake.org.gnome.Nautilus.desktop"); 0236 removedApps[QStringLiteral("image/jpeg")] << QStringLiteral("firefox.desktop"); 0237 removedApps[QStringLiteral("text/html")] << QStringLiteral("gvim.desktop") << QStringLiteral("abiword.desktop"); 0238 0239 // Clean-up non-existing apps 0240 removeNonExisting(preferredApps); 0241 removeNonExisting(removedApps); 0242 } 0243 0244 void cleanupTestCase() 0245 { 0246 QFile::remove(m_localConfig + QLatin1String{"/mimeapps.list"}); 0247 runKBuildSycoca(); 0248 } 0249 0250 void testParseSingleFile() 0251 { 0252 KOfferHash offerHash; 0253 KMimeAssociations parser(offerHash, KSycocaPrivate::self()->serviceFactory()); 0254 0255 QTemporaryDir tempDir; 0256 QVERIFY(tempDir.isValid()); 0257 0258 QFile tempFile(tempDir.path() + QLatin1String{"/mimeapps.list"}); 0259 QVERIFY(tempFile.open(QIODevice::WriteOnly)); 0260 tempFile.write(m_mimeAppsFileContents); 0261 const QString fileName = tempFile.fileName(); 0262 tempFile.close(); 0263 0264 // QTest::ignoreMessage(QtDebugMsg, "findServiceByDesktopPath: idontexist.desktop not found"); 0265 parser.parseMimeAppsList(fileName, 100); 0266 0267 for (auto it = preferredApps.cbegin(), endIt = preferredApps.cend(); it != endIt; ++it) { 0268 const QString mime = it.key(); 0269 // The data for derived types and aliases isn't for this test (which only looks at mimeapps.list) 0270 if (mime == QLatin1String("text/x-csrc") // 0271 || mime == QLatin1String("text/x-python") // 0272 || mime == QLatin1String("application/msword")) { 0273 continue; 0274 } 0275 const QList<KServiceOffer> offers = offerHash.offersFor(mime); 0276 for (const QString &service : it.value()) { 0277 KService::Ptr serv = KService::serviceByStorageId(service); 0278 if (serv && !offersContains(offers, serv)) { 0279 qDebug() << "expected offer" << serv->entryPath() << "not in offers for" << mime << ":"; 0280 for (const KServiceOffer &offer : offers) { 0281 qDebug() << offer.service()->storageId(); 0282 } 0283 QFAIL("offer does not have servicetype"); 0284 } 0285 } 0286 } 0287 0288 for (auto it = removedApps.cbegin(), end = removedApps.cend(); it != end; ++it) { 0289 const QString mime = it.key(); 0290 const QList<KServiceOffer> offers = offerHash.offersFor(mime); 0291 for (const QString &service : it.value()) { 0292 KService::Ptr serv = KService::serviceByStorageId(service); 0293 if (serv && offersContains(offers, serv)) { 0294 // qDebug() << serv.data() << serv->entryPath() << "does not have" << mime; 0295 QFAIL("offer should not have servicetype"); 0296 } 0297 } 0298 } 0299 } 0300 0301 void testGlobalAndLocalFiles() 0302 { 0303 KOfferHash offerHash; 0304 KMimeAssociations parser(offerHash, KSycocaPrivate::self()->serviceFactory()); 0305 0306 // Write global file 0307 QTemporaryDir tempDirGlobal; 0308 QVERIFY(tempDirGlobal.isValid()); 0309 0310 QFile tempFileGlobal(tempDirGlobal.path() + QLatin1String{"/mimeapps.list"}); 0311 QVERIFY(tempFileGlobal.open(QIODevice::WriteOnly)); 0312 QByteArray globalAppsFileContents = 0313 "[Added Associations]\n" 0314 "image/jpeg=firefox.desktop;\n" // removed by local config 0315 "text/html=firefox.desktop;\n" // mdv 0316 "image/png=fakejpegapplication.desktop;\n"; 0317 tempFileGlobal.write(globalAppsFileContents); 0318 const QString globalFileName = tempFileGlobal.fileName(); 0319 tempFileGlobal.close(); 0320 0321 // We didn't keep it, so we need to write the local file again 0322 QTemporaryDir tempDir; 0323 QVERIFY(tempDir.isValid()); 0324 0325 QFile tempFile(tempDir.path() + QLatin1String{"/mimeapps.list"}); 0326 QVERIFY(tempFile.open(QIODevice::WriteOnly)); 0327 tempFile.write(m_mimeAppsFileContents); 0328 const QString fileName = tempFile.fileName(); 0329 tempFile.close(); 0330 0331 parser.parseMimeAppsList(globalFileName, 1000); 0332 parser.parseMimeAppsList(fileName, 1050); // += 50 is correct. 0333 0334 QList<KServiceOffer> offers = offerHash.offersFor(QStringLiteral("image/jpeg")); 0335 std::stable_sort(offers.begin(), offers.end()); // like kbuildservicefactory.cpp does 0336 const QStringList expectedJpegApps = preferredApps[QStringLiteral("image/jpeg")]; 0337 QCOMPARE(assembleOffers(offers), expectedJpegApps); 0338 0339 offers = offerHash.offersFor(QStringLiteral("text/html")); 0340 std::stable_sort(offers.begin(), offers.end()); 0341 QStringList textHtmlApps = preferredApps[QStringLiteral("text/html")]; 0342 if (KService::serviceByStorageId(QStringLiteral("firefox.desktop"))) { 0343 textHtmlApps.append(QStringLiteral("firefox.desktop")); 0344 } 0345 qDebug() << assembleOffers(offers); 0346 QCOMPARE(assembleOffers(offers), textHtmlApps); 0347 0348 offers = offerHash.offersFor(QStringLiteral("image/png")); 0349 std::stable_sort(offers.begin(), offers.end()); 0350 QCOMPARE(assembleOffers(offers), QStringList() << QStringLiteral("fakejpegapplication.desktop")); 0351 } 0352 0353 void testSetupRealFile() 0354 { 0355 writeToMimeApps(m_mimeAppsFileContents); 0356 0357 // Test a trader query 0358 KService::List offers = KApplicationTrader::queryByMimeType(QStringLiteral("image/jpeg")); 0359 QVERIFY(!offers.isEmpty()); 0360 QCOMPARE(offers.first()->storageId(), QStringLiteral("fakejpegapplication.desktop")); 0361 0362 // Now the generic variant of the above test: 0363 // for each mimetype, check that the preferred apps are as specified 0364 for (auto it = preferredApps.cbegin(), endIt = preferredApps.cend(); it != endIt; ++it) { 0365 const QString mime = it.key(); 0366 const KService::List offers = KApplicationTrader::queryByMimeType(mime); 0367 const QStringList offerIds = assembleServices(offers, it.value().count()); 0368 if (offerIds != it.value()) { 0369 qDebug() << "offers for" << mime << ":"; 0370 for (int i = 0; i < offers.count(); ++i) { 0371 qDebug() << " " << i << ":" << offers[i]->storageId(); 0372 } 0373 qDebug() << " Expected:" << it.value(); 0374 const QStringList expectedPreferredServices = it.value(); 0375 for (int i = 0; i < expectedPreferredServices.count(); ++i) { 0376 qDebug() << mime << i << expectedPreferredServices[i]; 0377 // QCOMPARE(expectedPreferredServices[i], offers[i]->storageId()); 0378 } 0379 } 0380 QCOMPARE(offerIds, it.value()); 0381 } 0382 for (auto it = removedApps.constBegin(), endIt = removedApps.constEnd(); it != endIt; ++it) { 0383 const QString mime = it.key(); 0384 const KService::List offers = KApplicationTrader::queryByMimeType(mime); 0385 const QStringList offerIds = assembleServices(offers); 0386 for (const QString &service : it.value()) { 0387 const QString error = QStringLiteral("Offers for %1 should not contain %2").arg(mime, service); 0388 QVERIFY2(!offerIds.contains(service), qPrintable(error)); 0389 } 0390 } 0391 } 0392 0393 void testMultipleInheritance() 0394 { 0395 // application/x-shellscript inherits from both text/plain and application/x-executable 0396 KService::List offers = KApplicationTrader::queryByMimeType(QStringLiteral("application/x-shellscript")); 0397 QVERIFY(offerListHasService(offers, fakeTextApplication, true)); 0398 } 0399 0400 void testRemoveAssociationFromParent() 0401 { 0402 // I removed kate from text/plain, and it would still appear in text/x-java. 0403 0404 // First, let's check our fake app is associated with text/plain 0405 KService::List offers = KApplicationTrader::queryByMimeType(QStringLiteral("text/plain")); 0406 QVERIFY(offerListHasService(offers, fakeTextApplication, true)); 0407 0408 writeToMimeApps( 0409 QByteArray("[Removed Associations]\n" 0410 "text/plain=faketextapplication.desktop;\n")); 0411 0412 offers = KApplicationTrader::queryByMimeType(QStringLiteral("text/plain")); 0413 QVERIFY(!offerListHasService(offers, fakeTextApplication, false)); 0414 0415 offers = KApplicationTrader::queryByMimeType(QStringLiteral("text/x-java")); 0416 QVERIFY(!offerListHasService(offers, fakeTextApplication, false)); 0417 } 0418 0419 void testRemovedImplicitAssociation() // remove (implicit) assoc from derived mimetype 0420 { 0421 // #164584: Removing ark from opendocument.text didn't work 0422 const QString opendocument = QStringLiteral("application/vnd.oasis.opendocument.text"); 0423 0424 // [sanity checking of s-m-i installation] 0425 QMimeType mime = QMimeDatabase().mimeTypeForName(opendocument); 0426 QVERIFY(mime.isValid()); 0427 if (!mime.inherits(QStringLiteral("application/zip"))) { 0428 // CentOS patches out the application/zip inheritance from application/vnd.oasis.opendocument.text!! Grmbl. 0429 QSKIP("Broken distro where application/vnd.oasis.opendocument.text doesn't inherit from application/zip"); 0430 } 0431 0432 KService::List offers = KApplicationTrader::queryByMimeType(opendocument); 0433 QVERIFY(offerListHasService(offers, fakeArkApplication, true)); 0434 0435 writeToMimeApps( 0436 QByteArray("[Removed Associations]\n" 0437 "application/vnd.oasis.opendocument.text=fakearkapplication.desktop;\n")); 0438 0439 offers = KApplicationTrader::queryByMimeType(opendocument); 0440 QVERIFY(!offerListHasService(offers, fakeArkApplication, false)); 0441 0442 offers = KApplicationTrader::queryByMimeType(QStringLiteral("application/zip")); 0443 QVERIFY(offerListHasService(offers, fakeArkApplication, true)); 0444 } 0445 0446 void testRemovedImplicitAssociation178560() 0447 { 0448 // #178560: Removing ark from interface/x-winamp-skin didn't work 0449 // Using application/x-kns (another zip-derived mimetype) nowadays. 0450 const QString mime = QStringLiteral("application/x-kns"); 0451 0452 // That mimetype comes from kcoreaddons, let's make sure it's properly installed 0453 { 0454 QMimeDatabase db; 0455 QMimeType mime = db.mimeTypeForName(QStringLiteral("application/x-kns")); 0456 QVERIFY(mime.isValid()); 0457 QCOMPARE(mime.name(), QStringLiteral("application/x-kns")); 0458 QVERIFY(mime.inherits(QStringLiteral("application/zip"))); 0459 } 0460 0461 KService::List offers = KApplicationTrader::queryByMimeType(mime); 0462 QVERIFY(offerListHasService(offers, fakeArkApplication, true)); 0463 0464 writeToMimeApps( 0465 QByteArray("[Removed Associations]\n" 0466 "application/x-kns=fakearkapplication.desktop;\n")); 0467 0468 offers = KApplicationTrader::queryByMimeType(mime); 0469 QVERIFY(!offerListHasService(offers, fakeArkApplication, false)); 0470 0471 offers = KApplicationTrader::queryByMimeType(QStringLiteral("application/zip")); 0472 QVERIFY(offerListHasService(offers, fakeArkApplication, true)); 0473 } 0474 0475 // remove assoc from a mime which is both a parent and a derived mimetype 0476 void testRemovedMiddleAssociation() 0477 { 0478 // More tricky: x-theme inherits x-desktop inherits text/plain, 0479 // if we remove an association for x-desktop then x-theme shouldn't 0480 // get it from text/plain... 0481 0482 KService::List offers; 0483 writeToMimeApps( 0484 QByteArray("[Removed Associations]\n" 0485 "application/x-desktop=faketextapplication.desktop;\n")); 0486 0487 offers = KApplicationTrader::queryByMimeType(QStringLiteral("text/plain")); 0488 QVERIFY(offerListHasService(offers, fakeTextApplication, true)); 0489 0490 offers = KApplicationTrader::queryByMimeType(QStringLiteral("application/x-desktop")); 0491 QVERIFY(!offerListHasService(offers, fakeTextApplication, false)); 0492 0493 offers = KApplicationTrader::queryByMimeType(QStringLiteral("application/x-theme")); 0494 QVERIFY(!offerListHasService(offers, fakeTextApplication, false)); 0495 } 0496 0497 private: 0498 typedef QMap<QString /*mimetype*/, QStringList> ExpectedResultsMap; 0499 0500 void runKBuildSycoca() 0501 { 0502 // Wait for notifyDatabaseChanged DBus signal 0503 // (The real KCM code simply does the refresh in a slot, asynchronously) 0504 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 80) 0505 QSignalSpy spy(KSycoca::self(), qOverload<const QStringList &>(&KSycoca::databaseChanged)); 0506 #else 0507 QSignalSpy spy(KSycoca::self(), &KSycoca::databaseChanged); 0508 #endif 0509 0510 KBuildSycoca builder; 0511 QVERIFY(builder.recreate()); 0512 if (spy.isEmpty()) { 0513 spy.wait(); 0514 } 0515 } 0516 0517 void writeToMimeApps(const QByteArray &contents) 0518 { 0519 QString mimeAppsPath = m_localConfig + QLatin1String{"/mimeapps.list"}; 0520 QFile mimeAppsFile(mimeAppsPath); 0521 QVERIFY(mimeAppsFile.open(QIODevice::WriteOnly)); 0522 mimeAppsFile.write(contents); 0523 mimeAppsFile.close(); 0524 0525 runKBuildSycoca(); 0526 } 0527 0528 static bool offersContains(const QList<KServiceOffer> &offers, KService::Ptr serv) 0529 { 0530 for (const KServiceOffer &offer : offers) { 0531 if (offer.service()->storageId() == serv->storageId()) { 0532 return true; 0533 } 0534 } 0535 return false; 0536 } 0537 static QStringList assembleOffers(const QList<KServiceOffer> &offers) 0538 { 0539 QStringList lst; 0540 for (const KServiceOffer &offer : offers) { 0541 lst.append(offer.service()->storageId()); 0542 } 0543 return lst; 0544 } 0545 static QStringList assembleServices(const QList<KService::Ptr> &services, int maxCount = -1) 0546 { 0547 QStringList lst; 0548 for (const KService::Ptr &service : services) { 0549 lst.append(service->storageId()); 0550 if (maxCount > -1 && lst.count() == maxCount) { 0551 break; 0552 } 0553 } 0554 return lst; 0555 } 0556 0557 void removeNonExisting(ExpectedResultsMap &erm) 0558 { 0559 for (auto it = erm.begin(), endIt = erm.end(); it != endIt; ++it) { 0560 QMutableStringListIterator serv_it(it.value()); 0561 while (serv_it.hasNext()) { 0562 if (!KService::serviceByStorageId(serv_it.next())) { 0563 // qDebug() << "removing non-existing entry" << serv_it.value(); 0564 serv_it.remove(); 0565 } 0566 } 0567 } 0568 } 0569 QString m_localApps; 0570 QString m_localConfig; 0571 QByteArray m_mimeAppsFileContents; 0572 QString fakeTextApplication; 0573 QString fakeTextApplicationPrefixed; 0574 QString fakeDefaultTextApplication; 0575 QString fakeCSrcApplication; 0576 QString fakeJpegApplication; 0577 QString fakeHtmlApplication; 0578 QString fakeHtmlApplicationPrefixed; 0579 QString fakeArkApplication; 0580 QString fakeOktetaApplication; 0581 0582 ExpectedResultsMap preferredApps; 0583 ExpectedResultsMap removedApps; 0584 }; 0585 0586 FakeServiceFactory::~FakeServiceFactory() 0587 { 0588 } 0589 0590 QTEST_GUILESS_MAIN(KMimeAssociationsTest) 0591 0592 #include "kmimeassociationstest.moc"