File indexing completed on 2024-04-07 14:11:21

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-only
0006 */
0007 
0008 #include <QDBusInterface>
0009 #include <QDebug>
0010 #include <QObject>
0011 #include <QSignalSpy>
0012 #include <QTemporaryDir>
0013 #include <QTest>
0014 
0015 #include <KBookmark>
0016 #include <KBookmarkManager>
0017 #include <KConfig>
0018 #include <KConfigGroup>
0019 #include <KProtocolInfo>
0020 #include <QStandardPaths>
0021 #include <kfileplacesmodel.h>
0022 #include <solid/device.h>
0023 
0024 #include <stdlib.h>
0025 
0026 Q_DECLARE_METATYPE(KFilePlacesModel::GroupType)
0027 
0028 // Avoid QHash randomization so that the order of the devices is stable
0029 static void seedInit()
0030 {
0031     qputenv("QT_HASH_SEED", "0");
0032     // This env var has no effect because this comes too late. qCpuFeatures() was already called by
0033     // a Q_CONSTRUCTOR_FUNCTION inside QtGui (see image/qimage_conversions.cpp). Argh. QTBUG-47566.
0034     qputenv("QT_NO_CPU_FEATURE", "sse4.2");
0035 }
0036 Q_CONSTRUCTOR_FUNCTION(seedInit)
0037 
0038 class KFilePlacesModelTest : public QObject
0039 {
0040     Q_OBJECT
0041 
0042 private Q_SLOTS:
0043     void initTestCase();
0044     void cleanupTestCase();
0045 
0046     void testInitialList();
0047     void testAddingInLaterVersion_data();
0048     void testAddingInLaterVersion();
0049     void testReparse();
0050     void testInternalBookmarksHaveIds();
0051     void testHiding();
0052     void testMove();
0053     void testPlacesLifecycle();
0054     void testDevicePlugging();
0055     void testDragAndDrop();
0056     void testDeviceSetupTeardown();
0057     void testEnableBaloo();
0058     void testRemoteUrls_data();
0059     void testRemoteUrls();
0060     void testRefresh();
0061     void testConvertedUrl_data();
0062     void testConvertedUrl();
0063     void testBookmarkObject();
0064     void testDataChangedSignal();
0065     void testIconRole_data();
0066     void testIconRole();
0067     void testMoveFunction();
0068     void testPlaceGroupHidden();
0069     void testPlaceGroupHiddenVsPlaceChildShown();
0070     void testPlaceGroupHiddenAndShownWithHiddenChild();
0071     void testPlaceGroupHiddenGroupIndexesIntegrity();
0072     void testPlaceGroupHiddenSignal();
0073     void testPlaceGroupHiddenRole();
0074     void testFilterWithAlternativeApplicationName();
0075     void testSupportedSchemes();
0076 
0077 private:
0078     QStringList placesUrls(KFilePlacesModel *model = nullptr) const;
0079     QDBusInterface *fakeManager();
0080     QDBusInterface *fakeDevice(const QString &udi);
0081     void createPlacesModels();
0082 
0083     KFilePlacesModel *m_places;
0084     KFilePlacesModel *m_places2; // To check that they always stay in sync
0085     // actually supposed to work across processes,
0086     // but much harder to test
0087 
0088     QMap<QString, QDBusInterface *> m_interfacesMap;
0089     QTemporaryDir m_tmpHome;
0090     bool m_hasRecentlyUsedKio;
0091 };
0092 
0093 static QString bookmarksFile()
0094 {
0095     return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/user-places.xbel");
0096 }
0097 
0098 void KFilePlacesModelTest::initTestCase()
0099 {
0100     QVERIFY(m_tmpHome.isValid());
0101     qputenv("HOME", m_tmpHome.path().toUtf8()); // use a empty home dir
0102 
0103     QStandardPaths::setTestModeEnabled(true);
0104 
0105     // Ensure we'll have a clean bookmark file to start
0106     QFile::remove(bookmarksFile());
0107 
0108     // disable baloo by default
0109     KConfig config(QStringLiteral("baloofilerc"));
0110     KConfigGroup basicSettings = config.group("Basic Settings");
0111     basicSettings.writeEntry("Indexing-Enabled", false);
0112     config.sync();
0113 
0114     qRegisterMetaType<QModelIndex>();
0115     qRegisterMetaType<KFilePlacesModel::GroupType>();
0116 
0117     const QString fakeHw = QFINDTESTDATA("fakecomputer.xml");
0118     QVERIFY(!fakeHw.isEmpty());
0119     qputenv("SOLID_FAKEHW", QFile::encodeName(fakeHw));
0120     m_hasRecentlyUsedKio = qEnvironmentVariableIsSet("KDE_FULL_SESSION") && KProtocolInfo::isKnownProtocol(QStringLiteral("recentlyused"));
0121 
0122     createPlacesModels();
0123 }
0124 
0125 void KFilePlacesModelTest::createPlacesModels()
0126 {
0127     KBookmarkManager *mgr = KBookmarkManager::managerForExternalFile(bookmarksFile());
0128     QSignalSpy spy(mgr, &KBookmarkManager::changed);
0129     m_places = new KFilePlacesModel();
0130     m_places2 = new KFilePlacesModel();
0131 
0132     // When the xbel file is empty, KFilePlacesModel fills it with 3 default items
0133     // 5 when KIO worker recentlyused:/ is installed
0134     QCOMPARE(m_places->rowCount(), m_hasRecentlyUsedKio ? 5 : 3);
0135 
0136     QVERIFY(spy.wait());
0137 
0138     // Devices have a delayed loading. Waiting for KDirWatch also waits for that to happen
0139     QCOMPARE(m_places->rowCount(), m_hasRecentlyUsedKio ? 10 : 8);
0140 }
0141 
0142 void KFilePlacesModelTest::cleanupTestCase()
0143 {
0144     delete m_places;
0145     delete m_places2;
0146     qDeleteAll(m_interfacesMap);
0147     QFile::remove(bookmarksFile());
0148 }
0149 
0150 QStringList KFilePlacesModelTest::placesUrls(KFilePlacesModel *model) const
0151 {
0152     KFilePlacesModel *currentModel = model;
0153     if (!currentModel) {
0154         currentModel = m_places;
0155     }
0156     QStringList urls;
0157     for (int row = 0; row < currentModel->rowCount(); ++row) {
0158         QModelIndex index = currentModel->index(row, 0);
0159         urls << currentModel->url(index).toDisplayString(QUrl::PreferLocalFile);
0160     }
0161     return urls;
0162 }
0163 
0164 /* clang-format off */
0165 #define CHECK_PLACES_URLS(urls) \
0166     if (placesUrls() != urls) { \
0167         qDebug() << "Expected:" << urls; \
0168         qDebug() << "Got:" << placesUrls(); \
0169         QCOMPARE(placesUrls(), urls); \
0170     } \
0171     for (int row = 0; row < urls.size(); ++row) { \
0172         QModelIndex index = m_places->index(row, 0); \
0173         \
0174         QCOMPARE(m_places->url(index).toString(), QUrl::fromUserInput(urls[row]).toString()); \
0175         QCOMPARE(m_places->data(index, KFilePlacesModel::UrlRole).toUrl(), QUrl(m_places->url(index))); \
0176         \
0177         index = m_places2->index(row, 0);                                                               \
0178         \
0179         QCOMPARE(m_places2->url(index).toString(), QUrl::fromUserInput(urls[row]).toString()); \
0180         QCOMPARE(m_places2->data(index, KFilePlacesModel::UrlRole).toUrl(), QUrl(m_places2->url(index))); \
0181     } \
0182     \
0183     QCOMPARE(urls.size(), m_places->rowCount()); \
0184     QCOMPARE(urls.size(), m_places2->rowCount());
0185 /*clang-format on */
0186 
0187 QDBusInterface *KFilePlacesModelTest::fakeManager()
0188 {
0189     return fakeDevice(QStringLiteral("/org/kde/solid/fakehw"));
0190 }
0191 
0192 QDBusInterface *KFilePlacesModelTest::fakeDevice(const QString &udi)
0193 {
0194     QDBusInterface *interface = m_interfacesMap[udi];
0195     if (interface) {
0196         return interface;
0197     }
0198 
0199     QDBusInterface *iface = new QDBusInterface(QDBusConnection::sessionBus().baseService(), udi);
0200     m_interfacesMap[udi] = iface;
0201 
0202     return iface;
0203 }
0204 
0205 static const QStringList initialListOfPlaces()
0206 {
0207     return QStringList{QDir::homePath(), QStringLiteral("trash:/")};
0208 }
0209 
0210 static const QStringList initialListOfShared()
0211 {
0212     return QStringList{QStringLiteral("remote:/"), QStringLiteral("/media/nfs")};
0213 }
0214 
0215 static const QStringList initialListOfRecent()
0216 {
0217     auto list = QStringList();
0218     if (qEnvironmentVariableIsSet("KDE_FULL_SESSION") && KProtocolInfo::isKnownProtocol(QStringLiteral("recentlyused"))) {
0219         list << QStringLiteral("recentlyused:/files");
0220         list << QStringLiteral("recentlyused:/locations");
0221     }
0222     return list;
0223 }
0224 
0225 static const QStringList initialListOfDevices()
0226 {
0227     return QStringList{QStringLiteral("/foreign")};
0228 }
0229 
0230 static const QStringList initialListOfRemovableDevices()
0231 {
0232     return QStringList{QStringLiteral("/media/floppy0"), QStringLiteral("/media/XO-Y4"), QStringLiteral("/media/cdrom")};
0233 }
0234 
0235 static const QStringList initialListOfUrls()
0236 {
0237     return QStringList() << initialListOfPlaces() << initialListOfShared() << initialListOfRecent() << initialListOfDevices()
0238                          << initialListOfRemovableDevices();
0239 }
0240 
0241 void KFilePlacesModelTest::testInitialList()
0242 {
0243     const QStringList urls = initialListOfUrls();
0244     CHECK_PLACES_URLS(urls);
0245 }
0246 
0247 void KFilePlacesModelTest::testAddingInLaterVersion_data()
0248 {
0249     QTest::addColumn<QByteArray>("contents");
0250     QTest::addColumn<QStringList>("expectedUrls");
0251 
0252     // Create a places file with only Home in it, and no version number
0253     static const char contentsPart1[] =
0254         "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
0255         "<xbel xmlns:bookmark=\"http://www.freedesktop.org/standards/desktop-bookmarks\">\n";
0256 
0257     static const char versionXml[] =
0258         "  <info>\n"
0259         "   <metadata owner=\"http://www.kde.org\">\n"
0260         "    <kde_places_version>2</kde_places_version>\n"
0261         "   </metadata>\n"
0262         "  </info>\n";
0263 
0264     static const char contentsPart2[] =
0265         " <bookmark href=\"trash:/\">\n"
0266         "  <title>Home</title>\n"
0267         "  <info>\n"
0268         "   <metadata owner=\"http://freedesktop.org\">\n"
0269         "    <bookmark:icon name=\"user-home\"/>\n"
0270         "   </metadata>\n"
0271         "   <metadata owner=\"http://www.kde.org\">\n"
0272         "    <ID>1481703882/0</ID>\n"
0273         "    <isSystemItem>true</isSystemItem>\n"
0274         "   </metadata>\n"
0275         "  </info>\n"
0276         " </bookmark>\n"
0277         "</xbel>";
0278 
0279     // No version key: KFilePlacesModel will add the missing entries: home and remote
0280     // Just not in the usual order
0281     QStringList expectedWithReorder = initialListOfUrls();
0282     expectedWithReorder.move(1, 0);
0283     QTest::newRow("just_home_no_version") << (QByteArray(contentsPart1) + contentsPart2) << expectedWithReorder;
0284 
0285     // Existing version key: home and remote were removed by the user, leave them out
0286     QStringList expectedUrls{QStringLiteral("trash:/")};
0287     expectedUrls << initialListOfShared() << initialListOfRecent() << initialListOfDevices() << initialListOfRemovableDevices();
0288     QVERIFY(expectedUrls.removeOne(QStringLiteral("remote:/")));
0289     QTest::newRow("just_home_version_2") << (QByteArray(contentsPart1) + versionXml + contentsPart2) << expectedUrls;
0290 }
0291 
0292 void KFilePlacesModelTest::testAddingInLaterVersion()
0293 {
0294     QFETCH(QByteArray, contents);
0295     QFETCH(QStringList, expectedUrls);
0296 
0297     // Avoid interference
0298     delete m_places;
0299     delete m_places2;
0300     QCoreApplication::processEvents();
0301 
0302     KBookmarkManager *mgr = KBookmarkManager::managerForExternalFile(bookmarksFile());
0303 
0304     auto cleanupFunc = [this, mgr]() {
0305         QFile::remove(bookmarksFile());
0306         // let KDirWatch process the deletion
0307         QTRY_VERIFY(mgr->root().first().isNull());
0308         createPlacesModels();
0309         testInitialList();
0310     };
0311     struct Cleanup {
0312         explicit Cleanup(const std::function<void()> &f)
0313             : func(f)
0314         {
0315         }
0316         ~Cleanup()
0317         {
0318             func();
0319         }
0320         std::function<void()> func;
0321     } cleanup(cleanupFunc);
0322 
0323     QTest::qWait(1000); // for KDirWatch
0324     QSignalSpy spy(mgr, &KBookmarkManager::changed);
0325 
0326     // WHEN
0327     QFile file(bookmarksFile());
0328     QVERIFY(file.open(QIODevice::WriteOnly));
0329     file.write(contents);
0330     file.close();
0331     QVERIFY(spy.wait());
0332 
0333     // THEN
0334     KFilePlacesModel model;
0335     QCoreApplication::processEvents(); // Devices have a delayed loading
0336 
0337     if (placesUrls(&model) != expectedUrls) {
0338         qDebug() << "Expected:" << expectedUrls;
0339         qDebug() << "Got:" << placesUrls(&model);
0340         QCOMPARE(placesUrls(&model), expectedUrls);
0341     }
0342 }
0343 
0344 void KFilePlacesModelTest::testReparse()
0345 {
0346     // add item
0347     m_places->addPlace(QStringLiteral("foo"), QUrl::fromLocalFile(QStringLiteral("/foo")), QString(), QString());
0348 
0349     QStringList urls = initialListOfUrls();
0350     // it will be added at the end of places section
0351     urls.insert(2, QStringLiteral("/foo"));
0352     CHECK_PLACES_URLS(urls);
0353 
0354     // reparse the bookmark file
0355     KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces"));
0356     bookmarkManager->notifyCompleteChange(QString());
0357 
0358     // check if they are the same
0359     CHECK_PLACES_URLS(urls);
0360 
0361     // try to remove item
0362     m_places->removePlace(m_places->index(2, 0));
0363 
0364     urls = initialListOfUrls();
0365     CHECK_PLACES_URLS(urls);
0366 }
0367 
0368 void KFilePlacesModelTest::testInternalBookmarksHaveIds()
0369 {
0370     KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces"));
0371     KBookmarkGroup root = bookmarkManager->root();
0372 
0373     // Verify every entry has an id or an udi
0374     KBookmark bookmark = root.first();
0375     while (!bookmark.isNull()) {
0376         QVERIFY(!bookmark.metaDataItem(QStringLiteral("ID")).isEmpty() || !bookmark.metaDataItem(QStringLiteral("UDI")).isEmpty());
0377         // It's mutually exclusive though
0378         QVERIFY(bookmark.metaDataItem(QStringLiteral("ID")).isEmpty() || bookmark.metaDataItem(QStringLiteral("UDI")).isEmpty());
0379 
0380         bookmark = root.next(bookmark);
0381     }
0382 
0383     // Verify that adding a bookmark behind its back the model gives it an id
0384     // (in real life it requires the user to modify the file by hand,
0385     // unlikely but better safe than sorry).
0386     // It induces a small race condition which means several ids will be
0387     // successively set on the same bookmark but no big deal since it won't
0388     // break the system
0389     KBookmark foo = root.addBookmark(QStringLiteral("Foo"), QUrl(QStringLiteral("file:/foo")), QStringLiteral("red-folder"));
0390     QCOMPARE(foo.text(), QStringLiteral("Foo"));
0391     QVERIFY(foo.metaDataItem(QStringLiteral("ID")).isEmpty());
0392     bookmarkManager->emitChanged(root);
0393     QCOMPARE(foo.text(), QStringLiteral("Foo"));
0394     QVERIFY(!foo.metaDataItem(QStringLiteral("ID")).isEmpty());
0395 
0396     // Verify that all the ids are different
0397     bookmark = root.first();
0398     QSet<QString> ids;
0399     while (!bookmark.isNull()) {
0400         QString id;
0401         if (!bookmark.metaDataItem(QStringLiteral("UDI")).isEmpty()) {
0402             id = bookmark.metaDataItem(QStringLiteral("UDI"));
0403         } else {
0404             id = bookmark.metaDataItem(QStringLiteral("ID"));
0405         }
0406 
0407         if (ids.contains(id)) {
0408             // Some debugging help
0409             qDebug() << "Bookmarks file:" << bookmarksFile() << "contains one (or more) duplicated bookmarks:";
0410             QFile debugFile(bookmarksFile());
0411             QVERIFY(debugFile.open(QIODevice::ReadOnly));
0412             qDebug() << QString::fromUtf8(debugFile.readAll());
0413             QVERIFY2(!ids.contains(id), qPrintable("Duplicated ID found: " + id));
0414         }
0415         ids << id;
0416         bookmark = root.next(bookmark);
0417     }
0418 
0419     // Cleanup foo
0420     root.deleteBookmark(foo);
0421     bookmarkManager->emitChanged(root);
0422 }
0423 
0424 void KFilePlacesModelTest::testHiding()
0425 {
0426     // Verify that nothing is hidden
0427     for (int row = 0; row < m_places->rowCount(); ++row) {
0428         QModelIndex index = m_places->index(row, 0);
0429         QVERIFY(!m_places->isHidden(index));
0430     }
0431 
0432     QModelIndex a = m_places->index(2, 0);
0433     QModelIndex b = m_places->index(6, 0);
0434 
0435     QList<QVariant> args;
0436     QSignalSpy spy(m_places, &QAbstractItemModel::dataChanged);
0437 
0438     // Verify that hidden is taken into account and is not global
0439     m_places->setPlaceHidden(a, true);
0440     QVERIFY(m_places->isHidden(a));
0441     QVERIFY(m_places->data(a, KFilePlacesModel::HiddenRole).toBool());
0442     QVERIFY(!m_places->isHidden(b));
0443     QVERIFY(!m_places->data(b, KFilePlacesModel::HiddenRole).toBool());
0444     QCOMPARE(spy.count(), 1);
0445     args = spy.takeFirst();
0446     QCOMPARE(args.at(0).toModelIndex(), a);
0447     QCOMPARE(args.at(1).toModelIndex(), a);
0448 
0449     m_places->setPlaceHidden(b, true);
0450     QVERIFY(m_places->isHidden(a));
0451     QVERIFY(m_places->data(a, KFilePlacesModel::HiddenRole).toBool());
0452     QVERIFY(m_places->isHidden(b));
0453     QVERIFY(m_places->data(b, KFilePlacesModel::HiddenRole).toBool());
0454     QCOMPARE(spy.count(), 1);
0455     args = spy.takeFirst();
0456     QCOMPARE(args.at(0).toModelIndex(), b);
0457     QCOMPARE(args.at(1).toModelIndex(), b);
0458 
0459     m_places->setPlaceHidden(a, false);
0460     m_places->setPlaceHidden(b, false);
0461     QVERIFY(!m_places->isHidden(a));
0462     QVERIFY(!m_places->data(a, KFilePlacesModel::HiddenRole).toBool());
0463     QVERIFY(!m_places->isHidden(b));
0464     QVERIFY(!m_places->data(b, KFilePlacesModel::HiddenRole).toBool());
0465     QCOMPARE(spy.count(), 2);
0466     args = spy.takeFirst();
0467     QCOMPARE(args.at(0).toModelIndex(), a);
0468     QCOMPARE(args.at(1).toModelIndex(), a);
0469     args = spy.takeFirst();
0470     QCOMPARE(args.at(0).toModelIndex(), b);
0471     QCOMPARE(args.at(1).toModelIndex(), b);
0472 }
0473 
0474 void KFilePlacesModelTest::testMove()
0475 {
0476     QList<QVariant> args;
0477     QSignalSpy spy_inserted(m_places, &QAbstractItemModel::rowsInserted);
0478     QSignalSpy spy_removed(m_places, &QAbstractItemModel::rowsRemoved);
0479 
0480     KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces"));
0481     KBookmarkGroup root = bookmarkManager->root();
0482 
0483     KBookmark system_home = m_places->bookmarkForIndex(m_places->index(0, 0));
0484 
0485     // Trying move the root at the end of the list, should move it to the end of places section instead
0486     // to keep it grouped
0487     KBookmark last = root.first();
0488     while (!root.next(last).isNull()) {
0489         last = root.next(last);
0490     }
0491     root.moveBookmark(system_home, last);
0492     bookmarkManager->emitChanged(root);
0493 
0494     QStringList urls;
0495     urls << QStringLiteral("trash:/") << QDir::homePath() << initialListOfShared() << initialListOfRecent() << initialListOfDevices()
0496          << initialListOfRemovableDevices();
0497 
0498     CHECK_PLACES_URLS(urls);
0499     QCOMPARE(spy_inserted.count(), 1);
0500     args = spy_inserted.takeFirst();
0501     QCOMPARE(args.at(0).toModelIndex(), QModelIndex());
0502     QCOMPARE(args.at(1).toInt(), 1);
0503     QCOMPARE(args.at(2).toInt(), 1);
0504     QCOMPARE(spy_removed.count(), 1);
0505     args = spy_removed.takeFirst();
0506     QCOMPARE(args.at(0).toModelIndex(), QModelIndex());
0507     QCOMPARE(args.at(1).toInt(), 0);
0508     QCOMPARE(args.at(2).toInt(), 0);
0509 
0510     // Move home at the beginning of the list (at its original place)
0511     root.moveBookmark(system_home, KBookmark());
0512     bookmarkManager->emitChanged(root);
0513     urls.clear();
0514     urls << initialListOfPlaces() << initialListOfShared() << initialListOfRecent() << initialListOfDevices() << initialListOfRemovableDevices();
0515     CHECK_PLACES_URLS(urls);
0516     QCOMPARE(spy_inserted.count(), 1);
0517     args = spy_inserted.takeFirst();
0518     QCOMPARE(args.at(0).toModelIndex(), QModelIndex());
0519     QCOMPARE(args.at(1).toInt(), 1);
0520     QCOMPARE(args.at(2).toInt(), 1);
0521     QCOMPARE(spy_removed.count(), 1);
0522     args = spy_removed.takeFirst();
0523     QCOMPARE(args.at(0).toModelIndex(), QModelIndex());
0524     QCOMPARE(args.at(1).toInt(), 0);
0525     QCOMPARE(args.at(2).toInt(), 0);
0526 }
0527 
0528 void KFilePlacesModelTest::testDragAndDrop()
0529 {
0530     QList<QVariant> args;
0531     QSignalSpy spy_moved(m_places, &QAbstractItemModel::rowsMoved);
0532 
0533     // Monitor rowsInserted() and rowsRemoved() to ensure they are never emitted:
0534     // Moving with drag and drop is expected to emit rowsMoved()
0535     QSignalSpy spy_inserted(m_places, &QAbstractItemModel::rowsInserted);
0536     QSignalSpy spy_removed(m_places, &QAbstractItemModel::rowsRemoved);
0537 
0538     // Move /home at the end of the places list
0539     QModelIndexList indexes;
0540     indexes << m_places->index(0, 0);
0541     QMimeData *mimeData = m_places->mimeData(indexes);
0542     QVERIFY(m_places->dropMimeData(mimeData, Qt::MoveAction, 2, 0, QModelIndex()));
0543 
0544     QStringList urls;
0545     urls << QStringLiteral("trash:/") << QDir::homePath() << initialListOfShared() << initialListOfRecent() << initialListOfDevices()
0546          << initialListOfRemovableDevices();
0547     CHECK_PLACES_URLS(urls);
0548     QCOMPARE(spy_inserted.count(), 0);
0549     QCOMPARE(spy_removed.count(), 0);
0550     QCOMPARE(spy_moved.count(), 1);
0551     args = spy_moved.takeFirst();
0552     QCOMPARE(args.at(0).toModelIndex(), QModelIndex());
0553     QCOMPARE(args.at(1).toInt(), 0);
0554     QCOMPARE(args.at(2).toInt(), 0);
0555     QCOMPARE(args.at(3).toModelIndex(), QModelIndex());
0556     QCOMPARE(args.at(4).toInt(), 2);
0557 
0558     // Move home back at the beginning of the list
0559     indexes.clear();
0560     indexes << m_places->index(1, 0);
0561     mimeData = m_places->mimeData(indexes);
0562     QVERIFY(m_places->dropMimeData(mimeData, Qt::MoveAction, 0, 0, QModelIndex()));
0563 
0564     urls.clear();
0565     urls << QDir::homePath() << QStringLiteral("trash:/") << initialListOfShared() << initialListOfRecent() << initialListOfDevices()
0566          << initialListOfRemovableDevices();
0567     CHECK_PLACES_URLS(urls);
0568     QCOMPARE(spy_inserted.count(), 0);
0569     QCOMPARE(spy_removed.count(), 0);
0570     QCOMPARE(spy_moved.count(), 1);
0571     args = spy_moved.takeFirst();
0572     QCOMPARE(args.at(0).toModelIndex(), QModelIndex());
0573     QCOMPARE(args.at(1).toInt(), 1);
0574     QCOMPARE(args.at(2).toInt(), 1);
0575     QCOMPARE(args.at(3).toModelIndex(), QModelIndex());
0576     QCOMPARE(args.at(4).toInt(), 0);
0577 
0578     // Dropping on an item is not allowed
0579     indexes.clear();
0580     indexes << m_places->index(4, 0);
0581     mimeData = m_places->mimeData(indexes);
0582     QVERIFY(!m_places->dropMimeData(mimeData, Qt::MoveAction, -1, 0, m_places->index(2, 0)));
0583     CHECK_PLACES_URLS(urls);
0584     QCOMPARE(spy_inserted.count(), 0);
0585     QCOMPARE(spy_removed.count(), 0);
0586     QCOMPARE(spy_moved.count(), 0);
0587 }
0588 
0589 void KFilePlacesModelTest::testPlacesLifecycle()
0590 {
0591     QList<QVariant> args;
0592     QSignalSpy spy_inserted(m_places, &QAbstractItemModel::rowsInserted);
0593     QSignalSpy spy_removed(m_places, &QAbstractItemModel::rowsRemoved);
0594     QSignalSpy spy_changed(m_places, &QAbstractItemModel::dataChanged);
0595 
0596     m_places->addPlace(QStringLiteral("Foo"), QUrl::fromLocalFile(QStringLiteral("/home/foo")));
0597 
0598     QStringList urls;
0599     urls << initialListOfPlaces() << QStringLiteral("/home/foo") << initialListOfShared() << initialListOfRecent() << initialListOfDevices()
0600          << initialListOfRemovableDevices();
0601     CHECK_PLACES_URLS(urls);
0602     QCOMPARE(spy_inserted.count(), 1);
0603     args = spy_inserted.takeFirst();
0604     QCOMPARE(args.at(0).toModelIndex(), QModelIndex());
0605     QCOMPARE(args.at(1).toInt(), 2);
0606     QCOMPARE(args.at(2).toInt(), 2);
0607     QCOMPARE(spy_removed.count(), 0);
0608 
0609     KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces"));
0610     KBookmarkGroup root = bookmarkManager->root();
0611     KBookmark before_trash = m_places->bookmarkForIndex(m_places->index(0, 0));
0612     KBookmark foo = m_places->bookmarkForIndex(m_places->index(2, 0));
0613 
0614     root.moveBookmark(foo, before_trash);
0615     bookmarkManager->emitChanged(root);
0616 
0617     urls.clear();
0618     urls << QDir::homePath() << QStringLiteral("/home/foo") << QStringLiteral("trash:/") << initialListOfShared() << initialListOfRecent()
0619          << initialListOfDevices() << initialListOfRemovableDevices();
0620     CHECK_PLACES_URLS(urls);
0621     QCOMPARE(spy_inserted.count(), 1);
0622     args = spy_inserted.takeFirst();
0623     QCOMPARE(args.at(0).toModelIndex(), QModelIndex());
0624     QCOMPARE(args.at(1).toInt(), 2);
0625     QCOMPARE(args.at(2).toInt(), 2);
0626     QCOMPARE(spy_removed.count(), 1);
0627     args = spy_removed.takeFirst();
0628     QCOMPARE(args.at(0).toModelIndex(), QModelIndex());
0629     QCOMPARE(args.at(1).toInt(), 1);
0630     QCOMPARE(args.at(2).toInt(), 1);
0631 
0632     m_places->editPlace(m_places->index(1, 0), QStringLiteral("Foo"), QUrl::fromLocalFile(QStringLiteral("/mnt/foo")));
0633 
0634     urls.clear();
0635     urls << QDir::homePath() << QStringLiteral("/mnt/foo") << QStringLiteral("trash:/") << initialListOfShared() << initialListOfRecent()
0636          << initialListOfDevices() << initialListOfRemovableDevices();
0637     CHECK_PLACES_URLS(urls);
0638     QCOMPARE(spy_inserted.count(), 0);
0639     QCOMPARE(spy_removed.count(), 0);
0640     QCOMPARE(spy_changed.count(), 1);
0641     args = spy_changed.takeFirst();
0642     QCOMPARE(args.at(0).toModelIndex(), m_places->index(1, 0));
0643     QCOMPARE(args.at(1).toModelIndex(), m_places->index(1, 0));
0644 
0645     foo = m_places->bookmarkForIndex(m_places->index(1, 0));
0646     foo.setFullText(QStringLiteral("Bar"));
0647     bookmarkManager->notifyCompleteChange(QString());
0648 
0649     urls.clear();
0650     urls << QDir::homePath() << QStringLiteral("/mnt/foo") << QStringLiteral("trash:/") << initialListOfShared() << initialListOfRecent()
0651          << initialListOfDevices() << initialListOfRemovableDevices();
0652 
0653     CHECK_PLACES_URLS(urls);
0654     QCOMPARE(spy_inserted.count(), 0);
0655     QCOMPARE(spy_removed.count(), 0);
0656     QCOMPARE(spy_changed.count(), m_hasRecentlyUsedKio ? 11 : 9);
0657     args = spy_changed[2];
0658     QCOMPARE(args.at(0).toModelIndex(), m_places->index(2, 0));
0659     QCOMPARE(args.at(1).toModelIndex(), m_places->index(2, 0));
0660     spy_changed.clear();
0661 
0662     m_places->removePlace(m_places->index(1, 0));
0663 
0664     urls.clear();
0665     urls << initialListOfPlaces() << initialListOfShared() << initialListOfRecent() << initialListOfDevices() << initialListOfRemovableDevices();
0666     CHECK_PLACES_URLS(urls);
0667     QCOMPARE(spy_inserted.count(), 0);
0668     QCOMPARE(spy_removed.count(), 1);
0669     args = spy_removed.takeFirst();
0670     QCOMPARE(args.at(0).toModelIndex(), QModelIndex());
0671     QCOMPARE(args.at(1).toInt(), 1);
0672     QCOMPARE(args.at(2).toInt(), 1);
0673 
0674     m_places->addPlace(QStringLiteral("Foo"), QUrl::fromLocalFile(QStringLiteral("/home/foo")), QString(), QString(), m_places->index(0, 0));
0675 
0676     urls.clear();
0677     urls << QDir::homePath() << QStringLiteral("/home/foo") << QStringLiteral("trash:/") << initialListOfShared() << initialListOfRecent()
0678          << initialListOfDevices() << initialListOfRemovableDevices();
0679     CHECK_PLACES_URLS(urls);
0680     QCOMPARE(spy_inserted.count(), 1);
0681     args = spy_inserted.takeFirst();
0682     QCOMPARE(args.at(0).toModelIndex(), QModelIndex());
0683     QCOMPARE(args.at(1).toInt(), 1);
0684     QCOMPARE(args.at(2).toInt(), 1);
0685     QCOMPARE(spy_removed.count(), 0);
0686 
0687     m_places->removePlace(m_places->index(1, 0));
0688 }
0689 
0690 void KFilePlacesModelTest::testDevicePlugging()
0691 {
0692     QList<QVariant> args;
0693     QSignalSpy spy_inserted(m_places, &QAbstractItemModel::rowsInserted);
0694     QSignalSpy spy_removed(m_places, &QAbstractItemModel::rowsRemoved);
0695 
0696     fakeManager()->call(QStringLiteral("unplug"), "/org/kde/solid/fakehw/volume_part1_size_993284096");
0697 
0698     QStringList urls;
0699     urls << initialListOfPlaces() << initialListOfShared() << initialListOfRecent() << initialListOfDevices() << QStringLiteral("/media/floppy0")
0700          << QStringLiteral("/media/cdrom");
0701     CHECK_PLACES_URLS(urls);
0702     QCOMPARE(spy_inserted.count(), 0);
0703     QCOMPARE(spy_removed.count(), 1);
0704     args = spy_removed.takeFirst();
0705     QCOMPARE(args.at(0).toModelIndex(), QModelIndex());
0706     QCOMPARE(args.at(1).toInt(), m_hasRecentlyUsedKio ? 8 : 6);
0707     QCOMPARE(args.at(2).toInt(), m_hasRecentlyUsedKio ? 8 : 6);
0708 
0709     fakeManager()->call(QStringLiteral("plug"), "/org/kde/solid/fakehw/volume_part1_size_993284096");
0710 
0711     urls.clear();
0712     urls << initialListOfPlaces() << initialListOfShared() << initialListOfRecent() << initialListOfDevices() << initialListOfRemovableDevices();
0713     CHECK_PLACES_URLS(urls);
0714     QCOMPARE(spy_inserted.count(), 1);
0715     args = spy_inserted.takeFirst();
0716     QCOMPARE(args.at(0).toModelIndex(), QModelIndex());
0717     QCOMPARE(args.at(1).toInt(), m_hasRecentlyUsedKio ? 8 : 6);
0718     QCOMPARE(args.at(2).toInt(), m_hasRecentlyUsedKio ? 8 : 6);
0719     QCOMPARE(spy_removed.count(), 0);
0720 
0721     // Move the device in the list, and check that it memorizes the position across plug/unplug
0722 
0723     KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces"));
0724     KBookmarkGroup root = bookmarkManager->root();
0725     KBookmark before_floppy;
0726 
0727     KBookmark device = root.first(); // The device we'll move is the 6th bookmark
0728     const int count = m_hasRecentlyUsedKio ? 7 : 5;
0729     for (int i = 0; i < count; i++) {
0730         if (i == 2) {
0731             // store item before to be able to move it back to original position
0732             device = before_floppy = root.next(device);
0733         } else {
0734             device = root.next(device);
0735         }
0736     }
0737 
0738     root.moveBookmark(device, before_floppy);
0739     bookmarkManager->emitChanged(root);
0740 
0741     urls.clear();
0742     urls << initialListOfPlaces() << initialListOfShared() << initialListOfRecent() << initialListOfDevices() << QStringLiteral("/media/XO-Y4")
0743          << QStringLiteral("/media/floppy0") << QStringLiteral("/media/cdrom");
0744     CHECK_PLACES_URLS(urls);
0745     QCOMPARE(spy_inserted.count(), 1);
0746     args = spy_inserted.takeFirst();
0747     QCOMPARE(args.at(0).toModelIndex(), QModelIndex());
0748     QCOMPARE(args.at(1).toInt(), m_hasRecentlyUsedKio ? 8 : 6);
0749     QCOMPARE(args.at(2).toInt(), m_hasRecentlyUsedKio ? 8 : 6);
0750     QCOMPARE(spy_removed.count(), 1);
0751     args = spy_removed.takeFirst();
0752     QCOMPARE(args.at(0).toModelIndex(), QModelIndex());
0753     QCOMPARE(args.at(1).toInt(), m_hasRecentlyUsedKio ? 7 : 5);
0754     QCOMPARE(args.at(2).toInt(), m_hasRecentlyUsedKio ? 7 : 5);
0755 
0756     fakeManager()->call(QStringLiteral("unplug"), "/org/kde/solid/fakehw/volume_part1_size_993284096");
0757 
0758     urls.clear();
0759     urls << initialListOfPlaces() << initialListOfShared() << initialListOfRecent() << initialListOfDevices() << QStringLiteral("/media/floppy0")
0760          << QStringLiteral("/media/cdrom");
0761     CHECK_PLACES_URLS(urls);
0762     QCOMPARE(spy_inserted.count(), 0);
0763     QCOMPARE(spy_removed.count(), 1);
0764     args = spy_removed.takeFirst();
0765     QCOMPARE(args.at(0).toModelIndex(), QModelIndex());
0766     QCOMPARE(args.at(1).toInt(), m_hasRecentlyUsedKio ? 7 : 5);
0767     QCOMPARE(args.at(2).toInt(), m_hasRecentlyUsedKio ? 7 : 5);
0768 
0769     fakeManager()->call(QStringLiteral("plug"), "/org/kde/solid/fakehw/volume_part1_size_993284096");
0770 
0771     urls.clear();
0772     urls << initialListOfPlaces() << initialListOfShared() << initialListOfRecent() << initialListOfDevices() << QStringLiteral("/media/XO-Y4")
0773          << QStringLiteral("/media/floppy0") << QStringLiteral("/media/cdrom");
0774     CHECK_PLACES_URLS(urls);
0775     QCOMPARE(spy_inserted.count(), 1);
0776     args = spy_inserted.takeFirst();
0777     QCOMPARE(args.at(0).toModelIndex(), QModelIndex());
0778     QCOMPARE(args.at(1).toInt(), m_hasRecentlyUsedKio ? 7 : 5);
0779     QCOMPARE(args.at(2).toInt(), m_hasRecentlyUsedKio ? 7 : 5);
0780     QCOMPARE(spy_removed.count(), 0);
0781 
0782     KBookmark seventh = root.first();
0783     for (int i = 0; i < count; i++) {
0784         seventh = root.next(seventh);
0785     }
0786     root.moveBookmark(device, seventh);
0787     bookmarkManager->emitChanged(root);
0788 
0789     urls.clear();
0790     urls << initialListOfPlaces() << initialListOfShared() << initialListOfRecent() << initialListOfDevices() << initialListOfRemovableDevices();
0791     CHECK_PLACES_URLS(urls);
0792     QCOMPARE(spy_inserted.count(), 1);
0793     args = spy_inserted.takeFirst();
0794     QCOMPARE(args.at(0).toModelIndex(), QModelIndex());
0795     QCOMPARE(args.at(1).toInt(), m_hasRecentlyUsedKio ? 8 : 6);
0796     QCOMPARE(args.at(2).toInt(), m_hasRecentlyUsedKio ? 8 : 6);
0797     QCOMPARE(spy_removed.count(), 1);
0798     args = spy_removed.takeFirst();
0799     QCOMPARE(args.at(0).toModelIndex(), QModelIndex());
0800     QCOMPARE(args.at(1).toInt(), m_hasRecentlyUsedKio ? 7 : 5);
0801     QCOMPARE(args.at(2).toInt(), m_hasRecentlyUsedKio ? 7 : 5);
0802 }
0803 
0804 void KFilePlacesModelTest::testDeviceSetupTeardown()
0805 {
0806     QList<QVariant> args;
0807     QSignalSpy spy_changed(m_places, &QAbstractItemModel::dataChanged);
0808 
0809     fakeDevice(QStringLiteral("/org/kde/solid/fakehw/volume_part1_size_993284096/StorageAccess"))->call(QStringLiteral("teardown"));
0810 
0811     QCOMPARE(spy_changed.count(), 1);
0812     args = spy_changed.takeFirst();
0813     QCOMPARE(args.at(0).toModelIndex().row(), m_hasRecentlyUsedKio ? 8 : 6);
0814     QCOMPARE(args.at(1).toModelIndex().row(), m_hasRecentlyUsedKio ? 8 : 6);
0815 
0816     fakeDevice(QStringLiteral("/org/kde/solid/fakehw/volume_part1_size_993284096/StorageAccess"))->call(QStringLiteral("setup"));
0817 
0818     QCOMPARE(spy_changed.count(), 1);
0819     args = spy_changed.takeFirst();
0820     QCOMPARE(args.at(0).toModelIndex().row(), m_hasRecentlyUsedKio ? 8 : 6);
0821     QCOMPARE(args.at(1).toModelIndex().row(), m_hasRecentlyUsedKio ? 8 : 6);
0822 }
0823 
0824 void KFilePlacesModelTest::testEnableBaloo()
0825 {
0826     KConfig config(QStringLiteral("baloofilerc"));
0827     KConfigGroup basicSettings = config.group("Basic Settings");
0828     basicSettings.writeEntry("Indexing-Enabled", true);
0829     config.sync();
0830 
0831     KFilePlacesModel places_with_baloo;
0832     QStringList urls;
0833     for (int row = 0; row < places_with_baloo.rowCount(); ++row) {
0834         QModelIndex index = places_with_baloo.index(row, 0);
0835         urls << places_with_baloo.url(index).toDisplayString(QUrl::PreferLocalFile);
0836     }
0837 
0838     if (m_hasRecentlyUsedKio) {
0839         QVERIFY(urls.contains("recentlyused:/files"));
0840         QVERIFY(urls.contains("recentlyused:/locations"));
0841     }
0842 }
0843 
0844 void KFilePlacesModelTest::testRemoteUrls_data()
0845 {
0846     QTest::addColumn<QUrl>("url");
0847     QTest::addColumn<int>("expectedRow");
0848     QTest::addColumn<QString>("expectedGroup");
0849 
0850     QTest::newRow("Ftp") << QUrl(QStringLiteral("ftp://192.168.1.1/ftp")) << 4 << QStringLiteral("Remote");
0851     QTest::newRow("Samba") << QUrl(QStringLiteral("smb://192.168.1.1/share")) << 4 << QStringLiteral("Remote");
0852     QTest::newRow("Sftp") << QUrl(QStringLiteral("sftp://192.168.1.1/share")) << 4 << QStringLiteral("Remote");
0853     QTest::newRow("Fish") << QUrl(QStringLiteral("fish://192.168.1.1/share")) << 4 << QStringLiteral("Remote");
0854     QTest::newRow("Webdav") << QUrl(QStringLiteral("webdav://192.168.1.1/share")) << 4 << QStringLiteral("Remote");
0855 }
0856 
0857 void KFilePlacesModelTest::testRemoteUrls()
0858 {
0859     QFETCH(QUrl, url);
0860     QFETCH(int, expectedRow);
0861     QFETCH(QString, expectedGroup);
0862 
0863     QList<QVariant> args;
0864     QSignalSpy spy_inserted(m_places, &QAbstractItemModel::rowsInserted);
0865 
0866     // insert a new network url
0867     m_places->addPlace(QStringLiteral("My Shared"), url, QString(), QString(), QModelIndex());
0868 
0869     // check if url list is correct after insertion
0870     QStringList urls;
0871     urls << QDir::homePath() << QStringLiteral("trash:/") // places
0872          << QStringLiteral("remote:/") << QStringLiteral("/media/nfs") << url.toString() << initialListOfRecent() << QStringLiteral("/foreign")
0873          << QStringLiteral("/media/floppy0") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/cdrom");
0874     CHECK_PLACES_URLS(urls);
0875 
0876     // check if the new url was inserted in the right position (end of "Remote" section)
0877     QTRY_COMPARE(spy_inserted.count(), 1);
0878     args = spy_inserted.takeFirst();
0879     QCOMPARE(args.at(0).toModelIndex(), QModelIndex());
0880     QCOMPARE(args.at(1).toInt(), expectedRow);
0881     QCOMPARE(args.at(2).toInt(), expectedRow);
0882 
0883     // check if the new url has the right group "Remote"
0884     const QModelIndex index = m_places->index(expectedRow, 0);
0885     QCOMPARE(index.data(KFilePlacesModel::GroupRole).toString(), expectedGroup);
0886 
0887     m_places->removePlace(index);
0888 }
0889 
0890 void KFilePlacesModelTest::testRefresh()
0891 {
0892     KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces"));
0893     KBookmarkGroup root = bookmarkManager->root();
0894     KBookmark homePlace = root.first();
0895     const QModelIndex homePlaceIndex = m_places->index(0, 0);
0896 
0897     QCOMPARE(m_places->text(homePlaceIndex), homePlace.fullText());
0898 
0899     // modify bookmark
0900     homePlace.setFullText("Test change the text");
0901     QVERIFY(m_places->text(homePlaceIndex) != homePlace.fullText());
0902 
0903     // reload bookmark data
0904     m_places->refresh();
0905     QCOMPARE(m_places->text(homePlaceIndex), homePlace.fullText());
0906 }
0907 
0908 void KFilePlacesModelTest::testConvertedUrl_data()
0909 {
0910     QTest::addColumn<QUrl>("url");
0911     QTest::addColumn<QUrl>("expectedUrl");
0912 
0913     // places
0914     QTest::newRow("Places - Home") << QUrl::fromLocalFile(QDir::homePath()) << QUrl::fromLocalFile(QDir::homePath());
0915 
0916     // baloo -search
0917     QTest::newRow("Baloo - Documents") << QUrl("search:/documents") << QUrl("baloosearch:/documents");
0918 
0919     QTest::newRow("Baloo - Unknown Type") << QUrl("search:/unknown") << QUrl("search:/unknown");
0920 
0921     // baloo - timeline
0922     const QDate lastMonthDate = QDate::currentDate().addMonths(-1);
0923     QTest::newRow("Baloo - Last Month") << QUrl("timeline:/lastmonth")
0924                                         << QUrl(QString("timeline:/%1-%2").arg(lastMonthDate.year()).arg(lastMonthDate.month(), 2, 10, QLatin1Char('0')));
0925 
0926     // devices
0927     QTest::newRow("Devices - Floppy") << QUrl("file:///media/floppy0") << QUrl("file:///media/floppy0");
0928 }
0929 
0930 void KFilePlacesModelTest::testConvertedUrl()
0931 {
0932     QFETCH(QUrl, url);
0933     QFETCH(QUrl, expectedUrl);
0934 
0935     const QUrl convertedUrl = KFilePlacesModel::convertedUrl(url);
0936 
0937     QCOMPARE(convertedUrl.scheme(), expectedUrl.scheme());
0938     QCOMPARE(convertedUrl.path(), expectedUrl.path());
0939     QCOMPARE(convertedUrl, expectedUrl);
0940 }
0941 
0942 void KFilePlacesModelTest::testBookmarkObject()
0943 {
0944     // make sure that all items return a valid bookmark
0945     for (int row = 0; row < m_places->rowCount(); row++) {
0946         const QModelIndex index = m_places->index(row, 0);
0947         const KBookmark bookmark = m_places->bookmarkForIndex(index);
0948         QVERIFY(!bookmark.isNull());
0949     }
0950 }
0951 
0952 void KFilePlacesModelTest::testDataChangedSignal()
0953 {
0954     QSignalSpy dataChangedSpy(m_places, &KFilePlacesModel::dataChanged);
0955 
0956     const QModelIndex index = m_places->index(1, 0);
0957     const KBookmark bookmark = m_places->bookmarkForIndex(index);
0958 
0959     // call function with the same data
0960     m_places->editPlace(index, bookmark.fullText(), bookmark.url(), bookmark.icon(), bookmark.metaDataItem(QStringLiteral("OnlyInApp")));
0961     QCOMPARE(dataChangedSpy.count(), 0);
0962 
0963     // call function with different data
0964     const QString originalText = bookmark.fullText();
0965     m_places->editPlace(index, QStringLiteral("My text"), bookmark.url(), bookmark.icon(), bookmark.metaDataItem(QStringLiteral("OnlyInApp")));
0966     QCOMPARE(dataChangedSpy.count(), 1);
0967     QList<QVariant> args = dataChangedSpy.takeFirst();
0968     QCOMPARE(args.at(0).toModelIndex().row(), 1);
0969     QCOMPARE(args.at(0).toModelIndex().column(), 0);
0970     QCOMPARE(args.at(1).toModelIndex().row(), 1);
0971     QCOMPARE(args.at(1).toModelIndex().column(), 0);
0972     QCOMPARE(m_places->text(index), QStringLiteral("My text"));
0973 
0974     // restore original value
0975     dataChangedSpy.clear();
0976     m_places->editPlace(index, originalText, bookmark.url(), bookmark.icon(), bookmark.metaDataItem(QStringLiteral("OnlyInApp")));
0977     QCOMPARE(dataChangedSpy.count(), 1);
0978 }
0979 
0980 void KFilePlacesModelTest::testIconRole_data()
0981 {
0982     QTest::addColumn<QModelIndex>("index");
0983     QTest::addColumn<QString>("expectedIconName");
0984 
0985     // places
0986     int index = 0;
0987     QTest::newRow("Places - Home") << m_places->index(index++, 0) << QStringLiteral("user-home");
0988     QTest::newRow("Places - Trash") << m_places->index(index++, 0) << QStringLiteral("user-trash");
0989 
0990     QTest::newRow("Remote - Network") << m_places->index(index++, 0) << QStringLiteral("folder-network");
0991     QTest::newRow("Devices - Nfs") << m_places->index(index++, 0) << QStringLiteral("hwinfo");
0992     if (m_hasRecentlyUsedKio) {
0993         QTest::newRow("Recent Files") << m_places->index(index++, 0) << QStringLiteral("document-open-recent");
0994         QTest::newRow("Recent Locations") << m_places->index(index++, 0) << QStringLiteral("folder-open-recent");
0995     }
0996     QTest::newRow("Devices - foreign") << m_places->index(index++, 0) << QStringLiteral("blockdevice");
0997     QTest::newRow("Devices - Floppy") << m_places->index(index++, 0) << QStringLiteral("blockdevice");
0998     QTest::newRow("Devices - cdrom") << m_places->index(index++, 0) << QStringLiteral("blockdevice");
0999 }
1000 
1001 void KFilePlacesModelTest::testIconRole()
1002 {
1003     QFETCH(QModelIndex, index);
1004     QFETCH(QString, expectedIconName);
1005 
1006     QVERIFY(index.data(KFilePlacesModel::IconNameRole).toString().startsWith(expectedIconName));
1007 }
1008 
1009 void KFilePlacesModelTest::testMoveFunction()
1010 {
1011     QList<QVariant> args;
1012     QStringList urls = initialListOfUrls();
1013     QSignalSpy rowsMoved(m_places, &KFilePlacesModel::rowsMoved);
1014 
1015     // move item 0 to pos 1
1016     QVERIFY(m_places->movePlace(0, 2));
1017     urls.move(0, 1);
1018     QTRY_COMPARE(rowsMoved.count(), 1);
1019     args = rowsMoved.takeFirst();
1020     QCOMPARE(args.at(1).toInt(), 0); // start
1021     QCOMPARE(args.at(2).toInt(), 0); // end
1022     QCOMPARE(args.at(4).toInt(), 2); // row (destination)
1023     QCOMPARE(placesUrls(), urls);
1024     rowsMoved.clear();
1025 
1026     // move it back
1027     QVERIFY(m_places->movePlace(1, 0));
1028     urls.move(1, 0);
1029     QTRY_COMPARE(rowsMoved.count(), 1);
1030     args = rowsMoved.takeFirst();
1031     QCOMPARE(args.at(1).toInt(), 1); // start
1032     QCOMPARE(args.at(2).toInt(), 1); // end
1033     QCOMPARE(args.at(4).toInt(), 0); // row (destination)
1034     QCOMPARE(placesUrls(), urls);
1035     rowsMoved.clear();
1036 
1037     // target position is greater than model rows
1038     // will move to the end of the first group
1039     QVERIFY(m_places->movePlace(0, 20));
1040     urls.move(0, 1);
1041     QTRY_COMPARE(rowsMoved.count(), 1);
1042     args = rowsMoved.takeFirst();
1043     QCOMPARE(args.at(1).toInt(), 0); // start
1044     QCOMPARE(args.at(2).toInt(), 0); // end
1045     QCOMPARE(args.at(4).toInt(), 2); // row (destination)
1046     QCOMPARE(placesUrls(), urls);
1047     rowsMoved.clear();
1048 
1049     // move it back
1050     QVERIFY(m_places->movePlace(1, 0));
1051     urls.move(1, 0);
1052     QTRY_COMPARE(rowsMoved.count(), 1);
1053     args = rowsMoved.takeFirst();
1054     QCOMPARE(args.at(1).toInt(), 1); // start
1055     QCOMPARE(args.at(2).toInt(), 1); // end
1056     QCOMPARE(args.at(4).toInt(), 0); // row (destination)
1057     QCOMPARE(placesUrls(), urls);
1058     rowsMoved.clear();
1059 
1060     QVERIFY(m_places->movePlace(m_hasRecentlyUsedKio ? 8 : 7, m_hasRecentlyUsedKio ? 6 : 5));
1061     urls.move(m_hasRecentlyUsedKio ? 8 : 7, m_hasRecentlyUsedKio ? 6 : 5);
1062     QTRY_COMPARE(rowsMoved.count(), 1);
1063     args = rowsMoved.takeFirst();
1064     QCOMPARE(args.at(1).toInt(), m_hasRecentlyUsedKio ? 8 : 7); // start
1065     QCOMPARE(args.at(2).toInt(), m_hasRecentlyUsedKio ? 8 : 7); // end
1066     QCOMPARE(args.at(4).toInt(), m_hasRecentlyUsedKio ? 6 : 5); // row (destination)
1067     QCOMPARE(placesUrls(), urls);
1068     rowsMoved.clear();
1069 
1070     // move it back
1071     QVERIFY(m_places->movePlace(m_hasRecentlyUsedKio ? 6 : 5, m_hasRecentlyUsedKio ? 9 : 8));
1072     urls.move(m_hasRecentlyUsedKio ? 6 : 5, m_hasRecentlyUsedKio ? 8 : 7);
1073     QTRY_COMPARE(rowsMoved.count(), 1);
1074     args = rowsMoved.takeFirst();
1075     QCOMPARE(args.at(1).toInt(), m_hasRecentlyUsedKio ? 6 : 5); // start
1076     QCOMPARE(args.at(2).toInt(), m_hasRecentlyUsedKio ? 6 : 5); // end
1077     QCOMPARE(args.at(4).toInt(), m_hasRecentlyUsedKio ? 9 : 8); // row (destination)
1078     QCOMPARE(placesUrls(), urls);
1079     rowsMoved.clear();
1080 
1081     // use a invalid start position
1082     QVERIFY(!m_places->movePlace(100, 20));
1083     QCOMPARE(rowsMoved.count(), 0);
1084 
1085     // use same start and target position
1086     QVERIFY(!m_places->movePlace(1, 1));
1087     QCOMPARE(rowsMoved.count(), 0);
1088 }
1089 
1090 void KFilePlacesModelTest::testPlaceGroupHidden()
1091 {
1092     // GIVEN
1093     QCOMPARE(m_places->hiddenCount(), 0);
1094 
1095     QStringList urls;
1096     urls << initialListOfPlaces() << initialListOfShared() << initialListOfRecent() << initialListOfDevices() << initialListOfRemovableDevices();
1097     CHECK_PLACES_URLS(urls);
1098     QVector<QModelIndex> indexesHidden;
1099 
1100     // WHEN
1101     m_places->setGroupHidden(KFilePlacesModel::PlacesType, true);
1102 
1103     // THEN
1104     for (int row = 0; row < m_places->rowCount(); ++row) {
1105         QModelIndex index = m_places->index(row, 0);
1106         if (m_places->groupType(index) == KFilePlacesModel::PlacesType) {
1107             QVERIFY(m_places->isHidden(index));
1108             indexesHidden << index;
1109         }
1110     }
1111     QCOMPARE(indexesHidden.count(), initialListOfPlaces().size());
1112     QCOMPARE(m_places->hiddenCount(), indexesHidden.size());
1113 
1114     // and GIVEN
1115     QVector<QModelIndex> indexesShown;
1116 
1117     // WHEN
1118     m_places->setGroupHidden(KFilePlacesModel::PlacesType, false);
1119 
1120     // THEN
1121     for (int row = 0; row < m_places->rowCount(); ++row) {
1122         QModelIndex index = m_places->index(row, 0);
1123         if (m_places->groupType(index) == KFilePlacesModel::PlacesType) {
1124             QVERIFY(!m_places->isHidden(index));
1125             indexesShown << index;
1126         }
1127     }
1128     QCOMPARE(m_places->hiddenCount(), 0);
1129     QCOMPARE(indexesShown.count(), initialListOfPlaces().size());
1130 }
1131 
1132 void KFilePlacesModelTest::testPlaceGroupHiddenVsPlaceChildShown()
1133 {
1134     // GIVEN
1135     for (int row = 0; row < m_places->rowCount(); ++row) {
1136         QModelIndex index = m_places->index(row, 0);
1137         QVERIFY(!m_places->isHidden(index));
1138     }
1139     m_places->setGroupHidden(KFilePlacesModel::PlacesType, true);
1140 
1141     QModelIndex firstIndex = m_places->index(0, 0);
1142     const int amountOfPlaces = initialListOfPlaces().size();
1143     for (int row = 0; row < amountOfPlaces; ++row) {
1144         QModelIndex index = m_places->index(row, 0);
1145         QVERIFY(m_places->isHidden(index));
1146     }
1147     // WHEN
1148     m_places->setPlaceHidden(firstIndex, false);
1149 
1150     // THEN
1151     QVERIFY(m_places->isHidden(firstIndex)); // a child cannot show against its parent state
1152 
1153     // leaving in a clean state
1154     m_places->setGroupHidden(KFilePlacesModel::PlacesType, false);
1155 }
1156 
1157 void KFilePlacesModelTest::testPlaceGroupHiddenAndShownWithHiddenChild()
1158 {
1159     // GIVEN
1160     QCOMPARE(m_places->hiddenCount(), 0);
1161 
1162     QStringList urls;
1163     urls << initialListOfPlaces() << initialListOfShared() << initialListOfRecent() << initialListOfDevices() << initialListOfRemovableDevices();
1164     CHECK_PLACES_URLS(urls);
1165 
1166     QModelIndex firstIndexHidden = m_places->index(0, 0);
1167     m_places->setPlaceHidden(firstIndexHidden, true); // first place index is hidden within an hidden parent
1168     m_places->setGroupHidden(KFilePlacesModel::PlacesType, true);
1169     QCOMPARE(m_places->hiddenCount(), initialListOfPlaces().size());
1170 
1171     // WHEN
1172     m_places->setGroupHidden(KFilePlacesModel::PlacesType, false);
1173 
1174     // THEN
1175     QVector<QModelIndex> indexesShown;
1176     for (int row = 0; row < m_places->rowCount(); ++row) {
1177         QModelIndex index = m_places->index(row, 0);
1178         if (index == firstIndexHidden) {
1179             QVERIFY(m_places->isHidden(firstIndexHidden));
1180             continue;
1181         }
1182         if (m_places->groupType(index) == KFilePlacesModel::PlacesType) {
1183             QVERIFY(!m_places->isHidden(index));
1184             indexesShown << index;
1185         }
1186     }
1187     QCOMPARE(m_places->hiddenCount(), 1);
1188     QCOMPARE(indexesShown.count(), initialListOfPlaces().size() - 1 /*first child remains hidden*/);
1189 
1190     // leaving in a clean state
1191     m_places->setPlaceHidden(firstIndexHidden, false);
1192 }
1193 
1194 void KFilePlacesModelTest::testPlaceGroupHiddenGroupIndexesIntegrity()
1195 {
1196     // GIVEN
1197     m_places->setGroupHidden(KFilePlacesModel::PlacesType, true);
1198     QVERIFY(m_places->groupIndexes(KFilePlacesModel::UnknownType).isEmpty());
1199     QVERIFY(m_places->isGroupHidden(KFilePlacesModel::PlacesType));
1200     QCOMPARE(m_places->groupIndexes(KFilePlacesModel::PlacesType).count(), initialListOfPlaces().count());
1201     QCOMPARE(m_places->groupIndexes(KFilePlacesModel::RecentlySavedType).count(), m_hasRecentlyUsedKio ? 2 : 0);
1202     QCOMPARE(m_places->groupIndexes(KFilePlacesModel::SearchForType).count(), 0);
1203     QCOMPARE(m_places->groupIndexes(KFilePlacesModel::DevicesType).count(), initialListOfDevices().count());
1204     QCOMPARE(m_places->groupIndexes(KFilePlacesModel::RemovableDevicesType).count(), initialListOfRemovableDevices().count());
1205 
1206     // WHEN
1207     m_places->setGroupHidden(KFilePlacesModel::PlacesType, false);
1208 
1209     // THEN
1210     // Make sure that hidden place group doesn't change model
1211     QVERIFY(!m_places->isGroupHidden(KFilePlacesModel::PlacesType));
1212     QCOMPARE(m_places->groupIndexes(KFilePlacesModel::PlacesType).count(), initialListOfPlaces().count());
1213     QCOMPARE(m_places->groupIndexes(KFilePlacesModel::RecentlySavedType).count(), m_hasRecentlyUsedKio ? 2 : 0);
1214     QCOMPARE(m_places->groupIndexes(KFilePlacesModel::SearchForType).count(), 0);
1215     QCOMPARE(m_places->groupIndexes(KFilePlacesModel::DevicesType).count(), initialListOfDevices().count());
1216     QCOMPARE(m_places->groupIndexes(KFilePlacesModel::RemovableDevicesType).count(), initialListOfRemovableDevices().count());
1217 }
1218 
1219 void KFilePlacesModelTest::testPlaceGroupHiddenSignal()
1220 {
1221     QSignalSpy groupHiddenSignal(m_places, &KFilePlacesModel::groupHiddenChanged);
1222     m_places->setGroupHidden(KFilePlacesModel::SearchForType, true);
1223 
1224     // hide SearchForType group
1225     QTRY_COMPARE(groupHiddenSignal.count(), 1);
1226     QList<QVariant> args = groupHiddenSignal.takeFirst();
1227     QCOMPARE(args.at(0).toInt(), static_cast<int>(KFilePlacesModel::SearchForType));
1228     QCOMPARE(args.at(1).toBool(), true);
1229     groupHiddenSignal.clear();
1230 
1231     // try hide SearchForType which is already hidden
1232     m_places->setGroupHidden(KFilePlacesModel::SearchForType, true);
1233     QCOMPARE(groupHiddenSignal.count(), 0);
1234 
1235     // show SearchForType group
1236     m_places->setGroupHidden(KFilePlacesModel::SearchForType, false);
1237     QTRY_COMPARE(groupHiddenSignal.count(), 1);
1238     args = groupHiddenSignal.takeFirst();
1239     QCOMPARE(args.at(0).toInt(), static_cast<int>(KFilePlacesModel::SearchForType));
1240     QCOMPARE(args.at(1).toBool(), false);
1241 }
1242 
1243 void KFilePlacesModelTest::testPlaceGroupHiddenRole()
1244 {
1245     // on startup all groups are visible
1246     for (int r = 0, rMax = m_places->rowCount(); r < rMax; r++) {
1247         const QModelIndex index = m_places->index(r, 0);
1248         QCOMPARE(index.data(KFilePlacesModel::GroupHiddenRole).toBool(), false);
1249     }
1250 
1251     // set SearchFor group hidden
1252     m_places->setGroupHidden(KFilePlacesModel::SearchForType, true);
1253     for (auto groupType : {KFilePlacesModel::PlacesType,
1254                            KFilePlacesModel::RemoteType,
1255                            KFilePlacesModel::RecentlySavedType,
1256                            KFilePlacesModel::SearchForType,
1257                            KFilePlacesModel::DevicesType,
1258                            KFilePlacesModel::RemovableDevicesType}) {
1259         const bool groupShouldBeHidden = (groupType == KFilePlacesModel::SearchForType);
1260         const QModelIndexList indexes = m_places->groupIndexes(groupType);
1261         for (const QModelIndex &index : indexes) {
1262             QCOMPARE(index.data(KFilePlacesModel::GroupHiddenRole).toBool(), groupShouldBeHidden);
1263         }
1264     }
1265 
1266     // set SearchFor group visible again
1267     m_places->setGroupHidden(KFilePlacesModel::SearchForType, false);
1268     for (int r = 0, rMax = m_places->rowCount(); r < rMax; r++) {
1269         const QModelIndex index = m_places->index(r, 0);
1270         QCOMPARE(index.data(KFilePlacesModel::GroupHiddenRole).toBool(), false);
1271     }
1272 }
1273 
1274 void KFilePlacesModelTest::testFilterWithAlternativeApplicationName()
1275 {
1276     const QStringList urls = initialListOfUrls();
1277     const QString alternativeApplicationName = QStringLiteral("kfile_places_model_test");
1278 
1279     KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces"));
1280     KBookmarkGroup root = bookmarkManager->root();
1281 
1282     // create a new entry with alternative application name
1283     KBookmark bookmark = root.addBookmark(QStringLiteral("Extra entry"), QUrl(QStringLiteral("search:/videos-alternative")), {});
1284     const QString id = QUuid::createUuid().toString();
1285     bookmark.setMetaDataItem(QStringLiteral("ID"), id);
1286     bookmark.setMetaDataItem(QStringLiteral("OnlyInApp"), alternativeApplicationName);
1287     bookmarkManager->emitChanged(bookmarkManager->root());
1288 
1289     // make sure that the entry is not visible on the original model
1290     CHECK_PLACES_URLS(urls);
1291 
1292     // create a new model with alternativeApplicationName
1293     KFilePlacesModel *newModel = new KFilePlacesModel(alternativeApplicationName);
1294     QTRY_COMPARE(placesUrls(newModel).count(QStringLiteral("search:/videos-alternative")), 1);
1295     delete newModel;
1296 }
1297 
1298 void KFilePlacesModelTest::testSupportedSchemes()
1299 {
1300     QCoreApplication::processEvents(); // support running this test on its own
1301 
1302     QCOMPARE(m_places->supportedSchemes(), QStringList());
1303     QCOMPARE(placesUrls(), initialListOfUrls());
1304     m_places->setSupportedSchemes({"trash"});
1305     QCOMPARE(m_places->supportedSchemes(), QStringList("trash"));
1306     QCOMPARE(placesUrls(), QStringList("trash:/"));
1307     m_places->setSupportedSchemes({});
1308     QCOMPARE(m_places->supportedSchemes(), QStringList());
1309     QCOMPARE(placesUrls(), initialListOfUrls());
1310 }
1311 
1312 QTEST_MAIN(KFilePlacesModelTest)
1313 
1314 #include "kfileplacesmodeltest.moc"