diff --git a/autotests/kfileplacesmodeltest.cpp b/autotests/kfileplacesmodeltest.cpp --- a/autotests/kfileplacesmodeltest.cpp +++ b/autotests/kfileplacesmodeltest.cpp @@ -79,6 +79,9 @@ void testIconRole_data(); void testIconRole(); void testMoveFunction(); + void testPlaceGroupHidden(); + void testPlaceGroupHiddenVsPlaceChildShown(); + void testPlaceGroupHiddenAndShownWithHiddenChild(); private: QStringList placesUrls() const; @@ -95,7 +98,7 @@ static QString bookmarksFile() { - return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/user-places.xbel"; + return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/user-places.xbel"); } void KFilePlacesModelTest::initTestCase() @@ -168,8 +171,9 @@ QDBusInterface *KFilePlacesModelTest::fakeDevice(const QString &udi) { - if (m_interfacesMap.contains(udi)) { - return m_interfacesMap[udi]; + QDBusInterface *interface = m_interfacesMap[udi]; + if (interface) { + return interface; } QDBusInterface *iface = new QDBusInterface(QDBusConnection::sessionBus().baseService(), udi); @@ -1065,6 +1069,110 @@ QCOMPARE(rowsMoved.count(), 0); } +void KFilePlacesModelTest::testPlaceGroupHidden() +{ + // GIVEN + QCOMPARE(m_places->hiddenCount(), 0); + + QStringList urls; + urls << initialListOfPlaces() << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); + CHECK_PLACES_URLS(urls); + QVector indexesHidden; + + // WHEN + m_places->setGroupHidden(KFilePlacesModel::PlacesType, true); + + // THEN + for (int row = 0; row < m_places->rowCount(); ++row) { + QModelIndex index = m_places->index(row, 0); + if (m_places->groupType(index) == KFilePlacesModel::PlacesType) { + QVERIFY(m_places->isHidden(index)); + indexesHidden << index; + } + } + QCOMPARE(indexesHidden.count(), initialListOfPlaces().size()); + QCOMPARE(m_places->hiddenCount(), indexesHidden.size()); + + // and GIVEN + QVector indexesShown; + + // WHEN + m_places->setGroupHidden(KFilePlacesModel::PlacesType, false); + + // THEN + for (int row = 0; row < m_places->rowCount(); ++row) { + QModelIndex index = m_places->index(row, 0); + if (m_places->groupType(index) == KFilePlacesModel::PlacesType) { + QVERIFY(!m_places->isHidden(index)); + indexesShown << index; + } + } + QCOMPARE(m_places->hiddenCount(), 0); + QCOMPARE(indexesShown.count(), initialListOfPlaces().size()); +} + +void KFilePlacesModelTest::testPlaceGroupHiddenVsPlaceChildShown() +{ + // GIVEN + for (int row = 0; row < m_places->rowCount(); ++row) { + QModelIndex index = m_places->index(row, 0); + QVERIFY(!m_places->isHidden(index)); + } + m_places->setGroupHidden(KFilePlacesModel::PlacesType, true); + + QModelIndex firstIndex = m_places->index(0,0); + const int amountOfPlaces = initialListOfPlaces().size(); + for (int row = 0; row < amountOfPlaces; ++row) { + QModelIndex index = m_places->index(row, 0); + QVERIFY(m_places->isHidden(index)); + } + // WHEN + m_places->setPlaceHidden(firstIndex, false); + + // THEN + QVERIFY(m_places->isHidden(firstIndex)); // a child cannot show against its parent state + + // leaving in a clean state + m_places->setGroupHidden(KFilePlacesModel::PlacesType, false); +} + +void KFilePlacesModelTest::testPlaceGroupHiddenAndShownWithHiddenChild() +{ + // GIVEN + QCOMPARE(m_places->hiddenCount(), 0); + + QStringList urls; + urls << initialListOfPlaces() << initialListOfShared() << initialListOfDevices() << initialListOfRemovableDevices(); + CHECK_PLACES_URLS(urls); + + QModelIndex firstIndexHidden = m_places->index(0,0); + m_places->setPlaceHidden(firstIndexHidden, true); // first place index is hidden within an hidden parent + m_places->setGroupHidden(KFilePlacesModel::PlacesType, true); + QCOMPARE(m_places->hiddenCount(), initialListOfPlaces().size()); + + // WHEN + m_places->setGroupHidden(KFilePlacesModel::PlacesType, false); + + // THEN + QVector indexesShown; + for (int row = 0; row < m_places->rowCount(); ++row) { + QModelIndex index = m_places->index(row, 0); + if (index == firstIndexHidden) { + QVERIFY(m_places->isHidden(firstIndexHidden)); + continue; + } + if (m_places->groupType(index) == KFilePlacesModel::PlacesType) { + QVERIFY(!m_places->isHidden(index)); + indexesShown << index; + } + } + QCOMPARE(m_places->hiddenCount(), 1); + QCOMPARE(indexesShown.count(), initialListOfPlaces().size() - 1 /*first child remains hidden*/); + + // leaving in a clean state + m_places->setPlaceHidden(firstIndexHidden, false); +} + QTEST_MAIN(KFilePlacesModelTest) #include "kfileplacesmodeltest.moc" diff --git a/src/filewidgets/kfileplacesitem.cpp b/src/filewidgets/kfileplacesitem.cpp --- a/src/filewidgets/kfileplacesitem.cpp +++ b/src/filewidgets/kfileplacesitem.cpp @@ -18,7 +18,6 @@ */ #include "kfileplacesitem_p.h" -#include "kfileplacesmodel.h" #include #include @@ -101,24 +100,24 @@ m_text = bookmark.text(); } - const GroupType type = groupType(); + const KFilePlacesModel::GroupType type = groupType(); switch (type) { - case PlacesType: + case KFilePlacesModel::PlacesType: m_groupName = i18nc("@item", "Places"); break; - case RemoteType: + case KFilePlacesModel::RemoteType: m_groupName = i18nc("@item", "Remote"); break; - case RecentlySavedType: + case KFilePlacesModel::RecentlySavedType: m_groupName = i18nc("@item", "Recently Saved"); break; - case SearchForType: + case KFilePlacesModel::SearchForType: m_groupName = i18nc("@item", "Search For"); break; - case DevicesType: + case KFilePlacesModel::DevicesType: m_groupName = i18nc("@item", "Devices"); break; - case RemovableDevicesType: + case KFilePlacesModel::RemovableDevicesType: m_groupName = i18nc("@item", "Removable Devices"); break; default: @@ -144,39 +143,52 @@ } } -KFilePlacesItem::GroupType KFilePlacesItem::groupType() const +KFilePlacesModel::GroupType KFilePlacesItem::groupType() const { if (!isDevice()) { const QString protocol = bookmark().url().scheme(); if (protocol == QLatin1String("timeline")) { - return RecentlySavedType; + return KFilePlacesModel::RecentlySavedType; } if (protocol.contains(QLatin1String("search"))) { - return SearchForType; + return KFilePlacesModel::SearchForType; } if (protocol == QLatin1String("bluetooth") || protocol == QLatin1String("obexftp") || protocol == QLatin1String("kdeconnect")) { - return DevicesType; + return KFilePlacesModel::DevicesType; } if (protocol == QLatin1String("remote") || KProtocolInfo::protocolClass(protocol) != QLatin1String(":local")) { - return RemoteType; + return KFilePlacesModel::RemoteType; } else { - return PlacesType; + return KFilePlacesModel::PlacesType; } } if (m_drive && (m_drive->isHotpluggable() || m_drive->isRemovable())) { - return RemovableDevicesType; + return KFilePlacesModel::RemovableDevicesType; } else { - return DevicesType; + return KFilePlacesModel::DevicesType; } } +bool KFilePlacesItem::isHidden() const +{ + return m_bookmark.metaDataItem(QStringLiteral("IsHidden")) == QLatin1String("true"); +} + +void KFilePlacesItem::setHidden(bool hide) +{ + if (m_bookmark.isNull() || isHidden() == hide) { + return; + } + m_bookmark.setMetaDataItem(QStringLiteral("IsHidden"), hide ? QStringLiteral("true") : QStringLiteral("false")); +} + QVariant KFilePlacesItem::bookmarkData(int role) const { KBookmark b = bookmark(); @@ -191,7 +203,7 @@ case Qt::DecorationRole: return QIcon::fromTheme(iconNameForBookmark(b)); case Qt::BackgroundRole: - if (b.metaDataItem(QStringLiteral("IsHidden")) == QLatin1String("true")) { + if (isHidden()) { return QColor(Qt::lightGray); } else { return QVariant(); @@ -201,7 +213,7 @@ case KFilePlacesModel::SetupNeededRole: return false; case KFilePlacesModel::HiddenRole: - return b.metaDataItem(QStringLiteral("IsHidden")) == QLatin1String("true"); + return isHidden(); case KFilePlacesModel::IconNameRole: return iconNameForBookmark(b); default: diff --git a/src/filewidgets/kfileplacesitem_p.h b/src/filewidgets/kfileplacesitem_p.h --- a/src/filewidgets/kfileplacesitem_p.h +++ b/src/filewidgets/kfileplacesitem_p.h @@ -27,6 +27,7 @@ #include #include #include +#include "kfileplacesmodel.h" class KDirLister; namespace Solid @@ -64,7 +65,9 @@ void setBookmark(const KBookmark &bookmark); Solid::Device device() const; QVariant data(int role) const; - GroupType groupType() const; + KFilePlacesModel::GroupType groupType() const; + bool isHidden() const; + void setHidden(bool hide); static KBookmark createBookmark(KBookmarkManager *manager, const QString &label, diff --git a/src/filewidgets/kfileplacesmodel.h b/src/filewidgets/kfileplacesmodel.h --- a/src/filewidgets/kfileplacesmodel.h +++ b/src/filewidgets/kfileplacesmodel.h @@ -53,17 +53,30 @@ IconNameRole = 0x00a45c00 }; + enum GroupType { + PlacesType, + RemoteType, + RecentlySavedType, + SearchForType, + DevicesType, + RemovableDevicesType, + UnknownType + }; + KFilePlacesModel(QObject *parent = nullptr); ~KFilePlacesModel(); QUrl url(const QModelIndex &index) const; bool setupNeeded(const QModelIndex &index) const; QIcon icon(const QModelIndex &index) const; QString text(const QModelIndex &index) const; bool isHidden(const QModelIndex &index) const; + bool isGroupHidden(const GroupType type) const; + bool isGroupHidden(const QModelIndex &index) const; bool isDevice(const QModelIndex &index) const; Solid::Device deviceForIndex(const QModelIndex &index) const; KBookmark bookmarkForIndex(const QModelIndex &index) const; + GroupType groupType(const QModelIndex &index) const; QAction *teardownActionForIndex(const QModelIndex &index) const; QAction *ejectActionForIndex(const QModelIndex &index) const; @@ -76,6 +89,8 @@ void editPlace(const QModelIndex &index, const QString &text, const QUrl &url, const QString &iconName = QString(), const QString &appName = QString()); void removePlace(const QModelIndex &index) const; void setPlaceHidden(const QModelIndex &index, bool hidden); + void setGroupHidden(const GroupType type, bool hidden); + /** * @brief Move place at @p itemRow to a position before @p row * @since 5.41 diff --git a/src/filewidgets/kfileplacesmodel.cpp b/src/filewidgets/kfileplacesmodel.cpp --- a/src/filewidgets/kfileplacesmodel.cpp +++ b/src/filewidgets/kfileplacesmodel.cpp @@ -61,6 +61,26 @@ #include namespace { + QString stateNameForGroupType(KFilePlacesModel::GroupType type) + { + switch (type) { + case KFilePlacesModel::PlacesType: + return QStringLiteral("GroupState-Places-IsHidden"); + case KFilePlacesModel::RemoteType: + return QStringLiteral("GroupState-Remote-IsHidden"); + case KFilePlacesModel::RecentlySavedType: + return QStringLiteral("GroupState-RecentlySaved-IsHidden"); + case KFilePlacesModel::SearchForType: + return QStringLiteral("GroupState-SearchFor-IsHidden"); + case KFilePlacesModel::DevicesType: + return QStringLiteral("GroupState-Devices-IsHidden"); + case KFilePlacesModel::RemovableDevicesType: + return QStringLiteral("GroupState-RemovableDevices-IsHidden"); + default: + Q_UNREACHABLE(); + } + } + static bool isFileIndexingEnabled() { KConfig config(QStringLiteral("baloofilerc")); @@ -197,6 +217,11 @@ // Let's put some places in there if it's empty. KBookmarkGroup root = d->bookmarkManager->root(); + + const auto setDefaultMetadataItemForGroup = [&root](KFilePlacesModel::GroupType type) { + root.setMetaDataItem(stateNameForGroupType(type), QStringLiteral("false")); + }; + if (root.first().isNull() || !QFile::exists(file)) { // NOTE: The context for these I18N_NOOP2 calls has to be "KFile System Bookmarks". @@ -230,6 +255,11 @@ QStringLiteral("Trash"), I18N_NOOP2("KFile System Bookmarks", "Trash"), QUrl(QStringLiteral("trash:/")), QStringLiteral("user-trash")); + setDefaultMetadataItemForGroup(PlacesType); + setDefaultMetadataItemForGroup(RemoteType); + setDefaultMetadataItemForGroup(DevicesType); + setDefaultMetadataItemForGroup(RemovableDevicesType); + // Force bookmarks to be saved. If on open/save dialog and the bookmarks are not saved, QFile::exists // will always return false, which opening/closing all the time the open/save dialog would case the // bookmarks to be added once each time, having lots of times each bookmark. (ereslibre) @@ -266,6 +296,9 @@ QStringLiteral("Videos"), I18N_NOOP2("KFile System Bookmarks", "Videos"), QUrl(QStringLiteral("search:/videos")), QStringLiteral("folder-videos")); + setDefaultMetadataItemForGroup(SearchForType); + setDefaultMetadataItemForGroup(RecentlySavedType); + d->bookmarkManager->save(); } @@ -322,7 +355,24 @@ bool KFilePlacesModel::isHidden(const QModelIndex &index) const { - return data(index, HiddenRole).toBool(); + //Note: we do not want to show an index if its parent is hidden + return data(index, HiddenRole).toBool() || isGroupHidden(index); +} + +bool KFilePlacesModel::isGroupHidden(const GroupType type) const +{ + const QString hidden = d->bookmarkManager->root().metaDataItem(stateNameForGroupType(type)); + return hidden == QStringLiteral("true") ? true : false; +} + +bool KFilePlacesModel::isGroupHidden(const QModelIndex &index) const +{ + if (!index.isValid()) { + return false; + } + + KFilePlacesItem *item = static_cast(index.internalPointer()); + return isGroupHidden(item->groupType()); } bool KFilePlacesModel::isDevice(const QModelIndex &index) const @@ -361,6 +411,16 @@ return item->bookmark(); } +KFilePlacesModel::GroupType KFilePlacesModel::groupType(const QModelIndex &index) const +{ + if (!index.isValid()) { + return UnknownType; + } + + KFilePlacesItem *item = static_cast(index.internalPointer()); + return item->groupType(); +} + QVariant KFilePlacesModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { @@ -623,7 +683,7 @@ int KFilePlacesModel::Private::findNearestPosition(int source, int target) { const KFilePlacesItem *item = items.at(source); - const KFilePlacesItem::GroupType groupType = item->groupType(); + const KFilePlacesModel::GroupType groupType = item->groupType(); int newTarget = qMin(target, items.count() - 1); // moving inside the same group is ok @@ -937,16 +997,29 @@ KFilePlacesItem *item = static_cast(index.internalPointer()); - KBookmark bookmark = item->bookmark(); - - if (bookmark.isNull()) { + if (item->bookmark().isNull() || item->isHidden() == hidden) { return; } - bookmark.setMetaDataItem(QStringLiteral("IsHidden"), (hidden ? QStringLiteral("true") : QStringLiteral("false"))); + const bool groupHidden = isGroupHidden(item->groupType()); + const bool hidingChildOnShownParent = hidden && !groupHidden; + const bool showingChildOnShownParent = !hidden && !groupHidden; - refresh(); - emit dataChanged(index, index); + if (hidingChildOnShownParent || showingChildOnShownParent) { + item->setHidden(hidden); + + d->reloadAndSignal(); + emit dataChanged(index, index); + } +} + +void KFilePlacesModel::setGroupHidden(const GroupType type, bool hidden) +{ + if (isGroupHidden(type) == hidden) + return; + + d->bookmarkManager->root().setMetaDataItem(stateNameForGroupType(type), (hidden ? QStringLiteral("true") : QStringLiteral("false"))); + d->reloadAndSignal(); } bool KFilePlacesModel::movePlace(int itemRow, int row) diff --git a/src/tests/placesitemmodeltest.cpp b/src/tests/placesitemmodeltest.cpp new file mode 100644 --- /dev/null +++ b/src/tests/placesitemmodeltest.cpp @@ -0,0 +1,532 @@ +/*************************************************************************** + * Copyright (C) 2017 by Renato Araujo Oliveira * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "panels/places/placesitemmodel.h" +#include "panels/places/placesitem.h" +#include "views/viewproperties.h" +#include "kitemviews/kitemrange.h" + +Q_DECLARE_METATYPE(KItemRangeList) +Q_DECLARE_METATYPE(PlacesItem::GroupType) + +#ifdef Q_OS_WIN +//c:\ as root for windows +#define KDE_ROOT_PATH "C:\\" +#else +#define KDE_ROOT_PATH "/" +#endif + +static QString bookmarksFile() +{ + return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/user-places.xbel"; +} + +class PlacesItemModelTest : public QObject +{ + Q_OBJECT + +private slots: + void init(); + void cleanup(); + + void initTestCase(); + void cleanupTestCase(); + + void testModelSort(); + void testModelMove(); + void testGroups(); + void testPlaceItem_data(); + void testPlaceItem(); + void testTearDownDevice(); + void testDefaultViewProperties_data(); + void testDefaultViewProperties(); + void testClear(); + void testHideItem(); + void testSystemItems(); + void testEditBookmark(); + void testEditAfterCreation(); + void testEditMetadata(); + +private: + PlacesItemModel* m_model; + QMap m_interfacesMap; + + void setBalooEnabled(bool enabled); + int indexOf(const QUrl &url); + QDBusInterface *fakeManager(); + QDBusInterface *fakeDevice(const QString &udi); + QStringList placesUrls() const; + QStringList initialUrls() const; + void createPlaceItem(const QString &text, const QUrl &url, const QString &icon); +}; + +#define CHECK_PLACES_URLS(urls) \ + QStringList tmp(urls); \ + QStringList places = placesUrls(); \ + while(!places.isEmpty()) { \ + tmp.removeOne(places.takeFirst()); \ + } \ + if (!tmp.isEmpty()) { \ + qWarning() << "Expected:" << urls; \ + qWarning() << "Got:" << places; \ + QCOMPARE(places, urls); \ + } + +void PlacesItemModelTest::setBalooEnabled(bool enabled) +{ + KConfig config(QStringLiteral("baloofilerc")); + KConfigGroup basicSettings = config.group("Basic Settings"); + basicSettings.writeEntry("Indexing-Enabled", enabled); + config.sync(); +} + +int PlacesItemModelTest::indexOf(const QUrl &url) +{ + for (int r = 0; r < m_model->count(); r++) { + if (m_model->placesItem(r)->url() == url) { + return r; + } + } + return -1; +} + +QDBusInterface *PlacesItemModelTest::fakeManager() +{ + return fakeDevice(QStringLiteral("/org/kde/solid/fakehw")); +} + +QDBusInterface *PlacesItemModelTest::fakeDevice(const QString &udi) +{ + if (m_interfacesMap.contains(udi)) { + return m_interfacesMap[udi]; + } + + QDBusInterface *iface = new QDBusInterface(QDBusConnection::sessionBus().baseService(), udi); + m_interfacesMap[udi] = iface; + + return iface; +} + +QStringList PlacesItemModelTest::placesUrls() const +{ + QStringList urls; + for (int row = 0; row < m_model->count(); ++row) { + urls << m_model->placesItem(row)->url().toDisplayString(QUrl::PreferLocalFile); + } + return urls; +} + +void PlacesItemModelTest::createPlaceItem(const QString &text, const QUrl &url, const QString &icon) +{ + PlacesItem *item = m_model->createPlacesItem(text, + url, + icon); + QSignalSpy itemsInsertedSpy(m_model, &PlacesItemModel::itemsInserted); + m_model->appendItemToGroup(item); + QTRY_COMPARE(itemsInsertedSpy.count(), 1); +} + +void PlacesItemModelTest::init() +{ + m_model = new PlacesItemModel(); + // WORKAROUND: need to wait for bookmark to load, check: PlacesItemModel::updateBookmarks + QTest::qWait(300); + QCOMPARE(m_model->count(), 17); +} + +void PlacesItemModelTest::cleanup() +{ + delete m_model; + m_model = nullptr; +} + +void PlacesItemModelTest::initTestCase() +{ + QStandardPaths::setTestModeEnabled(true); + + const QString fakeHw = QFINDTESTDATA("fakecomputer.xml"); + QVERIFY(!fakeHw.isEmpty()); + qputenv("SOLID_FAKEHW", QFile::encodeName(fakeHw)); + + setBalooEnabled(true); + const QString bookmarsFileName = bookmarksFile(); + if (QFile::exists(bookmarsFileName)) { + // Ensure we'll have a clean bookmark file to start + QVERIFY(QFile::remove(bookmarsFileName)); + } + + qRegisterMetaType(); +} + +void PlacesItemModelTest::cleanupTestCase() +{ + qDeleteAll(m_interfacesMap); + QFile::remove(bookmarksFile()); +} + +QStringList PlacesItemModelTest::initialUrls() const +{ + QStringList urls; + + urls << QDir::homePath() << QStringLiteral("remote:/") << QStringLiteral(KDE_ROOT_PATH) << QStringLiteral("trash:/") + << QStringLiteral("timeline:/today") << QStringLiteral("timeline:/yesterday") << QStringLiteral("timeline:/thismonth") << QStringLiteral("timeline:/lastmonth") + << QStringLiteral("search:/documents") << QStringLiteral("search:/images") << QStringLiteral("search:/audio") << QStringLiteral("search:/videos") + << QStringLiteral("/media/cdrom") << QStringLiteral("/foreign") << QStringLiteral("/media/XO-Y4") << QStringLiteral("/media/nfs") << QStringLiteral("/media/floppy0"); + + return urls; +} + +void PlacesItemModelTest::testModelSort() +{ + CHECK_PLACES_URLS(initialUrls()); +} + +void PlacesItemModelTest::testModelMove() +{ + QStringList urls = initialUrls(); + KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(bookmarksFile(), QStringLiteral("kfilePlaces")); + KBookmarkGroup root = bookmarkManager->root(); + KBookmark systemRoot = m_model->placesItem(1)->bookmark(); + KBookmark last = m_model->placesItem(m_model->count() - 1)->bookmark(); + + // try to move the "root" path to the end of the list + root.moveBookmark(systemRoot, last); + bookmarkManager->emitChanged(root); + + // make sure that the items still grouped and the "root" item was moved to the end of places group instead + urls.move(1, 2); + CHECK_PLACES_URLS(urls); +} + +void PlacesItemModelTest::testGroups() +{ + const auto groups = m_model->groups(); + + QCOMPARE(groups.size(), 4); + QCOMPARE(groups.at(0).first, 0); + QCOMPARE(groups.at(0).second.toString(), QStringLiteral("Places")); + QCOMPARE(groups.at(1).first, 4); + QCOMPARE(groups.at(1).second.toString(), QStringLiteral("Recently Saved")); + QCOMPARE(groups.at(2).first, 8); + QCOMPARE(groups.at(2).second.toString(), QStringLiteral("Search For")); + QCOMPARE(groups.at(3).first, 12); + QCOMPARE(groups.at(3).second.toString(), QStringLiteral("Devices")); +} + +void PlacesItemModelTest::testPlaceItem_data() +{ + QTest::addColumn("url"); + QTest::addColumn("expectedIsHidden"); + QTest::addColumn("expectedIsSystemItem"); + QTest::addColumn("expectedGroupType"); + QTest::addColumn("expectedStorageSetupNeeded"); + + // places + QTest::newRow("Places - Home") << QUrl::fromLocalFile(QDir::homePath()) << false << true << PlacesItem::PlacesType << false; + + // baloo -search + QTest::newRow("Baloo - Documents") << QUrl("search:/documents") << false << true << PlacesItem::SearchForType << false; + + // baloo - timeline + QTest::newRow("Baloo - Last Month") << QUrl("timeline:/lastmonth") << false << true << PlacesItem::RecentlySavedType << false; + + // devices + QTest::newRow("Devices - Floppy") << QUrl("file:///media/floppy0") << false << false << PlacesItem::DevicesType << false; +} + +void PlacesItemModelTest::testPlaceItem() +{ + QFETCH(QUrl, url); + QFETCH(bool, expectedIsHidden); + QFETCH(bool, expectedIsSystemItem); + QFETCH(PlacesItem::GroupType, expectedGroupType); + QFETCH(bool, expectedStorageSetupNeeded); + + const int index = indexOf(url); + PlacesItem *item = m_model->placesItem(index); + QCOMPARE(item->url(), url); + QCOMPARE(item->isHidden(), expectedIsHidden); + QCOMPARE(item->isSystemItem(), expectedIsSystemItem); + QCOMPARE(item->groupType(), expectedGroupType); + QCOMPARE(item->storageSetupNeeded(), expectedStorageSetupNeeded); +} + +void PlacesItemModelTest::testTearDownDevice() +{ + const QUrl mediaUrl = QUrl::fromLocalFile(QStringLiteral("/media/XO-Y4")); + int index = indexOf(mediaUrl); + QVERIFY(index != -1); + + auto ejectAction = m_model->ejectAction(index); + QVERIFY(!ejectAction); + + auto teardownAction = m_model->teardownAction(index); + QVERIFY(teardownAction); + + QCOMPARE(m_model->count(), 17); + + QSignalSpy spyItemsRemoved(m_model, &PlacesItemModel::itemsRemoved); + fakeManager()->call(QStringLiteral("unplug"), "/org/kde/solid/fakehw/volume_part1_size_993284096"); + QTRY_COMPARE(m_model->count(), 16); + QCOMPARE(spyItemsRemoved.count(), 1); + const QList spyItemsRemovedArgs = spyItemsRemoved.takeFirst(); + const KItemRangeList removedRange = spyItemsRemovedArgs.at(0).value(); + QCOMPARE(removedRange.size(), 1); + QCOMPARE(removedRange.first().index, index); + QCOMPARE(removedRange.first().count, 1); + + QCOMPARE(indexOf(mediaUrl), -1); + + QSignalSpy spyItemsInserted(m_model, &PlacesItemModel::itemsInserted); + fakeManager()->call(QStringLiteral("plug"), "/org/kde/solid/fakehw/volume_part1_size_993284096"); + QTRY_COMPARE(m_model->count(), 17); + QCOMPARE(spyItemsInserted.count(), 1); + index = indexOf(mediaUrl); + + const QList args = spyItemsInserted.takeFirst(); + const KItemRangeList insertedRange = args.at(0).value(); + QCOMPARE(insertedRange.size(), 1); + QCOMPARE(insertedRange.first().index, index); + QCOMPARE(insertedRange.first().count, 1); +} + +void PlacesItemModelTest::testDefaultViewProperties_data() +{ + QTest::addColumn("url"); + QTest::addColumn("expectedViewMode"); + QTest::addColumn("expectedPreviewShow"); + QTest::addColumn >("expectedVisibleRole"); + + // places + QTest::newRow("Places - Home") << QUrl::fromLocalFile(QDir::homePath()) << DolphinView::IconsView << false << QList({"text"}); + + // baloo -search + QTest::newRow("Baloo - Documents") << QUrl("search:/documents") << DolphinView::DetailsView << false << QList({"text", "path"}); + + // baloo - timeline + QTest::newRow("Baloo - Last Month") << QUrl("timeline:/lastmonth") << DolphinView::DetailsView << false << QList({"text", "modificationtime"}); + + // devices + QTest::newRow("Devices - Floppy") << QUrl("file:///media/floppy0") << DolphinView::IconsView << false << QList({"text"}); +} + +void PlacesItemModelTest::testDefaultViewProperties() +{ + QFETCH(QUrl, url); + QFETCH(DolphinView::Mode, expectedViewMode); + QFETCH(bool, expectedPreviewShow); + QFETCH(QList, expectedVisibleRole); + + ViewProperties properties(m_model->convertedUrl(url)); + QCOMPARE(properties.viewMode(), expectedViewMode); + QCOMPARE(properties.previewsShown(), expectedPreviewShow); + QCOMPARE(properties.visibleRoles(), expectedVisibleRole); +} + +void PlacesItemModelTest::testClear() +{ + QCOMPARE(m_model->count(), 17); + m_model->clear(); + QCOMPARE(m_model->count(), 0); + QCOMPARE(m_model->hiddenCount(), 0); +} + +void PlacesItemModelTest::testHideItem() +{ + const QUrl mediaUrl = QUrl::fromLocalFile(QStringLiteral("/media/XO-Y4")); + const int index = indexOf(mediaUrl); + + PlacesItem *item = m_model->placesItem(index); + + QSignalSpy spyItemsRemoved(m_model, &PlacesItemModel::itemsRemoved); + QList spyItemsRemovedArgs; + KItemRangeList removedRange; + + QSignalSpy spyItemsInserted(m_model, &PlacesItemModel::itemsInserted); + QList spyItemsInsertedArgs; + KItemRangeList insertedRange; + QVERIFY(item); + + // hide an item + item->setHidden(true); + + // check if items removed was fired + QTRY_COMPARE(m_model->count(), 16); + QCOMPARE(spyItemsRemoved.count(), 1); + spyItemsRemovedArgs = spyItemsRemoved.takeFirst(); + removedRange = spyItemsRemovedArgs.at(0).value(); + QCOMPARE(removedRange.size(), 1); + QCOMPARE(removedRange.first().index, index); + QCOMPARE(removedRange.first().count, 1); + + // allow model to show hidden items + m_model->setHiddenItemsShown(true); + + // check if the items inserted was fired + spyItemsInsertedArgs = spyItemsInserted.takeFirst(); + insertedRange = spyItemsInsertedArgs.at(0).value(); + QCOMPARE(insertedRange.size(), 1); + QCOMPARE(insertedRange.first().index, index); + QCOMPARE(insertedRange.first().count, 1); + + // mark item as visible + item = m_model->placesItem(index); + item->setHidden(false); + + // mark model to hide invisible items + m_model->setHiddenItemsShown(true); + + QTRY_COMPARE(m_model->count(), 17); +} + +void PlacesItemModelTest::testSystemItems() +{ + QCOMPARE(m_model->count(), 17); + for (int r = 0; r < m_model->count(); r++) { + QCOMPARE(m_model->placesItem(r)->isSystemItem(), !m_model->placesItem(r)->device().isValid()); + } + + // create a new entry (non system item) + PlacesItem *item = m_model->createPlacesItem(QStringLiteral("Temporary Dir"), + QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation)), + QString()); + + QSignalSpy itemsInsertedSpy(m_model, &PlacesItemModel::itemsInserted); + m_model->appendItemToGroup(item); + + // check if the new entry was created + QTRY_COMPARE(itemsInsertedSpy.count(), 1); + QList args = itemsInsertedSpy.takeFirst(); + KItemRangeList range = args.at(0).value(); + QCOMPARE(range.first().index, 4); + QCOMPARE(range.first().count, 1); + QVERIFY(!m_model->placesItem(4)->isSystemItem()); + QCOMPARE(m_model->count(), 18); + + // remove new entry + QSignalSpy itemsRemovedSpy(m_model, &PlacesItemModel::itemsRemoved); + m_model->removeItem(4); + m_model->saveBookmarks(); + QTRY_COMPARE(itemsRemovedSpy.count(), 1); + args = itemsRemovedSpy.takeFirst(); + range = args.at(0).value(); + QCOMPARE(range.first().index, 4); + QCOMPARE(range.first().count, 1); + QTRY_COMPARE(m_model->count(), 17); +} + +void PlacesItemModelTest::testEditBookmark() +{ + QScopedPointer other(new PlacesItemModel()); + + createPlaceItem(QStringLiteral("Temporary Dir"), QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation)), QString()); + + QSignalSpy itemsChangedSply(m_model, &PlacesItemModel::itemsChanged); + m_model->item(4)->setText(QStringLiteral("Renamed place")); + m_model->saveBookmarks(); + QTRY_COMPARE(itemsChangedSply.count(), 1); + QList args = itemsChangedSply.takeFirst(); + KItemRangeList range = args.at(0).value(); + QCOMPARE(range.first().index, 4); + QCOMPARE(range.first().count, 1); + QSet roles = args.at(1).value >(); + QCOMPARE(roles.size(), 1); + QCOMPARE(*roles.begin(), QByteArrayLiteral("text")); + QCOMPARE(m_model->item(4)->text(), QStringLiteral("Renamed place")); + + // check if the item was updated in the other model + QTRY_COMPARE(other->item(4)->text(), QStringLiteral("Renamed place")); + + // remove new entry + QSignalSpy itemsRemovedSpy(m_model, &PlacesItemModel::itemsRemoved); + m_model->removeItem(4); + m_model->saveBookmarks(); + QTRY_COMPARE(itemsRemovedSpy.count(), 1); + args = itemsRemovedSpy.takeFirst(); + range = args.at(0).value(); + QCOMPARE(range.first().index, 4); + QCOMPARE(range.first().count, 1); + QTRY_COMPARE(m_model->count(), 17); +} + +void PlacesItemModelTest::testEditAfterCreation() +{ + createPlaceItem(QStringLiteral("Temporary Dir"), QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation)), QString()); + + PlacesItemModel *model = new PlacesItemModel(); + QTRY_COMPARE(model->count(), m_model->count()); + + PlacesItem *item = m_model->placesItem(4); + item->setText(QStringLiteral("Renamed place")); + m_model->saveBookmarks(); + + QTRY_COMPARE(model->count(), m_model->count()); + QTRY_COMPARE(model->placesItem(4)->text(), m_model->placesItem(4)->text()); + QTRY_COMPARE(model->placesItem(4)->bookmark().metaDataItem(QStringLiteral("OnlyInApp")), + m_model->placesItem(4)->bookmark().metaDataItem(QStringLiteral("OnlyInApp"))); + QTRY_COMPARE(model->placesItem(4)->icon(), m_model->placesItem(4)->icon()); + QTRY_COMPARE(model->placesItem(4)->url(), m_model->placesItem(4)->url()); + + m_model->removeItem(4); + m_model->saveBookmarks(); + QTRY_COMPARE(model->count(), m_model->count()); +} + +void PlacesItemModelTest::testEditMetadata() +{ + createPlaceItem(QStringLiteral("Temporary Dir"), QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation)), QString()); + + PlacesItemModel *model = new PlacesItemModel(); + QTRY_COMPARE(model->count(), m_model->count()); + + PlacesItem *item = m_model->placesItem(4); + item->bookmark().setMetaDataItem(QStringLiteral("OnlyInApp"), KAboutData::applicationData().componentName()); + m_model->saveBookmarks(); + + QTRY_COMPARE(model->count(), m_model->count()); + QTRY_COMPARE(model->placesItem(4)->bookmark().metaDataItem(QStringLiteral("OnlyInApp")), + KAboutData::applicationData().componentName()); + QTRY_COMPARE(model->placesItem(4)->text(), m_model->placesItem(4)->text()); + QTRY_COMPARE(model->placesItem(4)->bookmark().metaDataItem(QStringLiteral("OnlyInApp")), + m_model->placesItem(4)->bookmark().metaDataItem(QStringLiteral("OnlyInApp"))); + QTRY_COMPARE(model->placesItem(4)->icon(), m_model->placesItem(4)->icon()); + QTRY_COMPARE(model->placesItem(4)->url(), m_model->placesItem(4)->url()); + + m_model->removeItem(4); + m_model->saveBookmarks(); + QTRY_COMPARE(model->count(), m_model->count()); +} + +QTEST_MAIN(PlacesItemModelTest) + +#include "placesitemmodeltest.moc"