diff --git a/src/akonadi/CMakeLists.txt b/src/akonadi/CMakeLists.txt index 02f5f6c1..2ec13f06 100644 --- a/src/akonadi/CMakeLists.txt +++ b/src/akonadi/CMakeLists.txt @@ -1,43 +1,42 @@ set(akonadi_SRCS akonadiapplicationselectedattribute.cpp akonadicollectionfetchjobinterface.cpp - akonadicollectionsearchjobinterface.cpp akonadiconfigdialog.cpp akonadicontextqueries.cpp akonadicontextrepository.cpp akonadidatasourcequeries.cpp akonadidatasourcerepository.cpp akonadiitemfetchjobinterface.cpp akonadilivequeryhelpers.cpp akonadilivequeryintegrator.cpp akonadimessaging.cpp akonadimessaginginterface.cpp akonadimonitorimpl.cpp akonadimonitorinterface.cpp akonadinotequeries.cpp akonadinoterepository.cpp akonadiprojectqueries.cpp akonadiprojectrepository.cpp akonadiserializer.cpp akonadiserializerinterface.cpp akonadistorage.cpp akonadistorageinterface.cpp akonadistoragesettings.cpp akonaditagfetchjobinterface.cpp akonaditagqueries.cpp akonaditagrepository.cpp akonaditaskqueries.cpp akonaditaskrepository.cpp akonaditimestampattribute.cpp ) add_library(akonadi STATIC ${akonadi_SRCS}) target_link_libraries(akonadi KF5::AkonadiCalendar KF5::AkonadiCore KF5::AkonadiNotes KF5::AkonadiWidgets KF5::Mime KF5::CalendarCore KF5::IdentityManagement ) diff --git a/src/akonadi/akonadicollectionfetchjobinterface.h b/src/akonadi/akonadicollectionfetchjobinterface.h index c366c364..737dccfe 100644 --- a/src/akonadi/akonadicollectionfetchjobinterface.h +++ b/src/akonadi/akonadicollectionfetchjobinterface.h @@ -1,48 +1,47 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #ifndef AKONADI_COLLECTIONFETCHJOBINTERFACE_H #define AKONADI_COLLECTIONFETCHJOBINTERFACE_H #include class KJob; namespace Akonadi { class CollectionFetchJobInterface { public: CollectionFetchJobInterface(); virtual ~CollectionFetchJobInterface(); KJob *kjob(); virtual Collection::List collections() const = 0; virtual void setResource(const QString &resource) = 0; - virtual void setFiltered(bool filter) = 0; }; } #endif // AKONADI_COLLECTIONFETCHJOBINTERFACE_H diff --git a/src/akonadi/akonadicollectionsearchjobinterface.cpp b/src/akonadi/akonadicollectionsearchjobinterface.cpp deleted file mode 100644 index 6f6f0227..00000000 --- a/src/akonadi/akonadicollectionsearchjobinterface.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Mario Bensi - - 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) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - 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 "akonadicollectionsearchjobinterface.h" - -#include - -using namespace Akonadi; - -CollectionSearchJobInterface::CollectionSearchJobInterface() -{ -} - -CollectionSearchJobInterface::~CollectionSearchJobInterface() -{ -} - -KJob *CollectionSearchJobInterface::kjob() -{ - KJob *job = dynamic_cast(this); - Q_ASSERT(job); - return job; -} diff --git a/src/akonadi/akonadicollectionsearchjobinterface.h b/src/akonadi/akonadicollectionsearchjobinterface.h deleted file mode 100644 index 5abaa6a9..00000000 --- a/src/akonadi/akonadicollectionsearchjobinterface.h +++ /dev/null @@ -1,46 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Mario Bensi - - 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) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - 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. -*/ - -#ifndef AKONADI_COLLECTIONSEARCHJOBINTERFACE_H -#define AKONADI_COLLECTIONSEARCHJOBINTERFACE_H - -#include - -class KJob; - -namespace Akonadi { - -class CollectionSearchJobInterface -{ -public: - CollectionSearchJobInterface(); - virtual ~CollectionSearchJobInterface(); - - KJob *kjob(); - - virtual Collection::List collections() const = 0; -}; - -} - -#endif // AKONADI_COLLECTIONSEARCHJOBINTERFACE_H diff --git a/src/akonadi/akonadidatasourcequeries.cpp b/src/akonadi/akonadidatasourcequeries.cpp index c8442f40..3bd819cd 100644 --- a/src/akonadi/akonadidatasourcequeries.cpp +++ b/src/akonadi/akonadidatasourcequeries.cpp @@ -1,136 +1,90 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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 "akonadidatasourcequeries.h" #include "akonadistoragesettings.h" using namespace Akonadi; DataSourceQueries::DataSourceQueries(StorageInterface::FetchContentTypes contentTypes, const StorageInterface::Ptr &storage, const SerializerInterface::Ptr &serializer, const MonitorInterface::Ptr &monitor) : m_contentTypes(contentTypes), m_serializer(serializer), m_helpers(new LiveQueryHelpers(serializer, storage)), m_integrator(new LiveQueryIntegrator(serializer, monitor)) { m_integrator->addRemoveHandler([this] (const Collection &collection) { m_findChildren.remove(collection.id()); - m_findSearchChildren.remove(collection.id()); }); } bool DataSourceQueries::isDefaultSource(Domain::DataSource::Ptr source) const { auto sourceCollection = m_serializer->createCollectionFromDataSource(source); if (m_contentTypes == StorageInterface::Tasks) return sourceCollection == StorageSettings::instance().defaultTaskCollection(); else if (m_contentTypes == StorageInterface::Notes) return sourceCollection == StorageSettings::instance().defaultNoteCollection(); else return false; } void DataSourceQueries::changeDefaultSource(Domain::DataSource::Ptr source) { auto sourceCollection = m_serializer->createCollectionFromDataSource(source); if (m_contentTypes == StorageInterface::Tasks) { StorageSettings::instance().setDefaultTaskCollection(sourceCollection); } else if (m_contentTypes == StorageInterface::Notes) { StorageSettings::instance().setDefaultNoteCollection(sourceCollection); } } DataSourceQueries::DataSourceResult::Ptr DataSourceQueries::findTopLevel() const { auto fetch = m_helpers->fetchCollections(Collection::root(), m_contentTypes); auto predicate = createFetchPredicate(Collection::root()); m_integrator->bind("DataSourceQueries::findTopLevel", m_findTopLevel, fetch, predicate); return m_findTopLevel->result(); } DataSourceQueries::DataSourceResult::Ptr DataSourceQueries::findChildren(Domain::DataSource::Ptr source) const { Collection root = m_serializer->createCollectionFromDataSource(source); auto &query = m_findChildren[root.id()]; auto fetch = m_helpers->fetchCollections(root, m_contentTypes); auto predicate = createFetchPredicate(root); m_integrator->bind("DataSourceQueries::findChildren", query, fetch, predicate); return query->result(); } -QString DataSourceQueries::searchTerm() const -{ - return m_searchTerm; -} - -void DataSourceQueries::setSearchTerm(const QString &term) -{ - if (m_searchTerm == term) - return; - - m_searchTerm = term; - if (m_findSearchTopLevel) { - m_findSearchTopLevel->reset(); - } - foreach (auto query, m_findSearchChildren) - query->reset(); -} - -DataSourceQueries::DataSourceResult::Ptr DataSourceQueries::findSearchTopLevel() const -{ - auto fetch = m_helpers->searchCollections(Collection::root(), &m_searchTerm, m_contentTypes); - auto predicate = createSearchPredicate(Collection::root()); - m_integrator->bind("DataSourceQueries::findSearchTopLevel", m_findSearchTopLevel, fetch, predicate); - return m_findSearchTopLevel->result(); -} - -DataSourceQueries::DataSourceResult::Ptr DataSourceQueries::findSearchChildren(Domain::DataSource::Ptr source) const -{ - Collection root = m_serializer->createCollectionFromDataSource(source); - auto &query = m_findSearchChildren[root.id()]; - auto fetch = m_helpers->searchCollections(root, &m_searchTerm, m_contentTypes); - auto predicate = createSearchPredicate(root); - m_integrator->bind("DataSourceQueries::findSearchChildren", query, fetch, predicate); - return query->result(); -} - DataSourceQueries::CollectionInputQuery::PredicateFunction DataSourceQueries::createFetchPredicate(const Collection &root) const { return [this, root] (const Collection &collection) { - return collection.isValid() - && collection.parentCollection() == root - && m_serializer->isListedCollection(collection); - }; -} - -DataSourceQueries::CollectionInputQuery::PredicateFunction DataSourceQueries::createSearchPredicate(const Collection &root) const -{ - return [root] (const Collection &collection) { return collection.isValid() && collection.parentCollection() == root; }; } diff --git a/src/akonadi/akonadidatasourcequeries.h b/src/akonadi/akonadidatasourcequeries.h index 603f1f4e..96d53b5a 100644 --- a/src/akonadi/akonadidatasourcequeries.h +++ b/src/akonadi/akonadidatasourcequeries.h @@ -1,80 +1,71 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #ifndef AKONADI_DATASOURCEQUERIES_H #define AKONADI_DATASOURCEQUERIES_H #include "domain/datasourcequeries.h" #include "akonadi/akonadilivequeryhelpers.h" #include "akonadi/akonadilivequeryintegrator.h" namespace Akonadi { class DataSourceQueries : public Domain::DataSourceQueries { public: typedef QSharedPointer Ptr; typedef Domain::LiveQueryInput CollectionInputQuery; typedef Domain::LiveQueryOutput DataSourceQueryOutput; typedef Domain::QueryResultProvider DataSourceProvider; typedef Domain::QueryResult DataSourceResult; DataSourceQueries(StorageInterface::FetchContentTypes contentTypes, const StorageInterface::Ptr &storage, const SerializerInterface::Ptr &serializer, const MonitorInterface::Ptr &monitor); bool isDefaultSource(Domain::DataSource::Ptr source) const Q_DECL_OVERRIDE; private: void changeDefaultSource(Domain::DataSource::Ptr source) Q_DECL_OVERRIDE; public: DataSourceResult::Ptr findTopLevel() const Q_DECL_OVERRIDE; DataSourceResult::Ptr findChildren(Domain::DataSource::Ptr source) const Q_DECL_OVERRIDE; - QString searchTerm() const Q_DECL_OVERRIDE; - void setSearchTerm(const QString &term) Q_DECL_OVERRIDE; - DataSourceResult::Ptr findSearchTopLevel() const Q_DECL_OVERRIDE; - DataSourceResult::Ptr findSearchChildren(Domain::DataSource::Ptr source) const Q_DECL_OVERRIDE; - private: CollectionInputQuery::PredicateFunction createFetchPredicate(const Collection &root) const; - CollectionInputQuery::PredicateFunction createSearchPredicate(const Collection &root) const; StorageInterface::FetchContentTypes m_contentTypes; SerializerInterface::Ptr m_serializer; LiveQueryHelpers::Ptr m_helpers; LiveQueryIntegrator::Ptr m_integrator; mutable DataSourceQueryOutput::Ptr m_findTopLevel; mutable QHash m_findChildren; - QString m_searchTerm; - mutable DataSourceQueryOutput::Ptr m_findSearchTopLevel; - mutable QHash m_findSearchChildren; }; } #endif // AKONADI_DATASOURCEQUERIES_H diff --git a/src/akonadi/akonadilivequeryhelpers.cpp b/src/akonadi/akonadilivequeryhelpers.cpp index ff49a835..83c9d27c 100644 --- a/src/akonadi/akonadilivequeryhelpers.cpp +++ b/src/akonadi/akonadilivequeryhelpers.cpp @@ -1,203 +1,172 @@ /* This file is part of Zanshin Copyright 2015 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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 "akonadilivequeryhelpers.h" #include "akonadi/akonadicollectionfetchjobinterface.h" -#include "akonadi/akonadicollectionsearchjobinterface.h" #include "akonadi/akonadiitemfetchjobinterface.h" #include "akonadi/akonaditagfetchjobinterface.h" #include "utils/jobhandler.h" using namespace Akonadi; LiveQueryHelpers::LiveQueryHelpers(const SerializerInterface::Ptr &serializer, const StorageInterface::Ptr &storage) : m_serializer(serializer), m_storage(storage) { } LiveQueryHelpers::CollectionFetchFunction LiveQueryHelpers::fetchAllCollections(StorageInterface::FetchContentTypes contentTypes) const { auto storage = m_storage; return [storage, contentTypes] (const Domain::LiveQueryInput::AddFunction &add) { auto job = storage->fetchCollections(Collection::root(), StorageInterface::Recursive, contentTypes); Utils::JobHandler::install(job->kjob(), [job, add] { if (job->kjob()->error()) return; foreach (const auto &collection, job->collections()) add(collection); }); }; } LiveQueryHelpers::CollectionFetchFunction LiveQueryHelpers::fetchCollections(const Collection &root, StorageInterface::FetchContentTypes contentTypes) const { auto storage = m_storage; return [storage, contentTypes, root] (const Domain::LiveQueryInput::AddFunction &add) { auto job = storage->fetchCollections(root, StorageInterface::Recursive, contentTypes); Utils::JobHandler::install(job->kjob(), [root, job, add] { if (job->kjob()->error()) return; auto directChildren = QHash(); foreach (const auto &collection, job->collections()) { auto directChild = collection; while (directChild.parentCollection() != root) directChild = directChild.parentCollection(); if (!directChildren.contains(directChild.id())) directChildren[directChild.id()] = directChild; } foreach (const auto &directChild, directChildren) add(directChild); }); }; } -LiveQueryHelpers::CollectionFetchFunction LiveQueryHelpers::searchCollections(const Collection &root, const QString *searchTerm, - StorageInterface::FetchContentTypes contentTypes) const -{ - auto storage = m_storage; - return [storage, contentTypes, searchTerm, root] (const Domain::LiveQueryInput::AddFunction &add) { - if (searchTerm->isEmpty()) - return; - - auto job = storage->searchCollections(*searchTerm, contentTypes); - Utils::JobHandler::install(job->kjob(), [root, job, add] { - if (job->kjob()->error()) - return; - - auto directChildren = QHash(); - foreach (const auto &collection, job->collections()) { - auto directChild = collection; - while (directChild.parentCollection() != root && directChild.parentCollection().isValid()) - directChild = directChild.parentCollection(); - if (directChild.parentCollection() != root) - continue; - if (!directChildren.contains(directChild.id())) - directChildren[directChild.id()] = directChild; - } - - foreach (const auto &directChild, directChildren) - add(directChild); - }); - }; -} - LiveQueryHelpers::ItemFetchFunction LiveQueryHelpers::fetchItems(StorageInterface::FetchContentTypes contentTypes) const { auto serializer = m_serializer; auto storage = m_storage; return [serializer, storage, contentTypes] (const Domain::LiveQueryInput::AddFunction &add) { auto job = storage->fetchCollections(Akonadi::Collection::root(), StorageInterface::Recursive, contentTypes); Utils::JobHandler::install(job->kjob(), [serializer, storage, job, add] { if (job->kjob()->error() != KJob::NoError) return; foreach (const auto &collection, job->collections()) { if (!serializer->isSelectedCollection(collection)) continue; auto job = storage->fetchItems(collection); Utils::JobHandler::install(job->kjob(), [job, add] { if (job->kjob()->error() != KJob::NoError) return; foreach (const auto &item, job->items()) add(item); }); } }); }; } LiveQueryHelpers::ItemFetchFunction LiveQueryHelpers::fetchItems(const Tag &tag) const { // TODO: Qt5, use the proper implementation once we got a working akonadi #if 0 auto storage = m_storage; return [storage, tag] (const Domain::LiveQueryInput::AddFunction &add) { auto job = storage->fetchTagItems(tag); Utils::JobHandler::install(job->kjob(), [job, add] { if (job->kjob()->error() != KJob::NoError) return; foreach (const auto &item, job->items()) add(item); }); }; #else auto fetchFunction = fetchItems(StorageInterface::Tasks | StorageInterface::Notes); return [tag, fetchFunction] (const Domain::LiveQueryInput::AddFunction &add) { auto filterAdd = [tag, add] (const Item &item) { if (item.tags().contains(tag)) add(item); }; fetchFunction(filterAdd); }; #endif } LiveQueryHelpers::ItemFetchFunction LiveQueryHelpers::fetchSiblings(const Item &item) const { auto storage = m_storage; return [storage, item] (const Domain::LiveQueryInput::AddFunction &add) { auto job = storage->fetchItem(item); Utils::JobHandler::install(job->kjob(), [storage, job, add] { if (job->kjob()->error() != KJob::NoError) return; Q_ASSERT(job->items().size() == 1); auto item = job->items().at(0); Q_ASSERT(item.parentCollection().isValid()); auto job = storage->fetchItems(item.parentCollection()); Utils::JobHandler::install(job->kjob(), [job, add] { if (job->kjob()->error() != KJob::NoError) return; foreach (const auto &item, job->items()) add(item); }); }); }; } LiveQueryHelpers::TagFetchFunction LiveQueryHelpers::fetchTags() const { auto storage = m_storage; return [storage] (const Domain::LiveQueryInput::AddFunction &add) { auto job = storage->fetchTags(); Utils::JobHandler::install(job->kjob(), [job, add] { foreach (const auto &tag, job->tags()) add(tag); }); }; } diff --git a/src/akonadi/akonadilivequeryhelpers.h b/src/akonadi/akonadilivequeryhelpers.h index 5583f01c..33dd9d28 100644 --- a/src/akonadi/akonadilivequeryhelpers.h +++ b/src/akonadi/akonadilivequeryhelpers.h @@ -1,65 +1,63 @@ /* This file is part of Zanshin Copyright 2015 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #ifndef AKONADI_LIVEQUERYHELPERS_H #define AKONADI_LIVEQUERYHELPERS_H #include "akonadi/akonadiserializerinterface.h" #include "akonadi/akonadistorageinterface.h" #include "domain/livequery.h" namespace Akonadi { class LiveQueryHelpers { public: typedef QSharedPointer Ptr; typedef Domain::LiveQueryInput::FetchFunction CollectionFetchFunction; typedef Domain::LiveQueryInput::FetchFunction ItemFetchFunction; typedef Domain::LiveQueryInput::FetchFunction TagFetchFunction; LiveQueryHelpers(const SerializerInterface::Ptr &serializer, const StorageInterface::Ptr &storage); CollectionFetchFunction fetchAllCollections(StorageInterface::FetchContentTypes contentTypes) const; CollectionFetchFunction fetchCollections(const Collection &root, StorageInterface::FetchContentTypes contentTypes) const; - CollectionFetchFunction searchCollections(const Collection &root, const QString *searchTerm, - StorageInterface::FetchContentTypes contentTypes) const; ItemFetchFunction fetchItems(StorageInterface::FetchContentTypes contentTypes) const; ItemFetchFunction fetchItems(const Tag &tag) const; ItemFetchFunction fetchSiblings(const Item &item) const; TagFetchFunction fetchTags() const; private: SerializerInterface::Ptr m_serializer; StorageInterface::Ptr m_storage; }; } #endif // AKONADI_LIVEQUERYHELPERS_H diff --git a/src/akonadi/akonadiserializer.cpp b/src/akonadi/akonadiserializer.cpp index bd600366..e53cac2e 100644 --- a/src/akonadi/akonadiserializer.cpp +++ b/src/akonadi/akonadiserializer.cpp @@ -1,643 +1,610 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens Copyright 2014 Rémi Benoit 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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 "akonadiserializer.h" #include #include #include #include #include #include #include #include "utils/mem_fn.h" #include "akonadi/akonadiapplicationselectedattribute.h" #include "akonadi/akonaditimestampattribute.h" using namespace Akonadi; Serializer::Serializer() { } Serializer::~Serializer() { } bool Serializer::representsCollection(SerializerInterface::QObjectPtr object, Collection collection) { return object->property("collectionId").toLongLong() == collection.id(); } bool Serializer::representsItem(QObjectPtr object, Item item) { return object->property("itemId").toLongLong() == item.id(); } bool Serializer::representsAkonadiTag(Domain::Tag::Ptr tag, Tag akonadiTag) const { return tag->property("tagId").toLongLong() == akonadiTag.id(); } QString Serializer::objectUid(SerializerInterface::QObjectPtr object) { return object->property("todoUid").toString(); } Domain::DataSource::Ptr Serializer::createDataSourceFromCollection(Collection collection, DataSourceNameScheme naming) { if (!collection.isValid()) return Domain::DataSource::Ptr(); auto dataSource = Domain::DataSource::Ptr::create(); updateDataSourceFromCollection(dataSource, collection, naming); return dataSource; } void Serializer::updateDataSourceFromCollection(Domain::DataSource::Ptr dataSource, Collection collection, DataSourceNameScheme naming) { if (!collection.isValid()) return; QString name = collection.displayName(); if (naming == FullPath) { auto parent = collection.parentCollection(); while (parent.isValid() && parent != Akonadi::Collection::root()) { name = parent.displayName() + '/' + name; parent = parent.parentCollection(); } } dataSource->setName(name); const auto mimeTypes = collection.contentMimeTypes(); auto types = Domain::DataSource::ContentTypes(); if (mimeTypes.contains(NoteUtils::noteMimeType())) types |= Domain::DataSource::Notes; if (mimeTypes.contains(KCalCore::Todo::todoMimeType())) types |= Domain::DataSource::Tasks; dataSource->setContentTypes(types); if (collection.hasAttribute()) { auto iconName = collection.attribute()->iconName(); dataSource->setIconName(iconName); } if (!collection.hasAttribute()) { dataSource->setSelected(true); } else { auto isSelected = collection.attribute()->isSelected(); dataSource->setSelected(isSelected); } - if (collection.enabled()) - dataSource->setListStatus(Domain::DataSource::Bookmarked); - else if (collection.referenced()) - dataSource->setListStatus(Domain::DataSource::Listed); - else - dataSource->setListStatus(Domain::DataSource::Unlisted); - dataSource->setProperty("collectionId", collection.id()); } Collection Serializer::createCollectionFromDataSource(Domain::DataSource::Ptr dataSource) { const auto id = dataSource->property("collectionId").value(); auto collection = Collection(id); collection.attribute(Akonadi::Collection::AddIfMissing); auto selectedAttribute = collection.attribute(Akonadi::Collection::AddIfMissing); selectedAttribute->setSelected(dataSource->isSelected()); - switch (dataSource->listStatus()) { - case Domain::DataSource::Unlisted: - collection.setReferenced(false); - collection.setEnabled(false); - break; - case Domain::DataSource::Listed: - collection.setReferenced(true); - collection.setEnabled(false); - break; - case Domain::DataSource::Bookmarked: - collection.setReferenced(false); - collection.setEnabled(true); - break; - default: - qFatal("Shouldn't happen"); - break; - } - return collection; } -bool Serializer::isListedCollection(Collection collection) -{ - return collection.enabled() || collection.referenced(); -} - bool Serializer::isSelectedCollection(Collection collection) { - if (!isListedCollection(collection)) - return false; - if (!isNoteCollection(collection) && !isTaskCollection(collection)) return false; if (!collection.hasAttribute()) return true; return collection.attribute()->isSelected(); } bool Akonadi::Serializer::isNoteCollection(Akonadi::Collection collection) { return collection.contentMimeTypes().contains(NoteUtils::noteMimeType()); } bool Akonadi::Serializer::isTaskCollection(Akonadi::Collection collection) { return collection.contentMimeTypes().contains(KCalCore::Todo::todoMimeType()); } bool Serializer::isTaskItem(Item item) { if (!item.hasPayload()) return false; auto todo = item.payload(); return todo->customProperty("Zanshin", "Project").isEmpty(); } Domain::Task::Ptr Serializer::createTaskFromItem(Item item) { if (!isTaskItem(item)) return Domain::Task::Ptr(); auto task = Domain::Task::Ptr::create(); updateTaskFromItem(task, item); return task; } void Serializer::updateTaskFromItem(Domain::Task::Ptr task, Item item) { if (!isTaskItem(item)) return; auto todo = item.payload(); task->setTitle(todo->summary()); task->setText(todo->description()); task->setDone(todo->isCompleted()); task->setDoneDate(todo->completed().dateTime().toUTC()); task->setStartDate(todo->dtStart().dateTime().toUTC()); task->setDueDate(todo->dtDue().dateTime().toUTC()); task->setProperty("itemId", item.id()); task->setProperty("parentCollectionId", item.parentCollection().id()); task->setProperty("todoUid", todo->uid()); task->setProperty("relatedUid", todo->relatedTo()); task->setRunning(todo->customProperty("Zanshin", "Running") == QLatin1String("1")); if (todo->attendeeCount() > 0) { const auto attendees = todo->attendees(); const auto delegate = std::find_if(attendees.begin(), attendees.end(), [] (const KCalCore::Attendee::Ptr &attendee) { return attendee->status() == KCalCore::Attendee::Accepted; }); if (delegate != attendees.end()) { task->setDelegate(Domain::Task::Delegate((*delegate)->name(), (*delegate)->email())); } } } bool Serializer::isTaskChild(Domain::Task::Ptr task, Akonadi::Item item) { if (!isTaskItem(item)) return false; auto todo = item.payload(); if (todo->relatedTo() == task->property("todoUid")) return true; return false; } Akonadi::Item Serializer::createItemFromTask(Domain::Task::Ptr task) { auto todo = KCalCore::Todo::Ptr::create(); todo->setSummary(task->title()); todo->setDescription(task->text()); if (task->isDone()) todo->setCompleted(KDateTime(task->doneDate())); else todo->setCompleted(false); todo->setDtStart(KDateTime(task->startDate(), KDateTime::UTC)); todo->setDtDue(KDateTime(task->dueDate(), KDateTime::UTC)); if (task->property("todoUid").isValid()) { todo->setUid(task->property("todoUid").toString()); } if (task->property("relatedUid").isValid()) { todo->setRelatedTo(task->property("relatedUid").toString()); } if (task->delegate().isValid()) { KCalCore::Attendee::Ptr attendee(new KCalCore::Attendee(task->delegate().name(), task->delegate().email(), true, KCalCore::Attendee::Accepted)); todo->addAttendee(attendee); } if (task->isRunning()) { todo->setCustomProperty("Zanshin", "Running", "1"); } else { todo->removeCustomProperty("Zanshin", "Running"); } Akonadi::Item item; if (task->property("itemId").isValid()) { item.setId(task->property("itemId").value()); } if (task->property("parentCollectionId").isValid()) { auto parentId = task->property("parentCollectionId").value(); item.setParentCollection(Akonadi::Collection(parentId)); } item.setMimeType(KCalCore::Todo::todoMimeType()); item.setPayload(todo); return item; } QString Serializer::relatedUidFromItem(Akonadi::Item item) { if (isTaskItem(item)) { const auto todo = item.payload(); return todo->relatedTo(); } else if (isNoteItem(item)) { const auto message = item.payload(); const auto relatedHeader = message->headerByType("X-Zanshin-RelatedProjectUid"); return relatedHeader ? relatedHeader->asUnicodeString() : QString(); } else { return QString(); } } void Serializer::updateItemParent(Akonadi::Item item, Domain::Task::Ptr parent) { if (!isTaskItem(item)) return; auto todo = item.payload(); todo->setRelatedTo(parent->property("todoUid").toString()); } void Serializer::updateItemProject(Item item, Domain::Project::Ptr project) { if (isTaskItem(item)) { auto todo = item.payload(); todo->setRelatedTo(project->property("todoUid").toString()); } else if (isNoteItem(item)) { auto note = item.payload(); note->removeHeader("X-Zanshin-RelatedProjectUid"); const QByteArray parentUid = project->property("todoUid").toString().toUtf8(); if (!parentUid.isEmpty()) { auto relatedHeader = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); relatedHeader->from7BitString(parentUid); note->appendHeader(relatedHeader); } note->assemble(); } } void Serializer::removeItemParent(Akonadi::Item item) { if (!isTaskItem(item)) return; auto todo = item.payload(); todo->setRelatedTo(QString()); } void Serializer::promoteItemToProject(Akonadi::Item item) { if (!isTaskItem(item)) return; auto todo = item.payload(); todo->setRelatedTo(QString()); todo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); } void Serializer::clearItem(Akonadi::Item *item) { Q_ASSERT(item); if (!isTaskItem(*item)) return; // NOTE : Currently not working, when akonadistorage test will make it pass, we will use it // item->clearTags(); foreach (const Tag& tag, item->tags()) item->clearTag(tag); } Akonadi::Item::List Serializer::filterDescendantItems(const Akonadi::Item::List &potentialChildren, const Akonadi::Item &ancestorItem) { if (potentialChildren.isEmpty()) return Akonadi::Item::List(); Akonadi::Item::List itemsToProcess = potentialChildren; Q_ASSERT(ancestorItem.isValid() && ancestorItem.hasPayload()); KCalCore::Todo::Ptr todo = ancestorItem.payload(); const auto bound = std::partition(itemsToProcess.begin(), itemsToProcess.end(), [ancestorItem, todo](Akonadi::Item currentItem) { return (!currentItem.hasPayload() || currentItem == ancestorItem || currentItem.payload()->relatedTo() != todo->uid()); }); Akonadi::Item::List itemsRemoved; std::copy(itemsToProcess.begin(), bound, std::back_inserter(itemsRemoved)); itemsToProcess.erase(itemsToProcess.begin(), bound); auto result = std::accumulate(itemsToProcess.begin(), itemsToProcess.end(), Akonadi::Item::List(), [this, itemsRemoved](Akonadi::Item::List result, Akonadi::Item currentItem) { result << currentItem; return result += filterDescendantItems(itemsRemoved, currentItem); }); return result; } bool Serializer::isNoteItem(Item item) { return item.hasPayload(); } Domain::Note::Ptr Serializer::createNoteFromItem(Akonadi::Item item) { if (!isNoteItem(item)) return Domain::Note::Ptr(); Domain::Note::Ptr note = Domain::Note::Ptr::create(); updateNoteFromItem(note, item); return note; } void Serializer::updateNoteFromItem(Domain::Note::Ptr note, Item item) { if (!isNoteItem(item)) return; auto message = item.payload(); note->setTitle(message->subject(true)->asUnicodeString()); note->setText(message->mainBodyPart()->decodedText()); note->setProperty("itemId", item.id()); if (auto relatedHeader = message->headerByType("X-Zanshin-RelatedProjectUid")) { note->setProperty("relatedUid", relatedHeader->asUnicodeString()); } else { note->setProperty("relatedUid", QVariant()); } } Item Serializer::createItemFromNote(Domain::Note::Ptr note) { NoteUtils::NoteMessageWrapper builder; builder.setTitle(note->title()); builder.setText(note->text() + '\n'); // Adding an extra '\n' because KMime always removes it... KMime::Message::Ptr message = builder.message(); if (!note->property("relatedUid").toString().isEmpty()) { auto relatedHeader = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); relatedHeader->from7BitString(note->property("relatedUid").toString().toUtf8()); message->appendHeader(relatedHeader); } Akonadi::Item item; if (note->property("itemId").isValid()) { item.setId(note->property("itemId").value()); } item.setMimeType(Akonadi::NoteUtils::noteMimeType()); item.setPayload(message); return item; } bool Serializer::isProjectItem(Item item) { if (!item.hasPayload()) return false; return !isTaskItem(item); } Domain::Project::Ptr Serializer::createProjectFromItem(Item item) { if (!isProjectItem(item)) return Domain::Project::Ptr(); auto project = Domain::Project::Ptr::create(); updateProjectFromItem(project, item); return project; } void Serializer::updateProjectFromItem(Domain::Project::Ptr project, Item item) { if (!isProjectItem(item)) return; auto todo = item.payload(); project->setName(todo->summary()); project->setProperty("itemId", item.id()); project->setProperty("parentCollectionId", item.parentCollection().id()); project->setProperty("todoUid", todo->uid()); } Item Serializer::createItemFromProject(Domain::Project::Ptr project) { auto todo = KCalCore::Todo::Ptr::create(); todo->setSummary(project->name()); todo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); if (project->property("todoUid").isValid()) { todo->setUid(project->property("todoUid").toString()); } Akonadi::Item item; if (project->property("itemId").isValid()) { item.setId(project->property("itemId").value()); } if (project->property("parentCollectionId").isValid()) { auto parentId = project->property("parentCollectionId").value(); item.setParentCollection(Akonadi::Collection(parentId)); } item.setMimeType(KCalCore::Todo::todoMimeType()); item.setPayload(todo); return item; } bool Serializer::isProjectChild(Domain::Project::Ptr project, Item item) { const QString todoUid = project->property("todoUid").toString(); const QString relatedUid = relatedUidFromItem(item); return !todoUid.isEmpty() && !relatedUid.isEmpty() && todoUid == relatedUid; } Domain::Context::Ptr Serializer::createContextFromTag(Akonadi::Tag tag) { if (!isContext(tag)) return Domain::Context::Ptr(); auto context = Domain::Context::Ptr::create(); updateContextFromTag(context, tag); return context; } Akonadi::Tag Serializer::createTagFromContext(Domain::Context::Ptr context) { auto tag = Akonadi::Tag(); tag.setName(context->name()); tag.setType(Akonadi::SerializerInterface::contextTagType()); tag.setGid(QByteArray(context->name().toLatin1())); if (context->property("tagId").isValid()) tag.setId(context->property("tagId").value()); return tag; } void Serializer::updateContextFromTag(Domain::Context::Ptr context, Akonadi::Tag tag) { if (!isContext(tag)) return; context->setProperty("tagId", tag.id()); context->setName(tag.name()); } bool Serializer::hasContextTags(Item item) const { using namespace std::placeholders; Tag::List tags = item.tags(); return std::any_of(tags.constBegin(), tags.constEnd(), std::bind(Utils::mem_fn(&Serializer::isContext), this, _1)); } bool Serializer::hasAkonadiTags(Item item) const { using namespace std::placeholders; Tag::List tags = item.tags(); return std::any_of(tags.constBegin(), tags.constEnd(), std::bind(Utils::mem_fn(&Serializer::isAkonadiTag), this, _1)); } bool Serializer::isContext(const Akonadi::Tag &tag) const { return (tag.type() == Akonadi::SerializerInterface::contextTagType()); } bool Serializer::isAkonadiTag(const Tag &tag) const { return tag.type() == Akonadi::Tag::PLAIN; } bool Serializer::isContextTag(const Domain::Context::Ptr &context, const Akonadi::Tag &tag) const { return (context->property("tagId").value() == tag.id()); } bool Serializer::isContextChild(Domain::Context::Ptr context, Item item) const { if (!context->property("tagId").isValid()) return false; auto tagId = context->property("tagId").value(); Akonadi::Tag tag(tagId); return item.hasTag(tag); } Domain::Tag::Ptr Serializer::createTagFromAkonadiTag(Akonadi::Tag akonadiTag) { if (!isAkonadiTag(akonadiTag)) return Domain::Tag::Ptr(); auto tag = Domain::Tag::Ptr::create(); updateTagFromAkonadiTag(tag, akonadiTag); return tag; } void Serializer::updateTagFromAkonadiTag(Domain::Tag::Ptr tag, Akonadi::Tag akonadiTag) { if (!isAkonadiTag(akonadiTag)) return; tag->setProperty("tagId", akonadiTag.id()); tag->setName(akonadiTag.name()); } Akonadi::Tag Serializer::createAkonadiTagFromTag(Domain::Tag::Ptr tag) { auto akonadiTag = Akonadi::Tag(); akonadiTag.setName(tag->name()); akonadiTag.setType(Akonadi::Tag::PLAIN); akonadiTag.setGid(QByteArray(tag->name().toLatin1())); const auto tagProperty = tag->property("tagId"); if (tagProperty.isValid()) akonadiTag.setId(tagProperty.value()); return akonadiTag; } bool Serializer::isTagChild(Domain::Tag::Ptr tag, Akonadi::Item item) { if (!tag->property("tagId").isValid()) return false; auto tagId = tag->property("tagId").value(); Akonadi::Tag akonadiTag(tagId); return item.hasTag(akonadiTag); } diff --git a/src/akonadi/akonadiserializer.h b/src/akonadi/akonadiserializer.h index c0cc4a25..c28dbae4 100644 --- a/src/akonadi/akonadiserializer.h +++ b/src/akonadi/akonadiserializer.h @@ -1,100 +1,99 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens Copyright 2014 Rémi Benoit 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #ifndef AKONADI_SERIALIZER_H #define AKONADI_SERIALIZER_H #include "akonadiserializerinterface.h" namespace Akonadi { class Item; class Tag; class Serializer : public SerializerInterface { public: Serializer(); virtual ~Serializer(); bool representsCollection(QObjectPtr object, Collection collection) Q_DECL_OVERRIDE; bool representsItem(QObjectPtr object, Item item) Q_DECL_OVERRIDE; bool representsAkonadiTag(Domain::Tag::Ptr tag, Akonadi::Tag akonadiTag) const Q_DECL_OVERRIDE; QString objectUid(QObjectPtr object) Q_DECL_OVERRIDE; Domain::DataSource::Ptr createDataSourceFromCollection(Akonadi::Collection collection, DataSourceNameScheme naming) Q_DECL_OVERRIDE; void updateDataSourceFromCollection(Domain::DataSource::Ptr dataSource, Akonadi::Collection collection, DataSourceNameScheme naming) Q_DECL_OVERRIDE; virtual Akonadi::Collection createCollectionFromDataSource(Domain::DataSource::Ptr dataSource) Q_DECL_OVERRIDE; - bool isListedCollection(Akonadi::Collection collection) Q_DECL_OVERRIDE; virtual bool isSelectedCollection(Akonadi::Collection collection) Q_DECL_OVERRIDE; virtual bool isNoteCollection(Akonadi::Collection collection) Q_DECL_OVERRIDE; virtual bool isTaskCollection(Akonadi::Collection collection) Q_DECL_OVERRIDE; bool isTaskItem(Akonadi::Item item) Q_DECL_OVERRIDE; Domain::Task::Ptr createTaskFromItem(Akonadi::Item item) Q_DECL_OVERRIDE; void updateTaskFromItem(Domain::Task::Ptr task, Akonadi::Item item) Q_DECL_OVERRIDE; Akonadi::Item createItemFromTask(Domain::Task::Ptr task) Q_DECL_OVERRIDE; bool isTaskChild(Domain::Task::Ptr task, Akonadi::Item item) Q_DECL_OVERRIDE; QString relatedUidFromItem(Akonadi::Item item) Q_DECL_OVERRIDE; void updateItemParent(Akonadi::Item item, Domain::Task::Ptr parent) Q_DECL_OVERRIDE; void updateItemProject(Akonadi::Item item, Domain::Project::Ptr project) Q_DECL_OVERRIDE; void removeItemParent(Akonadi::Item item) Q_DECL_OVERRIDE; void promoteItemToProject(Akonadi::Item item) Q_DECL_OVERRIDE; void clearItem(Akonadi::Item *item) Q_DECL_OVERRIDE; Akonadi::Item::List filterDescendantItems(const Akonadi::Item::List &potentialChildren, const Akonadi::Item &ancestorItem) Q_DECL_OVERRIDE; bool isNoteItem(Akonadi::Item item) Q_DECL_OVERRIDE; Domain::Note::Ptr createNoteFromItem(Akonadi::Item item) Q_DECL_OVERRIDE; void updateNoteFromItem(Domain::Note::Ptr note, Akonadi::Item item) Q_DECL_OVERRIDE; Akonadi::Item createItemFromNote(Domain::Note::Ptr note) Q_DECL_OVERRIDE; bool isProjectItem(Akonadi::Item item) Q_DECL_OVERRIDE; Domain::Project::Ptr createProjectFromItem(Akonadi::Item item) Q_DECL_OVERRIDE; void updateProjectFromItem(Domain::Project::Ptr project, Akonadi::Item item) Q_DECL_OVERRIDE; Akonadi::Item createItemFromProject(Domain::Project::Ptr project) Q_DECL_OVERRIDE; bool isProjectChild(Domain::Project::Ptr project, Akonadi::Item item) Q_DECL_OVERRIDE; Domain::Context::Ptr createContextFromTag(Akonadi::Tag tag) Q_DECL_OVERRIDE; void updateContextFromTag(Domain::Context::Ptr context, Akonadi::Tag tag) Q_DECL_OVERRIDE; Akonadi::Tag createTagFromContext(Domain::Context::Ptr context) Q_DECL_OVERRIDE; bool isContextTag(const Domain::Context::Ptr &context, const Akonadi::Tag &tag) const Q_DECL_OVERRIDE; bool isContextChild(Domain::Context::Ptr context, Akonadi::Item item) const Q_DECL_OVERRIDE; Domain::Tag::Ptr createTagFromAkonadiTag(Akonadi::Tag akonadiTag) Q_DECL_OVERRIDE; void updateTagFromAkonadiTag(Domain::Tag::Ptr tag, Akonadi::Tag akonadiTag) Q_DECL_OVERRIDE; Akonadi::Tag createAkonadiTagFromTag(Domain::Tag::Ptr tag) Q_DECL_OVERRIDE; bool isTagChild(Domain::Tag::Ptr tag, Akonadi::Item item) Q_DECL_OVERRIDE; bool hasContextTags(Akonadi::Item item) const Q_DECL_OVERRIDE; bool hasAkonadiTags(Akonadi::Item item) const Q_DECL_OVERRIDE; private: bool isContext(const Akonadi::Tag &tag) const; bool isAkonadiTag(const Akonadi::Tag &tag) const; }; } #endif // AKONADI_SERIALIZERINTERFACE_H diff --git a/src/akonadi/akonadiserializerinterface.h b/src/akonadi/akonadiserializerinterface.h index cd661931..6dbe5c32 100644 --- a/src/akonadi/akonadiserializerinterface.h +++ b/src/akonadi/akonadiserializerinterface.h @@ -1,118 +1,117 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #ifndef AKONADI_SERIALIZERINTERFACE_H #define AKONADI_SERIALIZERINTERFACE_H #include "domain/datasource.h" #include "domain/tag.h" #include "domain/task.h" #include "domain/note.h" #include "domain/project.h" #include "domain/context.h" #include namespace Akonadi { class Collection; class Item; class Tag; class SerializerInterface { public: typedef QSharedPointer Ptr; typedef QSharedPointer QObjectPtr; enum DataSourceNameScheme { FullPath, BaseName }; SerializerInterface(); virtual ~SerializerInterface(); virtual bool representsCollection(QObjectPtr object, Akonadi::Collection collection) = 0; virtual bool representsItem(QObjectPtr object, Akonadi::Item item) = 0; virtual bool representsAkonadiTag(Domain::Tag::Ptr tag, Akonadi::Tag akonadiTag) const = 0; virtual QString objectUid(QObjectPtr object) = 0; virtual Domain::DataSource::Ptr createDataSourceFromCollection(Akonadi::Collection collection, DataSourceNameScheme naming) = 0; virtual void updateDataSourceFromCollection(Domain::DataSource::Ptr dataSource, Akonadi::Collection collection, DataSourceNameScheme naming) = 0; virtual Akonadi::Collection createCollectionFromDataSource(Domain::DataSource::Ptr dataSource) = 0; - virtual bool isListedCollection(Akonadi::Collection collection) = 0; virtual bool isSelectedCollection(Akonadi::Collection collection) = 0; virtual bool isNoteCollection(Akonadi::Collection collection) = 0; virtual bool isTaskCollection(Akonadi::Collection collection) = 0; virtual bool isTaskItem(Akonadi::Item item) = 0; virtual Domain::Task::Ptr createTaskFromItem(Akonadi::Item item) = 0; virtual void updateTaskFromItem(Domain::Task::Ptr task, Akonadi::Item item) = 0; virtual Akonadi::Item createItemFromTask(Domain::Task::Ptr task) = 0; virtual bool isTaskChild(Domain::Task::Ptr task, Akonadi::Item item) = 0; virtual QString relatedUidFromItem(Akonadi::Item item) = 0; virtual void updateItemParent(Akonadi::Item item, Domain::Task::Ptr parent) = 0; virtual void updateItemProject(Akonadi::Item item, Domain::Project::Ptr project) = 0; virtual void removeItemParent(Akonadi::Item item) = 0; virtual void promoteItemToProject(Akonadi::Item item) = 0; virtual void clearItem(Akonadi::Item *item) = 0; virtual Akonadi::Item::List filterDescendantItems(const Akonadi::Item::List &potentialChildren, const Akonadi::Item &ancestorItem) = 0; virtual bool isNoteItem(Akonadi::Item item) = 0; virtual Domain::Note::Ptr createNoteFromItem(Akonadi::Item item) = 0; virtual void updateNoteFromItem(Domain::Note::Ptr note, Akonadi::Item item) = 0; virtual Akonadi::Item createItemFromNote(Domain::Note::Ptr note) = 0; Domain::Artifact::Ptr createArtifactFromItem(const Akonadi::Item &item); void updateArtifactFromItem(const Domain::Artifact::Ptr &artifact, const Akonadi::Item &item); virtual bool isProjectItem(Akonadi::Item item) = 0; virtual Domain::Project::Ptr createProjectFromItem(Akonadi::Item item) = 0; virtual void updateProjectFromItem(Domain::Project::Ptr project, Akonadi::Item item) = 0; virtual Akonadi::Item createItemFromProject(Domain::Project::Ptr project) = 0; virtual bool isProjectChild(Domain::Project::Ptr project, Akonadi::Item item) = 0; virtual Domain::Context::Ptr createContextFromTag(Akonadi::Tag tag) = 0; virtual void updateContextFromTag(Domain::Context::Ptr context, Akonadi::Tag tag) = 0; virtual Akonadi::Tag createTagFromContext(Domain::Context::Ptr context) = 0; virtual bool isContextTag(const Domain::Context::Ptr &context, const Akonadi::Tag &tag) const = 0; virtual bool isContextChild(Domain::Context::Ptr context, Akonadi::Item item) const = 0; virtual Domain::Tag::Ptr createTagFromAkonadiTag(Akonadi::Tag tag) = 0; virtual void updateTagFromAkonadiTag(Domain::Tag::Ptr tag, Akonadi::Tag akonadiTag) = 0; virtual Akonadi::Tag createAkonadiTagFromTag(Domain::Tag::Ptr tag) = 0; virtual bool isTagChild(Domain::Tag::Ptr tag, Akonadi::Item item) = 0; virtual bool hasContextTags(Akonadi::Item item) const = 0; virtual bool hasAkonadiTags(Akonadi::Item item) const = 0; static QByteArray contextTagType(); }; } #endif // AKONADI_SERIALIZERINTERFACE_H diff --git a/src/akonadi/akonadistorage.cpp b/src/akonadi/akonadistorage.cpp index 44014fe9..5baf2436 100644 --- a/src/akonadi/akonadistorage.cpp +++ b/src/akonadi/akonadistorage.cpp @@ -1,407 +1,317 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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 "akonadistorage.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "akonadi/akonadicollectionfetchjobinterface.h" -#include "akonadi/akonadicollectionsearchjobinterface.h" #include "akonadi/akonadiitemfetchjobinterface.h" #include "akonadi/akonaditagfetchjobinterface.h" #include "akonadi/akonadistoragesettings.h" using namespace Akonadi; class CollectionJob : public CollectionFetchJob, public CollectionFetchJobInterface { Q_OBJECT public: CollectionJob(const Collection &collection, Type type = FirstLevel, QObject *parent = Q_NULLPTR) : CollectionFetchJob(collection, type, parent), m_collection(collection), m_type(type) { } Collection::List collections() const Q_DECL_OVERRIDE { auto collections = CollectionFetchJob::collections(); // Memorize them to reconstruct the ancestor chain later QMap collectionsMap; collectionsMap[m_collection.id()] = m_collection; foreach (const auto &collection, collections) { collectionsMap[collection.id()] = collection; } // Why the hell isn't fetchScope() const and returning a reference??? auto self = const_cast(this); const auto allowedMimeTypes = self->fetchScope().contentMimeTypes().toSet(); if (!allowedMimeTypes.isEmpty()) { collections.erase(std::remove_if(collections.begin(), collections.end(), [allowedMimeTypes] (const Collection &collection) { auto mimeTypes = collection.contentMimeTypes().toSet(); return mimeTypes.intersect(allowedMimeTypes).isEmpty(); }), collections.end()); } if (m_type != Base) { // Replace the dummy parents in the ancestor chain with proper ones // full of juicy data std::function reconstructAncestors = [collectionsMap, &reconstructAncestors, this] (const Collection &collection) -> Collection { Q_ASSERT(collection.isValid()); if (collection == m_collection) return collection; auto parent = collection.parentCollection(); auto reconstructedParent = reconstructAncestors(collectionsMap[parent.id()]); auto result = collection; result.setParentCollection(reconstructedParent); return result; }; std::transform(collections.begin(), collections.end(), collections.begin(), reconstructAncestors); } return collections; } void setResource(const QString &resource) Q_DECL_OVERRIDE { fetchScope().setResource(resource); } - void setFiltered(bool filter) Q_DECL_OVERRIDE - { - fetchScope().setListFilter(filter ? Akonadi::CollectionFetchScope::Display - : Akonadi::CollectionFetchScope::NoFilter); - } - private: const Collection m_collection; const Type m_type; }; -class CollectionSearchJob : public CollectionFetchJob, public CollectionSearchJobInterface -{ - Q_OBJECT -public: - CollectionSearchJob(const QString &collectionName, QObject *parent = Q_NULLPTR) - : CollectionFetchJob(Akonadi::Collection::root(), - CollectionJob::Recursive, - parent), - m_collectionName(collectionName) - { - } - - Collection::List collections() const Q_DECL_OVERRIDE - { - auto collections = CollectionFetchJob::collections(); - - // Memorize them to reconstruct the ancestor chain later - QMap collectionsMap; - collectionsMap[Akonadi::Collection::root().id()] = Akonadi::Collection::root(); - foreach (const auto &collection, collections) { - collectionsMap[collection.id()] = collection; - } - - // Why the hell isn't fetchScope() const and returning a reference??? - auto self = const_cast(this); - const auto allowedMimeTypes = self->fetchScope().contentMimeTypes().toSet(); - - collections.erase(std::remove_if(collections.begin(), collections.end(), - [allowedMimeTypes, this] (const Collection &collection) { - auto mimeTypes = collection.contentMimeTypes().toSet(); - return mimeTypes.intersect(allowedMimeTypes).isEmpty() - || !collection.displayName().contains(m_collectionName, Qt::CaseInsensitive); - }), - collections.end()); - - // Replace the dummy parents in the ancestor chain with proper ones - // full of juicy data - std::function reconstructAncestors = - [collectionsMap, &reconstructAncestors, this] (const Collection &collection) -> Collection { - Q_ASSERT(collection.isValid()); - - if (collection == Akonadi::Collection::root()) - return collection; - - auto parent = collection.parentCollection(); - auto reconstructedParent = reconstructAncestors(collectionsMap[parent.id()]); - - auto result = collection; - result.setParentCollection(reconstructedParent); - return result; - }; - - std::transform(collections.begin(), collections.end(), - collections.begin(), reconstructAncestors); - - return collections; - } - -private: - QString m_collectionName; -}; - class ItemJob : public ItemFetchJob, public ItemFetchJobInterface { Q_OBJECT public: using ItemFetchJob::ItemFetchJob; Item::List items() const Q_DECL_OVERRIDE { return ItemFetchJob::items(); } void setCollection(const Collection &collection) Q_DECL_OVERRIDE { ItemFetchJob::setCollection(collection); } }; class TagJob : public TagFetchJob, public TagFetchJobInterface { Q_OBJECT public: using TagFetchJob::TagFetchJob; Tag::List tags() const Q_DECL_OVERRIDE { return TagFetchJob::tags(); } }; Storage::Storage() { } Storage::~Storage() { } Collection Storage::defaultTaskCollection() { return StorageSettings::instance().defaultTaskCollection(); } Collection Storage::defaultNoteCollection() { return StorageSettings::instance().defaultNoteCollection(); } KJob *Storage::createItem(Item item, Collection collection) { return new ItemCreateJob(item, collection); } KJob *Storage::updateItem(Item item, QObject *parent) { return new ItemModifyJob(item, parent); } KJob *Storage::removeItem(Item item) { return new ItemDeleteJob(item); } KJob *Storage::removeItems(Item::List items, QObject *parent) { return new ItemDeleteJob(items, parent); } KJob *Storage::moveItem(Item item, Collection collection, QObject *parent) { return new ItemMoveJob(item, collection, parent); } KJob *Storage::moveItems(Item::List items, Collection collection, QObject *parent) { return new ItemMoveJob(items, collection, parent); } KJob *Storage::createCollection(Collection collection, QObject *parent) { return new CollectionCreateJob(collection, parent); } KJob *Storage::updateCollection(Collection collection, QObject *parent) { return new CollectionModifyJob(collection, parent); } KJob *Storage::removeCollection(Collection collection, QObject *parent) { return new CollectionDeleteJob(collection, parent); } KJob *Storage::createTransaction() { return new TransactionSequence(); } KJob *Storage::createTag(Tag tag) { return new TagCreateJob(tag); } KJob *Storage::updateTag(Tag tag) { return new TagModifyJob(tag); } KJob *Storage::removeTag(Tag tag) { return new Akonadi::TagDeleteJob(tag); } CollectionFetchJobInterface *Storage::fetchCollections(Collection collection, StorageInterface::FetchDepth depth, FetchContentTypes types) { QStringList contentMimeTypes; if (types & Notes) contentMimeTypes << NoteUtils::noteMimeType(); if (types & Tasks) contentMimeTypes << KCalCore::Todo::todoMimeType(); auto job = new CollectionJob(collection, jobTypeFromDepth(depth)); auto scope = job->fetchScope(); scope.setContentMimeTypes(contentMimeTypes); scope.setIncludeStatistics(true); scope.setAncestorRetrieval(CollectionFetchScope::All); scope.setListFilter(Akonadi::CollectionFetchScope::Display); job->setFetchScope(scope); return job; } -CollectionSearchJobInterface *Storage::searchCollections(QString collectionName, FetchContentTypes types) -{ - QStringList contentMimeTypes; - if (types & Notes) - contentMimeTypes << NoteUtils::noteMimeType(); - if (types & Tasks) - contentMimeTypes << KCalCore::Todo::todoMimeType(); - - auto job = new CollectionSearchJob(collectionName); - - auto scope = job->fetchScope(); - scope.setContentMimeTypes(contentMimeTypes); - scope.setIncludeStatistics(true); - scope.setAncestorRetrieval(CollectionFetchScope::All); - scope.setListFilter(Akonadi::CollectionFetchScope::NoFilter); - job->setFetchScope(scope); - - return job; -} - - ItemFetchJobInterface *Storage::fetchItems(Collection collection) { auto job = new ItemJob(collection); configureItemFetchJob(job); return job; } ItemFetchJobInterface *Storage::fetchItem(Akonadi::Item item) { auto job = new ItemJob(item); configureItemFetchJob(job); return job; } ItemFetchJobInterface *Storage::fetchTagItems(Tag tag) { auto job = new ItemJob(tag); configureItemFetchJob(job); return job; } TagFetchJobInterface *Storage::fetchTags() { return new TagJob; } CollectionFetchJob::Type Storage::jobTypeFromDepth(StorageInterface::FetchDepth depth) { auto jobType = CollectionJob::Base; switch (depth) { case Base: jobType = CollectionJob::Base; break; case FirstLevel: jobType = CollectionJob::FirstLevel; break; case Recursive: jobType = CollectionJob::Recursive; break; default: qFatal("Unexpected enum value"); break; } return jobType; } void Storage::configureItemFetchJob(ItemJob *job) { auto scope = job->fetchScope(); scope.fetchFullPayload(); scope.fetchAllAttributes(); scope.setFetchTags(true); scope.tagFetchScope().setFetchIdOnly(false); scope.setAncestorRetrieval(ItemFetchScope::All); job->setFetchScope(scope); } #include "akonadistorage.moc" diff --git a/src/akonadi/akonadistorage.h b/src/akonadi/akonadistorage.h index fb47b2db..af843649 100644 --- a/src/akonadi/akonadistorage.h +++ b/src/akonadi/akonadistorage.h @@ -1,74 +1,73 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #ifndef AKONADI_STORAGE_H #define AKONADI_STORAGE_H #include "akonadistorageinterface.h" #include class ItemJob; namespace Akonadi { class Storage : public StorageInterface { public: Storage(); virtual ~Storage(); Akonadi::Collection defaultTaskCollection() Q_DECL_OVERRIDE; Akonadi::Collection defaultNoteCollection() Q_DECL_OVERRIDE; KJob *createItem(Item item, Collection collection) Q_DECL_OVERRIDE; KJob *updateItem(Item item, QObject *parent = Q_NULLPTR) Q_DECL_OVERRIDE; KJob *removeItem(Akonadi::Item item) Q_DECL_OVERRIDE; KJob *removeItems(Item::List items, QObject *parent = Q_NULLPTR) Q_DECL_OVERRIDE; KJob *moveItem(Item item, Collection collection, QObject *parent = Q_NULLPTR) Q_DECL_OVERRIDE; KJob *moveItems(Item::List item, Collection collection, QObject *parent = Q_NULLPTR) Q_DECL_OVERRIDE; KJob *createCollection(Collection collection, QObject *parent = Q_NULLPTR) Q_DECL_OVERRIDE; KJob *updateCollection(Collection collection, QObject *parent = Q_NULLPTR) Q_DECL_OVERRIDE; KJob *removeCollection(Collection collection, QObject *parent = Q_NULLPTR) Q_DECL_OVERRIDE; KJob *createTransaction() Q_DECL_OVERRIDE; KJob *createTag(Akonadi::Tag tag) Q_DECL_OVERRIDE; KJob *updateTag(Akonadi::Tag tag) Q_DECL_OVERRIDE; KJob *removeTag(Akonadi::Tag tag) Q_DECL_OVERRIDE; CollectionFetchJobInterface *fetchCollections(Akonadi::Collection collection, FetchDepth depth, FetchContentTypes types) Q_DECL_OVERRIDE; - CollectionSearchJobInterface *searchCollections(QString collectionName, FetchContentTypes types) Q_DECL_OVERRIDE; ItemFetchJobInterface *fetchItems(Akonadi::Collection collection) Q_DECL_OVERRIDE; ItemFetchJobInterface *fetchItem(Akonadi::Item item) Q_DECL_OVERRIDE; ItemFetchJobInterface *fetchTagItems(Akonadi::Tag tag) Q_DECL_OVERRIDE; TagFetchJobInterface *fetchTags() Q_DECL_OVERRIDE; private: CollectionFetchJob::Type jobTypeFromDepth(StorageInterface::FetchDepth depth); void configureItemFetchJob(ItemJob *job); }; } #endif // AKONADI_STORAGE_H diff --git a/src/akonadi/akonadistorageinterface.h b/src/akonadi/akonadistorageinterface.h index 4f044334..7c52ae1c 100644 --- a/src/akonadi/akonadistorageinterface.h +++ b/src/akonadi/akonadistorageinterface.h @@ -1,97 +1,95 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #ifndef AKONADI_STORAGEINTERFACE_H #define AKONADI_STORAGEINTERFACE_H #include #include #include class KJob; class QObject; class QByteArray; namespace Akonadi { class Collection; class CollectionFetchJobInterface; -class CollectionSearchJobInterface; class ItemFetchJobInterface; class TagFetchJobInterface; class StorageInterface { public: typedef QSharedPointer Ptr; enum FetchDepth { Base, FirstLevel, Recursive }; enum FetchContentType { AllContent = 0x0, Tasks = 0x1, Notes = 0x2 }; Q_DECLARE_FLAGS(FetchContentTypes, FetchContentType) StorageInterface(); virtual ~StorageInterface(); virtual Akonadi::Collection defaultTaskCollection() = 0; virtual Akonadi::Collection defaultNoteCollection() = 0; virtual KJob *createItem(Akonadi::Item item, Akonadi::Collection collection) = 0; virtual KJob *updateItem(Akonadi::Item item, QObject *parent = Q_NULLPTR) = 0; virtual KJob *removeItem(Akonadi::Item item) = 0; virtual KJob *removeItems(Item::List items, QObject *parent = Q_NULLPTR) = 0; virtual KJob *moveItem(Item item, Collection collection, QObject *parent = Q_NULLPTR) = 0; virtual KJob *moveItems(Item::List item, Collection collection, QObject *parent = Q_NULLPTR) = 0; virtual KJob *createCollection(Collection collection, QObject *parent = Q_NULLPTR) = 0; virtual KJob *updateCollection(Collection collection, QObject *parent = Q_NULLPTR) = 0; virtual KJob *removeCollection(Collection collection, QObject *parent = Q_NULLPTR) = 0; virtual KJob *createTransaction() = 0; virtual KJob *createTag(Akonadi::Tag tag) = 0; virtual KJob *updateTag(Akonadi::Tag tag) = 0; virtual KJob *removeTag(Akonadi::Tag tag) = 0; virtual CollectionFetchJobInterface *fetchCollections(Akonadi::Collection collection, FetchDepth depth, FetchContentTypes types) = 0; - virtual CollectionSearchJobInterface *searchCollections(QString collectionName, FetchContentTypes types) = 0; virtual ItemFetchJobInterface *fetchItems(Akonadi::Collection collection) = 0; virtual ItemFetchJobInterface *fetchItem(Akonadi::Item item) = 0; virtual ItemFetchJobInterface *fetchTagItems(Akonadi::Tag tag) = 0; virtual TagFetchJobInterface *fetchTags() = 0; }; } Q_DECLARE_OPERATORS_FOR_FLAGS(Akonadi::StorageInterface::FetchContentTypes) #endif // AKONADI_STORAGEINTERFACE_H diff --git a/src/domain/datasource.cpp b/src/domain/datasource.cpp index 3ade2cf8..9d91558d 100644 --- a/src/domain/datasource.cpp +++ b/src/domain/datasource.cpp @@ -1,109 +1,94 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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 "datasource.h" using namespace Domain; DataSource::DataSource(QObject *parent) : QObject(parent), m_contentTypes(NoContent), - m_listStatus(Unlisted), m_selected(false) { } DataSource::~DataSource() { } QString DataSource::name() const { return m_name; } QString DataSource::iconName() const { return m_iconName; } DataSource::ContentTypes DataSource::contentTypes() const { return m_contentTypes; } -DataSource::ListStatus DataSource::listStatus() const -{ - return m_listStatus; -} - bool DataSource::isSelected() const { return m_selected; } void DataSource::setName(const QString &name) { if (m_name == name) return; m_name = name; emit nameChanged(name); } void DataSource::setIconName(const QString &iconName) { if (m_iconName == iconName) return; m_iconName = iconName; emit iconNameChanged(iconName); } void DataSource::setContentTypes(ContentTypes types) { if (m_contentTypes == types) return; m_contentTypes = types; emit contentTypesChanged(types); } -void DataSource::setListStatus(ListStatus status) -{ - if (m_listStatus == status) - return; - - m_listStatus = status; - emit listStatusChanged(status); -} - void DataSource::setSelected(bool selected) { if (m_selected == selected) return; m_selected = selected; emit selectedChanged(selected); } diff --git a/src/domain/datasource.h b/src/domain/datasource.h index 30cdb8af..ac2b7d8f 100644 --- a/src/domain/datasource.h +++ b/src/domain/datasource.h @@ -1,100 +1,88 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #ifndef DOMAIN_DATASOURCE_H #define DOMAIN_DATASOURCE_H #include #include #include namespace Domain { // cppcheck somehow doesn't see the ctor in here // cppcheck-suppress noConstructor class DataSource : public QObject { Q_OBJECT - Q_ENUMS(ContentType ListStatus) + Q_ENUMS(ContentType) Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) Q_PROPERTY(QString iconName READ iconName WRITE setIconName NOTIFY iconNameChanged) Q_PROPERTY(Domain::DataSource::ContentTypes contentTypes READ contentTypes WRITE setContentTypes NOTIFY contentTypesChanged) - Q_PROPERTY(Domain::DataSource::ListStatus listStatus READ listStatus WRITE setListStatus NOTIFY listStatusChanged) Q_PROPERTY(bool selected READ isSelected WRITE setSelected NOTIFY selectedChanged) public: typedef QSharedPointer Ptr; typedef QList List; enum ContentType { NoContent = 0, Tasks, Notes }; Q_DECLARE_FLAGS(ContentTypes, ContentType) - enum ListStatus { - Unlisted = 0, - Listed = 1, - Bookmarked = 3 - }; - explicit DataSource(QObject *parent = Q_NULLPTR); virtual ~DataSource(); QString name() const; QString iconName() const; ContentTypes contentTypes() const; - ListStatus listStatus() const; bool isSelected() const; public slots: void setName(const QString &name); void setIconName(const QString &iconName); void setContentTypes(Domain::DataSource::ContentTypes types); - void setListStatus(Domain::DataSource::ListStatus status); void setSelected(bool selected); signals: void nameChanged(const QString &name); void iconNameChanged(const QString &iconName); void contentTypesChanged(Domain::DataSource::ContentTypes types); - void listStatusChanged(Domain::DataSource::ListStatus status); void selectedChanged(bool selected); private: QString m_name; QString m_iconName; ContentTypes m_contentTypes; - ListStatus m_listStatus; bool m_selected; }; } Q_DECLARE_METATYPE(Domain::DataSource::Ptr) Q_DECLARE_METATYPE(Domain::DataSource::ContentTypes) Q_DECLARE_OPERATORS_FOR_FLAGS(Domain::DataSource::ContentTypes) -Q_DECLARE_METATYPE(Domain::DataSource::ListStatus) #endif // DOMAIN_DATASOURCE_H diff --git a/src/domain/datasourcequeries.h b/src/domain/datasourcequeries.h index d416391f..64ac9490 100644 --- a/src/domain/datasourcequeries.h +++ b/src/domain/datasourcequeries.h @@ -1,79 +1,74 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #ifndef DOMAIN_DATASOURCEQUERIES_H #define DOMAIN_DATASOURCEQUERIES_H #include #include "datasource.h" #include "queryresult.h" namespace Domain { class DataSourceQueries; class DataSourceQueriesNotifier : public QObject { Q_OBJECT signals: void defaultSourceChanged(); private: friend class DataSourceQueries; }; class DataSourceQueries { public: typedef QSharedPointer Ptr; DataSourceQueries(); virtual ~DataSourceQueries(); DataSourceQueriesNotifier *notifier() const; virtual bool isDefaultSource(DataSource::Ptr source) const = 0; void setDefaultSource(DataSource::Ptr source); // HACK: Ugly right? Find me another way to mock changeDefaultSource then... #ifdef ZANSHIN_I_SWEAR_I_AM_IN_A_PRESENTATION_TEST public: #else private: #endif virtual void changeDefaultSource(DataSource::Ptr source) = 0; public: virtual QueryResult::Ptr findTopLevel() const = 0; virtual QueryResult::Ptr findChildren(DataSource::Ptr source) const = 0; - - virtual QString searchTerm() const = 0; - virtual void setSearchTerm(const QString &term) = 0; - virtual QueryResult::Ptr findSearchTopLevel() const = 0; - virtual QueryResult::Ptr findSearchChildren(DataSource::Ptr source) const = 0; }; } #endif // DOMAIN_DATASOURCEQUERIES_H diff --git a/src/presentation/availablesourcesmodel.cpp b/src/presentation/availablesourcesmodel.cpp index 1574b5ff..6a144460 100644 --- a/src/presentation/availablesourcesmodel.cpp +++ b/src/presentation/availablesourcesmodel.cpp @@ -1,262 +1,155 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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 "availablesourcesmodel.h" #include #include "domain/datasourcequeries.h" #include "domain/datasourcerepository.h" #include "presentation/querytreemodel.h" using namespace Presentation; AvailableSourcesModel::AvailableSourcesModel(const Domain::DataSourceQueries::Ptr &dataSourceQueries, const Domain::DataSourceRepository::Ptr &dataSourceRepository, QObject *parent) : QObject(parent), m_sourceListModel(Q_NULLPTR), - m_searchListModel(Q_NULLPTR), m_dataSourceQueries(dataSourceQueries), m_dataSourceRepository(dataSourceRepository) { } QAbstractItemModel *AvailableSourcesModel::sourceListModel() { if (!m_sourceListModel) m_sourceListModel = createSourceListModel(); return m_sourceListModel; } -QAbstractItemModel *AvailableSourcesModel::searchListModel() -{ - if (!m_searchListModel) - m_searchListModel = createSearchListModel(); - return m_searchListModel; -} - -void AvailableSourcesModel::listSource(const Domain::DataSource::Ptr &source) -{ - Q_ASSERT(source); - source->setSelected(true); - source->setListStatus(Domain::DataSource::Listed); - const auto job = m_dataSourceRepository->update(source); - installHandler(job, tr("Cannot modify source %1").arg(source->name())); -} - -void AvailableSourcesModel::unlistSource(const Domain::DataSource::Ptr &source) -{ - Q_ASSERT(source); - source->setSelected(false); - source->setListStatus(Domain::DataSource::Unlisted); - const auto job = m_dataSourceRepository->update(source); - installHandler(job, tr("Cannot modify source %1").arg(source->name())); -} - -void AvailableSourcesModel::bookmarkSource(const Domain::DataSource::Ptr &source) -{ - Q_ASSERT(source); - if (source->listStatus() == Domain::DataSource::Bookmarked) - source->setListStatus(Domain::DataSource::Listed); - else - source->setListStatus(Domain::DataSource::Bookmarked); - const auto job = m_dataSourceRepository->update(source); - installHandler(job, tr("Cannot modify source %1").arg(source->name())); -} - void AvailableSourcesModel::showConfigDialog() { m_dataSourceRepository->showConfigDialog(); } QAbstractItemModel *AvailableSourcesModel::createSourceListModel() { auto query = [this] (const Domain::DataSource::Ptr &source) { if (!source) return m_dataSourceQueries->findTopLevel(); else return m_dataSourceQueries->findChildren(source); }; auto flags = [] (const Domain::DataSource::Ptr &source) -> Qt::ItemFlags { const Qt::ItemFlags defaultFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; if (source->contentTypes() != Domain::DataSource::NoContent) return defaultFlags | Qt::ItemIsUserCheckable; else return defaultFlags; }; auto data = [this] (const Domain::DataSource::Ptr &source, int role) -> QVariant { if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::DecorationRole && role != Qt::CheckStateRole && role != QueryTreeModelBase::IconNameRole && role != QueryTreeModelBase::IsDefaultRole) { return QVariant(); } if (role == Qt::DisplayRole || role == Qt::EditRole) { return source->name(); } else if (role == Qt::DecorationRole || role == QueryTreeModelBase::IconNameRole) { const QString iconName = source->iconName().isEmpty() ? QStringLiteral("folder") : source->iconName(); if (role == Qt::DecorationRole) return QVariant::fromValue(QIcon::fromTheme(iconName)); else return iconName; } else if (role == Qt::CheckStateRole) { if (source->contentTypes() != Domain::DataSource::NoContent) return source->isSelected() ? Qt::Checked : Qt::Unchecked; else return QVariant(); } else if (role == QueryTreeModelBase::IsDefaultRole) { return m_dataSourceQueries->isDefaultSource(source); } else { return QVariant(); } }; auto setData = [this] (const Domain::DataSource::Ptr &source, const QVariant &value, int role) { if (role != Qt::CheckStateRole) return false; if (source->contentTypes() == Domain::DataSource::NoContent) return false; source->setSelected(value.toInt() == Qt::Checked); const auto job = m_dataSourceRepository->update(source); installHandler(job, tr("Cannot modify source %1").arg(source->name())); return true; }; auto drop = [] (const QMimeData *mimeData, Qt::DropAction, const Domain::DataSource::Ptr &source) { Q_UNUSED(mimeData) Q_UNUSED(source) return false; }; auto drag = [](const Domain::DataSource::List &) -> QMimeData* { return Q_NULLPTR; }; connect(m_dataSourceQueries->notifier(), &Domain::DataSourceQueriesNotifier::defaultSourceChanged, this, &AvailableSourcesModel::onDefaultSourceChanged); return new QueryTreeModel(query, flags, data, setData, drop, drag, this); } -QAbstractItemModel *AvailableSourcesModel::createSearchListModel() -{ - auto query = [this] (const Domain::DataSource::Ptr &source) { - if (!source) - return m_dataSourceQueries->findSearchTopLevel(); - else - return m_dataSourceQueries->findSearchChildren(source); - }; - - auto flags = [] (const Domain::DataSource::Ptr &source) { - Q_UNUSED(source) - return Qt::ItemIsSelectable | Qt::ItemIsEnabled; - }; - - auto data = [] (const Domain::DataSource::Ptr &source, int role) -> QVariant { - if (role != Qt::DisplayRole - && role != Qt::EditRole - && role != Qt::DecorationRole - && role != QueryTreeModelBase::IconNameRole) { - return QVariant(); - } - - if (role == Qt::DisplayRole || role == Qt::EditRole) { - return source->name(); - } else if (role == Qt::DecorationRole || role == QueryTreeModelBase::IconNameRole) { - const QString iconName = source->iconName().isEmpty() ? QStringLiteral("folder") : source->iconName(); - - if (role == Qt::DecorationRole) - return QVariant::fromValue(QIcon::fromTheme(iconName)); - else - return iconName; - } else { - return QVariant(); - } - }; - - auto setData = [this] (const Domain::DataSource::Ptr &source, const QVariant &value, int role) { - Q_UNUSED(source) - Q_UNUSED(value) - Q_UNUSED(role) - return false; - }; - - auto drop = [] (const QMimeData *mimeData, Qt::DropAction, const Domain::DataSource::Ptr &source) { - Q_UNUSED(mimeData) - Q_UNUSED(source) - return false; - }; - - auto drag = [](const Domain::DataSource::List &) -> QMimeData* { - return Q_NULLPTR; - }; - - return new QueryTreeModel(query, flags, data, setData, drop, drag, this); -} - -QString AvailableSourcesModel::searchTerm() const -{ - return m_dataSourceQueries->searchTerm(); -} - -void AvailableSourcesModel::setSearchTerm(const QString &term) -{ - if (term == searchTerm()) - return; - - m_dataSourceQueries->setSearchTerm(term); - emit searchTermChanged(term); -} - void AvailableSourcesModel::setDefaultItem(const QModelIndex &index) { auto source = index.data(QueryTreeModelBase::ObjectRole).value(); Q_ASSERT(source); m_dataSourceQueries->setDefaultSource(source); } void AvailableSourcesModel::onDefaultSourceChanged() { emitDefaultSourceChanged(QModelIndex()); } void AvailableSourcesModel::emitDefaultSourceChanged(const QModelIndex &root) { const auto rowCount = m_sourceListModel->rowCount(root); for (int row = 0; row < rowCount; row++) { const auto index = m_sourceListModel->index(row, 0, root); // TODO Qt5: Remove static_cast emit static_cast(m_sourceListModel)->dataChanged(index, index); emitDefaultSourceChanged(index); } } diff --git a/src/presentation/availablesourcesmodel.h b/src/presentation/availablesourcesmodel.h index 7d30445c..025aa847 100644 --- a/src/presentation/availablesourcesmodel.h +++ b/src/presentation/availablesourcesmodel.h @@ -1,87 +1,72 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #ifndef PRESENTATION_AVAILABLESOURCESMODEL_H #define PRESENTATION_AVAILABLESOURCESMODEL_H #include #include "domain/datasourcequeries.h" #include "domain/datasourcerepository.h" #include "presentation/metatypes.h" #include "presentation/errorhandlingmodelbase.h" class QModelIndex; namespace Presentation { class AvailableSourcesModel : public QObject, public ErrorHandlingModelBase { Q_OBJECT Q_PROPERTY(QAbstractItemModel* sourceListModel READ sourceListModel) - Q_PROPERTY(QAbstractItemModel* searchListModel READ searchListModel) - Q_PROPERTY(QString searchTerm READ searchTerm WRITE setSearchTerm NOTIFY searchTermChanged) public: explicit AvailableSourcesModel(const Domain::DataSourceQueries::Ptr &dataSourceQueries, const Domain::DataSourceRepository::Ptr &dataSourceRepository, QObject *parent = Q_NULLPTR); QAbstractItemModel *sourceListModel(); - QAbstractItemModel *searchListModel(); - - QString searchTerm() const; - void setSearchTerm(const QString &term); - -signals: - void searchTermChanged(const QString &term); public slots: void setDefaultItem(const QModelIndex &index); - void listSource(const Domain::DataSource::Ptr &source); - void unlistSource(const Domain::DataSource::Ptr &source); - void bookmarkSource(const Domain::DataSource::Ptr &source); - void showConfigDialog(); private slots: void onDefaultSourceChanged(); private: void emitDefaultSourceChanged(const QModelIndex &root); QAbstractItemModel *createSourceListModel(); - QAbstractItemModel *createSearchListModel(); QAbstractItemModel *m_sourceListModel; - QAbstractItemModel *m_searchListModel; Domain::DataSourceQueries::Ptr m_dataSourceQueries; Domain::DataSourceRepository::Ptr m_dataSourceRepository; }; } #endif // PRESENTATION_AVAILABLESOURCESMODEL_H diff --git a/src/widgets/availablesourcesview.cpp b/src/widgets/availablesourcesview.cpp index 249293ff..03b84f8a 100644 --- a/src/widgets/availablesourcesview.cpp +++ b/src/widgets/availablesourcesview.cpp @@ -1,209 +1,163 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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 "availablesourcesview.h" #include #include #include #include #include #include #include #include #include #include "presentation/metatypes.h" #include "presentation/querytreemodelbase.h" #include "widgets/datasourcedelegate.h" using namespace Widgets; AvailableSourcesView::AvailableSourcesView(QWidget *parent) : QWidget(parent), m_defaultAction(new QAction(this)), m_model(Q_NULLPTR), m_sortProxy(new QSortFilterProxyModel(this)), m_sourcesView(new QTreeView(this)) { m_sortProxy->setDynamicSortFilter(true); m_sortProxy->sort(0); - auto searchEdit = new KLineEdit(this); - searchEdit->setObjectName(QStringLiteral("searchEdit")); - searchEdit->setClearButtonShown(true); - searchEdit->setPlaceholderText(tr("Search...")); - connect(searchEdit, &QLineEdit::textChanged, this, &AvailableSourcesView::onSearchTextChanged); -#ifndef ZANSHIN_HIDING_SOURCES_ENABLED - searchEdit->hide(); -#endif - m_sourcesView->setObjectName(QStringLiteral("sourcesView")); m_sourcesView->header()->hide(); m_sourcesView->setModel(m_sortProxy); connect(m_sourcesView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &AvailableSourcesView::onSelectionChanged); connect(m_sourcesView->model(), &QAbstractItemModel::rowsInserted, m_sourcesView, &QTreeView::expand); connect(m_sourcesView->model(), &QAbstractItemModel::layoutChanged, m_sourcesView, &QTreeView::expandAll); connect(m_sourcesView->model(), &QAbstractItemModel::modelReset, m_sourcesView, &QTreeView::expandAll); auto delegate = new DataSourceDelegate(m_sourcesView); -#ifndef ZANSHIN_HIDING_SOURCES_ENABLED - delegate->setActionsEnabled(false); -#endif - connect(delegate, &DataSourceDelegate::actionTriggered, this, &AvailableSourcesView::onActionTriggered); m_sourcesView->setItemDelegate(delegate); auto actionBar = new QToolBar(this); actionBar->setObjectName(QStringLiteral("actionBar")); actionBar->setIconSize(QSize(16, 16)); m_defaultAction->setObjectName(QStringLiteral("defaultAction")); m_defaultAction->setText(tr("Use as default source")); m_defaultAction->setIcon(QIcon::fromTheme(QStringLiteral("folder-favorites"))); connect(m_defaultAction, &QAction::triggered, this, &AvailableSourcesView::onDefaultTriggered); actionBar->addAction(m_defaultAction); auto layout = new QVBoxLayout; - layout->addWidget(searchEdit); layout->addWidget(m_sourcesView); auto actionBarLayout = new QHBoxLayout; actionBarLayout->setContentsMargins(0, 0, 0, 0); actionBarLayout->setAlignment(Qt::AlignRight); actionBarLayout->addWidget(actionBar); layout->addLayout(actionBarLayout); setLayout(layout); auto margins = layout->contentsMargins(); margins.setBottom(0); layout->setContentsMargins(margins); auto settingsAction = new QAction(this); settingsAction->setObjectName(QStringLiteral("settingsAction")); settingsAction->setText(tr("Configure %1...").arg(QApplication::applicationName())); settingsAction->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); connect(settingsAction, &QAction::triggered, this, &AvailableSourcesView::onSettingsTriggered); m_actions.insert(QStringLiteral("options_configure"), settingsAction); onSelectionChanged(); } QHash AvailableSourcesView::globalActions() const { return m_actions; } QObject *AvailableSourcesView::model() const { return m_model; } void AvailableSourcesView::setModel(QObject *model) { if (model == m_model) return; m_sortProxy->setSourceModel(Q_NULLPTR); m_model = model; setEnabled(m_model); if (!m_model) return; setSourceModel("sourceListModel"); } void AvailableSourcesView::onSelectionChanged() { const auto selectedIndexes = m_sourcesView->selectionModel()->selectedIndexes(); auto selectedSources = Domain::DataSource::List(); std::transform(selectedIndexes.constBegin(), selectedIndexes.constEnd(), std::back_inserter(selectedSources), [] (const QModelIndex &index) { return index.data(Presentation::QueryTreeModelBase::ObjectRole) .value(); }); m_defaultAction->setEnabled(selectedSources.size() == 1 && selectedSources.first()->contentTypes() != Domain::DataSource::NoContent); } void AvailableSourcesView::onSettingsTriggered() { QMetaObject::invokeMethod(m_model, "showConfigDialog"); } void AvailableSourcesView::onDefaultTriggered() { const auto currentIndex = m_sourcesView->currentIndex(); const auto index = m_sortProxy->mapToSource(currentIndex); if (index.isValid()) QMetaObject::invokeMethod(m_model, "setDefaultItem", Q_ARG(QModelIndex, index)); } -void AvailableSourcesView::onActionTriggered(const Domain::DataSource::Ptr &source, int action) -{ - switch (action) { - case DataSourceDelegate::AddToList: - QMetaObject::invokeMethod(m_model, "listSource", - Q_ARG(Domain::DataSource::Ptr, source)); - break; - case DataSourceDelegate::RemoveFromList: - QMetaObject::invokeMethod(m_model, "unlistSource", - Q_ARG(Domain::DataSource::Ptr, source)); - break; - case DataSourceDelegate::Bookmark: - QMetaObject::invokeMethod(m_model, "bookmarkSource", - Q_ARG(Domain::DataSource::Ptr, source)); - break; - default: - qFatal("Shouldn't happen"); - break; - } -} - void AvailableSourcesView::setSourceModel(const QByteArray &propertyName) { QVariant modelProperty = m_model->property(propertyName); if (modelProperty.canConvert()) m_sortProxy->setSourceModel(modelProperty.value()); } - -void AvailableSourcesView::onSearchTextChanged(const QString &text) -{ - if (text.size() <= 2) { - m_model->setProperty("searchTerm", QString()); - setSourceModel("sourceListModel"); - } else { - m_model->setProperty("searchTerm", text); - setSourceModel("searchListModel"); - } -} diff --git a/src/widgets/availablesourcesview.h b/src/widgets/availablesourcesview.h index 11aa0292..c2461de8 100644 --- a/src/widgets/availablesourcesview.h +++ b/src/widgets/availablesourcesview.h @@ -1,70 +1,68 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #ifndef WIDGETS_AVAILABLESOURCESVIEW_H #define WIDGETS_AVAILABLESOURCESVIEW_H #include #include #include "domain/datasource.h" class QSortFilterProxyModel; class QTreeView; namespace Widgets { class AvailableSourcesView : public QWidget { Q_OBJECT public: explicit AvailableSourcesView(QWidget *parent = Q_NULLPTR); QHash globalActions() const; QObject *model() const; void setSourceModel(const QByteArray &propertyName); public slots: void setModel(QObject *model); private slots: void onSelectionChanged(); void onSettingsTriggered(); void onDefaultTriggered(); - void onActionTriggered(const Domain::DataSource::Ptr &source, int action); - void onSearchTextChanged(const QString &text); private: QHash m_actions; QAction *m_defaultAction; QObject *m_model; QSortFilterProxyModel *m_sortProxy; QTreeView *m_sourcesView; }; } #endif // WIDGETS_AVAILABLESOURCESVIEW_H diff --git a/src/widgets/datasourcedelegate.cpp b/src/widgets/datasourcedelegate.cpp index a9b6358f..eb20ccff 100644 --- a/src/widgets/datasourcedelegate.cpp +++ b/src/widgets/datasourcedelegate.cpp @@ -1,197 +1,61 @@ /* This file is part of Zanshin Copyright 2014 Christian Mollekopf Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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 "datasourcedelegate.h" #include #include #include "presentation/querytreemodel.h" using namespace Widgets; const int DELEGATE_HEIGHT = 16; DataSourceDelegate::DataSourceDelegate(QObject *parent) - : QStyledItemDelegate(parent), - m_actionsEnabled(true) + : QStyledItemDelegate(parent) { - m_pixmaps[AddToList] = QIcon::fromTheme(QStringLiteral("list-add")).pixmap(DELEGATE_HEIGHT); - m_pixmaps[RemoveFromList] = QIcon::fromTheme(QStringLiteral("list-remove")).pixmap(DELEGATE_HEIGHT); - m_pixmaps[Bookmark] = QIcon::fromTheme(QStringLiteral("bookmarks")).pixmap(DELEGATE_HEIGHT); -} - - -bool DataSourceDelegate::isActionsEnabled() const -{ - return m_actionsEnabled; -} - -void DataSourceDelegate::setActionsEnabled(bool actionsEnabled) -{ - m_actionsEnabled = actionsEnabled; -} - -static QRect createButtonRect(const QRect &itemRect, int position) -{ - static const int border = 2; - const int side = itemRect.height() - (2 * border); - const int offset = side * (position + 1) + border * (position + 2); - return itemRect.adjusted(itemRect.width() - (offset + side), border, -offset, -border); -} - -static QStyle *currentStyle(const QStyleOptionViewItem &option) -{ - QWidget const *widget = option.widget; - QStyle *style = widget ? widget->style() : QApplication::style(); - return style; -} - -static QStyleOptionButton createButtonOption(const QStyleOptionViewItem &itemOption, const QPixmap &pixmap, int position) -{ - const QRect itemRect = itemOption.rect; - const QRect buttonRect = createButtonRect(itemRect, position); - - QStyleOptionButton buttonOption; - buttonOption.state = QStyle::State_Active | QStyle::State_Enabled; - buttonOption.icon = pixmap; - buttonOption.rect = buttonRect; - buttonOption.iconSize = pixmap.size(); - return buttonOption; -} - -static QList actionsForSource(const Domain::DataSource::Ptr &source, bool isHovered, bool enabled) -{ - auto actions = QList(); - - if (!enabled) - return actions; - - if (source->contentTypes() == Domain::DataSource::NoContent) - return actions; - - if (source->listStatus() == Domain::DataSource::Unlisted) { - actions << DataSourceDelegate::AddToList; - } else { - actions << DataSourceDelegate::Bookmark; - if (isHovered) - actions << DataSourceDelegate::RemoveFromList; - } - - return actions; -} - -static Domain::DataSource::Ptr sourceForIndex(const QModelIndex &index) -{ - const auto data = index.data(Presentation::QueryTreeModel::ObjectRole); - const auto source = data.value(); - Q_ASSERT(source); - return source; } void DataSourceDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index) const { Q_ASSERT(index.isValid()); - const auto source = sourceForIndex(index); - const auto isHovered = bool(opt.state & QStyle::State_MouseOver); const auto isDefault = index.data(Presentation::QueryTreeModel::IsDefaultRole).toBool(); QStyleOptionViewItem option = opt; initStyleOption(&option, index); option.font.setBold(isDefault); - QStyle *s = currentStyle(opt); - - int position = 0; - foreach (Action action, actionsForSource(source, isHovered, m_actionsEnabled)) { - QStyleOptionButton buttonOption = createButtonOption(option, m_pixmaps[action], position); - if (action == Bookmark - && source->listStatus() != Domain::DataSource::Bookmarked) { - buttonOption.state &= ~QStyle::State_Enabled; - } - s->drawControl(QStyle::CE_PushButton, &buttonOption, painter, Q_NULLPTR); - position++; - } - - if (position != 0) { - const auto firstButtonLeft = createButtonRect(option.rect, position - 1).left(); - option.rect.setRight(firstButtonLeft); - } QStyledItemDelegate::paint(painter, option, index); } -bool DataSourceDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, - const QStyleOptionViewItem &option, const QModelIndex &index) -{ - Q_ASSERT(event); - Q_ASSERT(model); - - int button = -1; - - switch (event->type()) { - case QEvent::MouseButtonRelease: - { - QMouseEvent *mouseEvent = static_cast(event); - - for (int position = 0; position < 4; position++) { - if (createButtonRect(option.rect, position).contains(mouseEvent->pos())) { - button = position; - break; - } - } - - if (mouseEvent->button() != Qt::LeftButton || button < 0) { - return QStyledItemDelegate::editorEvent(event, model, option, index); - } - } - break; - - default: - return QStyledItemDelegate::editorEvent(event, model, option, index); - } - - Q_ASSERT(button >= 0); - QStyleOptionViewItem opt = option; - opt.state |= QStyle::State_MouseOver; - - const auto source = sourceForIndex(index); - auto actions = actionsForSource(source, true, m_actionsEnabled); - if (actions.count() > button) { - const Action a = actions.at(button); - emit actionTriggered(source, a); - return true; - } - - return QStyledItemDelegate::editorEvent(event, model, option, index); -} - QSize DataSourceDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { QSize size = QStyledItemDelegate::sizeHint(option, index); - // Make sure we got a constant height suitable for the buttons + // Make sure we got a constant height size.setHeight(DELEGATE_HEIGHT + 4); return size; } diff --git a/src/widgets/datasourcedelegate.h b/src/widgets/datasourcedelegate.h index 0e383d53..faddb61b 100644 --- a/src/widgets/datasourcedelegate.h +++ b/src/widgets/datasourcedelegate.h @@ -1,70 +1,49 @@ /* This file is part of Zanshin Copyright 2014 Christian Mollekopf Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #ifndef DATASOURCEDELEGATE_H #define DATASOURCEDELEGATE_H #include #include "domain/datasource.h" namespace Widgets { class DataSourceDelegate : public QStyledItemDelegate { Q_OBJECT - Q_ENUMS(Action) public: - enum Action { - AddToList = 0, - RemoveFromList, - Bookmark - }; - explicit DataSourceDelegate(QObject *parent = Q_NULLPTR); - bool isActionsEnabled() const; - void setActionsEnabled(bool isActionsEnabled); - void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE; - -signals: - void actionTriggered(const Domain::DataSource::Ptr &source, int action); - -protected: - bool editorEvent(QEvent *event, QAbstractItemModel *model, - const QStyleOptionViewItem &option, const QModelIndex &index) Q_DECL_OVERRIDE; - -private: - bool m_actionsEnabled; - QHash m_pixmaps; }; } #endif diff --git a/tests/testlib/akonadifakejobs.cpp b/tests/testlib/akonadifakejobs.cpp index 1962dfe6..ce6c41a1 100644 --- a/tests/testlib/akonadifakejobs.cpp +++ b/tests/testlib/akonadifakejobs.cpp @@ -1,102 +1,82 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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 "akonadifakejobs.h" #include using namespace Testlib; void AkonadiFakeCollectionFetchJob::setCollections(const Akonadi::Collection::List &collections) { m_collections = collections; } Akonadi::Collection::List AkonadiFakeCollectionFetchJob::collections() const { return isDone() ? m_collections : Akonadi::Collection::List(); } QString AkonadiFakeCollectionFetchJob::resource() const { return m_resource; } void AkonadiFakeCollectionFetchJob::setResource(const QString &resource) { m_resource = resource; } -bool AkonadiFakeCollectionFetchJob::filtered() const -{ - return m_filter; -} - -void AkonadiFakeCollectionFetchJob::setFiltered(bool filter) -{ - m_filter = filter; -} - -void AkonadiFakeCollectionSearchJob::setCollections(const Akonadi::Collection::List &collections) -{ - m_collections = collections; -} - -Akonadi::Collection::List AkonadiFakeCollectionSearchJob::collections() const -{ - return isDone() ? m_collections : Akonadi::Collection::List(); -} - void AkonadiFakeItemFetchJob::setItems(const Akonadi::Item::List &items) { m_items = items; } Akonadi::Item::List AkonadiFakeItemFetchJob::items() const { if (expectedError() != KJob::NoError) return m_items; return isDone() ? m_items : Akonadi::Item::List(); } Akonadi::Collection AkonadiFakeItemFetchJob::collection() const { return m_collection; } void AkonadiFakeItemFetchJob::setCollection(const Akonadi::Collection &collection) { m_collection = collection; } void AkonadiFakeTagFetchJob::setTags(const Akonadi::Tag::List &tags) { m_tags = tags; } Akonadi::Tag::List AkonadiFakeTagFetchJob::tags() const { return isDone() ? m_tags : Akonadi::Tag::List(); } diff --git a/tests/testlib/akonadifakejobs.h b/tests/testlib/akonadifakejobs.h index f5d9707f..58206f78 100644 --- a/tests/testlib/akonadifakejobs.h +++ b/tests/testlib/akonadifakejobs.h @@ -1,103 +1,85 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #ifndef TESTLIB_AKONADIFAKEJOBS_H #define TESTLIB_AKONADIFAKEJOBS_H #include "fakejob.h" #include "akonadi/akonadicollectionfetchjobinterface.h" -#include "akonadi/akonadicollectionsearchjobinterface.h" #include "akonadi/akonadiitemfetchjobinterface.h" #include "akonadi/akonaditagfetchjobinterface.h" namespace Testlib { // cppcheck seems to get confused by the "using" in this class // cppcheck-suppress noConstructor class AkonadiFakeCollectionFetchJob : public FakeJob, public Akonadi::CollectionFetchJobInterface { Q_OBJECT public: using FakeJob::FakeJob; void setCollections(const Akonadi::Collection::List &collections); Akonadi::Collection::List collections() const Q_DECL_OVERRIDE; QString resource() const; void setResource(const QString &resource) Q_DECL_OVERRIDE; - bool filtered() const; - void setFiltered(bool filter) Q_DECL_OVERRIDE; - private: Akonadi::Collection::List m_collections; QString m_resource; - bool m_filter; -}; - -class AkonadiFakeCollectionSearchJob : public FakeJob, public Akonadi::CollectionSearchJobInterface -{ - Q_OBJECT -public: - using FakeJob::FakeJob; - - void setCollections(const Akonadi::Collection::List &collections); - Akonadi::Collection::List collections() const Q_DECL_OVERRIDE; - -private: - Akonadi::Collection::List m_collections; }; class AkonadiFakeItemFetchJob : public FakeJob, public Akonadi::ItemFetchJobInterface { Q_OBJECT public: using FakeJob::FakeJob; void setItems(const Akonadi::Item::List &items); Akonadi::Item::List items() const Q_DECL_OVERRIDE; Akonadi::Collection collection() const; void setCollection(const Akonadi::Collection &collection) Q_DECL_OVERRIDE; private: Akonadi::Item::List m_items; Akonadi::Collection m_collection; }; class AkonadiFakeTagFetchJob : public FakeJob, public Akonadi::TagFetchJobInterface { Q_OBJECT public: using FakeJob::FakeJob; void setTags(const Akonadi::Tag::List &tags); Akonadi::Tag::List tags() const Q_DECL_OVERRIDE; private: Akonadi::Tag::List m_tags; }; } #endif // TESTLIB_AKONADIFAKEJOBS_H diff --git a/tests/testlib/akonadifakestorage.cpp b/tests/testlib/akonadifakestorage.cpp index 8735e92c..165db029 100644 --- a/tests/testlib/akonadifakestorage.cpp +++ b/tests/testlib/akonadifakestorage.cpp @@ -1,552 +1,515 @@ /* This file is part of Zanshin Copyright 2015 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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 "akonadifakestorage.h" #include #include #include #include "akonadi/akonadistoragesettings.h" #include "akonadifakedata.h" #include "akonadifakejobs.h" #include "utils/jobhandler.h" using namespace Testlib; class AkonadiFakeTransaction : public FakeJob { Q_OBJECT public: explicit AkonadiFakeTransaction() : FakeJob(), m_nextIdx(0) { } private slots: void onTimeout() Q_DECL_OVERRIDE { auto jobs = childJobs(); if (m_nextIdx == 0) { const auto it = std::find_if(jobs.constBegin(), jobs.constEnd(), [] (FakeJob *job) { return job->expectedError() != 0; }); if (it != jobs.constEnd()) { setError((*it)->expectedError()); setErrorText((*it)->expectedErrorText()); emitResult(); return; } } if (m_nextIdx >= jobs.size()) { emitResult(); return; } auto job = jobs[m_nextIdx]; connect(job, &KJob::result, this, &AkonadiFakeTransaction::onTimeout); job->start(); m_nextIdx++; } private: QList childJobs() const { QList jobs = findChildren(); jobs.erase(std::remove_if(jobs.begin(), jobs.end(), [this] (FakeJob *job) { return job->parent() != this; }), jobs.end()); return jobs; } int m_nextIdx; }; Utils::JobHandler::StartMode startModeForParent(QObject *parent) { bool isTransaction = qobject_cast(parent); return isTransaction ? Utils::JobHandler::ManualStart : Utils::JobHandler::AutoStart; } void noop() {} AkonadiFakeStorage::AkonadiFakeStorage(AkonadiFakeData *data) : m_data(data) { } Akonadi::Collection AkonadiFakeStorage::defaultTaskCollection() { return Akonadi::StorageSettings::instance().defaultTaskCollection(); } Akonadi::Collection AkonadiFakeStorage::defaultNoteCollection() { return Akonadi::StorageSettings::instance().defaultNoteCollection(); } KJob *AkonadiFakeStorage::createItem(Akonadi::Item item, Akonadi::Collection collection) { Q_ASSERT(!item.isValid()); auto job = new FakeJob; if (!m_data->item(item.id()).isValid()) { Utils::JobHandler::install(job, [=] () mutable { item.setId(m_data->maxItemId() + 1); item.setParentCollection(collection); // Force payload detach item.setPayloadFromData(item.payloadData()); m_data->createItem(item); }); } else { job->setExpectedError(1, QStringLiteral("Item already exists")); Utils::JobHandler::install(job, noop); } return job; } KJob *AkonadiFakeStorage::updateItem(Akonadi::Item item, QObject *parent) { auto job = new FakeJob(parent); auto startMode = startModeForParent(parent); if (m_data->item(item.id()).isValid()) { Utils::JobHandler::install(job, [=] () mutable { // Force payload detach item.setPayloadFromData(item.payloadData()); m_data->modifyItem(item); }, startMode); } else { job->setExpectedError(1, QStringLiteral("Item doesn't exist")); Utils::JobHandler::install(job, noop, startMode); } return job; } KJob *AkonadiFakeStorage::removeItem(Akonadi::Item item) { auto job = new FakeJob; if (m_data->item(item.id()).isValid()) { Utils::JobHandler::install(job, [=] { m_data->removeItem(item); }); } else { job->setExpectedError(1, QStringLiteral("Item doesn't exist")); Utils::JobHandler::install(job, noop); } return job; } KJob *AkonadiFakeStorage::removeItems(Akonadi::Item::List items, QObject *parent) { auto job = new FakeJob; auto startMode = startModeForParent(parent); bool allItemsExist = std::all_of(items.constBegin(), items.constEnd(), [=] (const Akonadi::Item &item) { return m_data->item(item.id()).isValid(); }); if (allItemsExist) { Utils::JobHandler::install(job, [=] { foreach (const Akonadi::Item &item, items) { m_data->removeItem(item); } }, startMode); } else { job->setExpectedError(1, QStringLiteral("At least one item doesn't exist")); Utils::JobHandler::install(job, noop, startMode); } return job; } KJob *AkonadiFakeStorage::moveItem(Akonadi::Item item, Akonadi::Collection collection, QObject *parent) { auto job = new FakeJob(parent); auto startMode = startModeForParent(parent); if (m_data->item(item.id()).isValid() && m_data->collection(collection.id()).isValid()) { Utils::JobHandler::install(job, [=] () mutable { item.setParentCollection(collection); // Force payload detach item.setPayloadFromData(item.payloadData()); m_data->modifyItem(item); }, startMode); } else { job->setExpectedError(1, QStringLiteral("The item or the collection doesn't exist")); Utils::JobHandler::install(job, noop, startMode); } return job; } KJob *AkonadiFakeStorage::moveItems(Akonadi::Item::List items, Akonadi::Collection collection, QObject *parent) { using namespace std::placeholders; auto job = new FakeJob(parent); auto startMode = startModeForParent(parent); bool allItemsExist = std::all_of(items.constBegin(), items.constEnd(), [=] (const Akonadi::Item &item) { return m_data->item(item.id()).isValid(); }); if (allItemsExist && m_data->collection(collection.id()).isValid()) { Utils::JobHandler::install(job, [=] () mutable { std::transform(items.constBegin(), items.constEnd(), items.begin(), [=] (const Akonadi::Item &item) { auto result = item; result.setParentCollection(collection); // Force payload detach result.setPayloadFromData(result.payloadData()); return result; }); foreach (const Akonadi::Item &item, items) { m_data->modifyItem(item); } }, startMode); } else { job->setExpectedError(1, QStringLiteral("One of the items or the collection doesn't exist")); Utils::JobHandler::install(job, noop, startMode); } return job; } KJob *AkonadiFakeStorage::createCollection(Akonadi::Collection collection, QObject *parent) { Q_ASSERT(!collection.isValid()); auto job = new FakeJob(parent); auto startMode = startModeForParent(parent); if (!m_data->collection(collection.id()).isValid()) { Utils::JobHandler::install(job, [=] () mutable { collection.setId(m_data->maxCollectionId() + 1); m_data->createCollection(collection); }, startMode); } else { job->setExpectedError(1, QStringLiteral("The collection already exists")); Utils::JobHandler::install(job, noop, startMode); } return job; } KJob *AkonadiFakeStorage::updateCollection(Akonadi::Collection collection, QObject *parent) { auto job = new FakeJob(parent); auto startMode = startModeForParent(parent); if (m_data->collection(collection.id()).isValid()) { Utils::JobHandler::install(job, [=] { m_data->modifyCollection(collection); }, startMode); } else { job->setExpectedError(1, QStringLiteral("The collection doesn't exist")); Utils::JobHandler::install(job, noop, startMode); } return job; } KJob *AkonadiFakeStorage::removeCollection(Akonadi::Collection collection, QObject *parent) { auto job = new FakeJob(parent); auto startMode = startModeForParent(parent); if (m_data->collection(collection.id()).isValid()) { Utils::JobHandler::install(job, [=] { m_data->removeCollection(collection); }, startMode); } else { job->setExpectedError(1, QStringLiteral("The collection doesn't exist")); Utils::JobHandler::install(job, noop, startMode); } return job; } KJob *AkonadiFakeStorage::createTransaction() { auto job = new AkonadiFakeTransaction; Utils::JobHandler::install(job, noop); return job; } KJob *AkonadiFakeStorage::createTag(Akonadi::Tag tag) { Q_ASSERT(!tag.isValid()); auto job = new FakeJob; if (!m_data->tag(tag.id()).isValid()) { Utils::JobHandler::install(job, [=] () mutable { tag.setId(m_data->maxTagId() + 1); m_data->createTag(tag); }); } else { job->setExpectedError(1, QStringLiteral("The tag already exists")); Utils::JobHandler::install(job, noop); } return job; } KJob *AkonadiFakeStorage::updateTag(Akonadi::Tag tag) { auto job = new FakeJob; if (m_data->tag(tag.id()).isValid()) { Utils::JobHandler::install(job, [=] { m_data->modifyTag(tag); }); } else { job->setExpectedError(1, QStringLiteral("The tag doesn't exist")); Utils::JobHandler::install(job, noop); } return job; } KJob *AkonadiFakeStorage::removeTag(Akonadi::Tag tag) { auto job = new FakeJob; if (m_data->tag(tag.id()).isValid()) { Utils::JobHandler::install(job, [=] { m_data->removeTag(tag); }); } else { job->setExpectedError(1, QStringLiteral("The tag doesn't exist")); Utils::JobHandler::install(job, noop); } return job; } Akonadi::CollectionFetchJobInterface *AkonadiFakeStorage::fetchCollections(Akonadi::Collection collection, Akonadi::StorageInterface::FetchDepth depth, Akonadi::StorageInterface::FetchContentTypes types) { auto job = new AkonadiFakeCollectionFetchJob; auto children = Akonadi::Collection::List(); switch (depth) { case Base: children << m_data->collection(findId(collection)); break; case FirstLevel: children << m_data->childCollections(findId(collection)); break; case Recursive: children = collectChildren(collection); break; } auto collections = Akonadi::Collection::List(); if (types == Akonadi::StorageInterface::AllContent) { collections = children; } else { std::copy_if(children.constBegin(), children.constEnd(), std::back_inserter(collections), [types] (const Akonadi::Collection &col) { const auto mime = col.contentMimeTypes(); return ((types & Akonadi::StorageInterface::Tasks) && mime.contains(KCalCore::Todo::todoMimeType())) || ((types & Akonadi::StorageInterface::Notes) && mime.contains(Akonadi::NoteUtils::noteMimeType())); }); } if (depth != Base) { // Replace the dummy parents in the ancestor chain with proper ones // full of juicy data using namespace std::placeholders; auto completeCollection = std::bind(&AkonadiFakeData::reconstructAncestors, m_data, _1, collection); std::transform(collections.begin(), collections.end(), collections.begin(), completeCollection); } const auto behavior = m_data->storageBehavior().fetchCollectionsBehavior(collection.id()); if (behavior == AkonadiFakeStorageBehavior::NormalFetch) job->setCollections(collections); job->setExpectedError(m_data->storageBehavior().fetchCollectionsErrorCode(collection.id())); Utils::JobHandler::install(job, noop); return job; } -Akonadi::CollectionSearchJobInterface *AkonadiFakeStorage::searchCollections(QString collectionName, FetchContentTypes types) -{ - auto job = new AkonadiFakeCollectionSearchJob; - const auto allCollections = m_data->collections(); - auto foundCollections = Akonadi::Collection::List(); - - std::copy_if(allCollections.constBegin(), allCollections.constEnd(), - std::back_inserter(foundCollections), - [collectionName, types] (const Akonadi::Collection &col) { - const auto mime = col.contentMimeTypes().toSet(); - auto contentMimeTypes = QSet(); - if (types & Notes) - contentMimeTypes << Akonadi::NoteUtils::noteMimeType(); - if (types & Tasks) - contentMimeTypes << KCalCore::Todo::todoMimeType(); - - const bool supportedType = contentMimeTypes.isEmpty() - || !(mime & contentMimeTypes).isEmpty(); - return supportedType && col.displayName().contains(collectionName, Qt::CaseInsensitive); - }); - - // Replace the dummy parents in the ancestor chain with proper ones - // full of juicy data - using namespace std::placeholders; - auto reconstructCollection = std::bind(&AkonadiFakeData::reconstructAncestors, - m_data, _1, Akonadi::Collection::root()); - std::transform(foundCollections.begin(), foundCollections.end(), - foundCollections.begin(), reconstructCollection); - - const auto behavior = m_data->storageBehavior().searchCollectionsBehavior(collectionName); - if (behavior == AkonadiFakeStorageBehavior::NormalFetch) - job->setCollections(foundCollections); - job->setExpectedError(m_data->storageBehavior().searchCollectionsErrorCode(collectionName)); - Utils::JobHandler::install(job, noop); - return job; -} - Akonadi::ItemFetchJobInterface *AkonadiFakeStorage::fetchItems(Akonadi::Collection collection) { auto items = m_data->childItems(findId(collection)); std::transform(items.begin(), items.end(), items.begin(), [this] (const Akonadi::Item &item) { auto result = m_data->reconstructItemDependencies(item); // Force payload detach result.setPayloadFromData(result.payloadData()); return result; }); auto job = new AkonadiFakeItemFetchJob; const auto behavior = m_data->storageBehavior().fetchItemsBehavior(collection.id()); if (behavior == AkonadiFakeStorageBehavior::NormalFetch) job->setItems(items); job->setExpectedError(m_data->storageBehavior().fetchItemsErrorCode(collection.id())); return job; } Akonadi::ItemFetchJobInterface *AkonadiFakeStorage::fetchItem(Akonadi::Item item) { auto fullItem = m_data->item(findId(item)); fullItem = m_data->reconstructItemDependencies(fullItem); // Force payload detach fullItem.setPayloadFromData(fullItem.payloadData()); auto job = new AkonadiFakeItemFetchJob; const auto behavior = m_data->storageBehavior().fetchItemBehavior(item.id()); if (behavior == AkonadiFakeStorageBehavior::NormalFetch) job->setItems(Akonadi::Item::List() << fullItem); job->setExpectedError(m_data->storageBehavior().fetchItemErrorCode(item.id())); return job; } Akonadi::ItemFetchJobInterface *AkonadiFakeStorage::fetchTagItems(Akonadi::Tag tag) { auto items = m_data->tagItems(findId(tag)); std::transform(items.begin(), items.end(), items.begin(), [this] (const Akonadi::Item &item) { auto collection = m_data->reconstructAncestors(item.parentCollection()); auto result = item; result.setParentCollection(collection); // Force payload detach result.setPayloadFromData(result.payloadData()); return result; }); auto job = new AkonadiFakeItemFetchJob; const auto behavior = m_data->storageBehavior().fetchTagItemsBehavior(tag.id()); if (behavior == AkonadiFakeStorageBehavior::NormalFetch) job->setItems(items); job->setExpectedError(m_data->storageBehavior().fetchTagItemsErrorCode(tag.id())); return job; } Akonadi::TagFetchJobInterface *AkonadiFakeStorage::fetchTags() { auto job = new AkonadiFakeTagFetchJob; const auto behavior = m_data->storageBehavior().fetchTagsBehavior(); if (behavior == AkonadiFakeStorageBehavior::NormalFetch) job->setTags(m_data->tags()); job->setExpectedError(m_data->storageBehavior().fetchTagsErrorCode()); return job; } Akonadi::Tag::Id AkonadiFakeStorage::findId(const Akonadi::Tag &tag) { if (tag.isValid() || tag.gid().isEmpty()) return tag.id(); const auto gid = tag.gid(); auto tags = m_data->tags(); auto result = std::find_if(tags.constBegin(), tags.constEnd(), [gid] (const Akonadi::Tag &tag) { return tag.gid() == gid; }); return (result != tags.constEnd()) ? result->id() : tag.id(); } Akonadi::Collection::Id AkonadiFakeStorage::findId(const Akonadi::Collection &collection) { if (collection.isValid() || collection.remoteId().isEmpty()) return collection.id(); const auto remoteId = collection.remoteId(); auto collections = m_data->collections(); auto result = std::find_if(collections.constBegin(), collections.constEnd(), [remoteId] (const Akonadi::Collection &collection) { return collection.remoteId() == remoteId; }); return (result != collections.constEnd()) ? result->id() : collection.id(); } Akonadi::Item::Id AkonadiFakeStorage::findId(const Akonadi::Item &item) { if (item.isValid() || item.remoteId().isEmpty()) return item.id(); const auto remoteId = item.remoteId(); auto items = m_data->items(); auto result = std::find_if(items.constBegin(), items.constEnd(), [remoteId] (const Akonadi::Item &item) { return item.remoteId() == remoteId; }); return (result != items.constEnd()) ? result->id() : item.id(); } Akonadi::Collection::List AkonadiFakeStorage::collectChildren(const Akonadi::Collection &root) { auto collections = Akonadi::Collection::List(); foreach (const auto &child, m_data->childCollections(findId(root))) { if (child.enabled() || child.referenced()) collections << m_data->collection(findId(child)); collections += collectChildren(child); } return collections; } #include "akonadifakestorage.moc" diff --git a/tests/testlib/akonadifakestorage.h b/tests/testlib/akonadifakestorage.h index fade61e3..a0de6a60 100644 --- a/tests/testlib/akonadifakestorage.h +++ b/tests/testlib/akonadifakestorage.h @@ -1,76 +1,75 @@ /* This file is part of Zanshin Copyright 2015 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #ifndef TESTLIB_AKONADIFAKESTORAGE_H #define TESTLIB_AKONADIFAKESTORAGE_H #include "akonadi/akonadistorageinterface.h" namespace Testlib { class AkonadiFakeData; class AkonadiFakeStorage : public Akonadi::StorageInterface { public: explicit AkonadiFakeStorage(AkonadiFakeData *data); Akonadi::Collection defaultTaskCollection() Q_DECL_OVERRIDE; Akonadi::Collection defaultNoteCollection() Q_DECL_OVERRIDE; KJob *createItem(Akonadi::Item item, Akonadi::Collection collection) Q_DECL_OVERRIDE; KJob *updateItem(Akonadi::Item item, QObject *parent = Q_NULLPTR) Q_DECL_OVERRIDE; KJob *removeItem(Akonadi::Item item) Q_DECL_OVERRIDE; KJob *removeItems(Akonadi::Item::List items, QObject *parent = Q_NULLPTR) Q_DECL_OVERRIDE; KJob *moveItem(Akonadi::Item item, Akonadi::Collection collection, QObject *parent = Q_NULLPTR) Q_DECL_OVERRIDE; KJob *moveItems(Akonadi::Item::List items, Akonadi::Collection collection, QObject *parent = Q_NULLPTR) Q_DECL_OVERRIDE; KJob *createCollection(Akonadi::Collection collection, QObject *parent = Q_NULLPTR) Q_DECL_OVERRIDE; KJob *updateCollection(Akonadi::Collection collection, QObject *parent = Q_NULLPTR) Q_DECL_OVERRIDE; KJob *removeCollection(Akonadi::Collection collection, QObject *parent = Q_NULLPTR) Q_DECL_OVERRIDE; KJob *createTransaction() Q_DECL_OVERRIDE; KJob *createTag(Akonadi::Tag tag) Q_DECL_OVERRIDE; KJob *updateTag(Akonadi::Tag tag) Q_DECL_OVERRIDE; KJob *removeTag(Akonadi::Tag tag) Q_DECL_OVERRIDE; Akonadi::CollectionFetchJobInterface *fetchCollections(Akonadi::Collection collection, FetchDepth depth, FetchContentTypes types) Q_DECL_OVERRIDE; - Akonadi::CollectionSearchJobInterface *searchCollections(QString collectionName, FetchContentTypes types) Q_DECL_OVERRIDE; Akonadi::ItemFetchJobInterface *fetchItems(Akonadi::Collection collection) Q_DECL_OVERRIDE; Akonadi::ItemFetchJobInterface *fetchItem(Akonadi::Item item) Q_DECL_OVERRIDE; Akonadi::ItemFetchJobInterface *fetchTagItems(Akonadi::Tag tag) Q_DECL_OVERRIDE; Akonadi::TagFetchJobInterface *fetchTags() Q_DECL_OVERRIDE; private: Akonadi::Tag::Id findId(const Akonadi::Tag &tag); Akonadi::Collection::Id findId(const Akonadi::Collection &collection); Akonadi::Item::Id findId(const Akonadi::Item &item); Akonadi::Collection::List collectChildren(const Akonadi::Collection &root); AkonadiFakeData *m_data; }; } #endif // TESTLIB_AKONADIFAKESTORAGE_H diff --git a/tests/testlib/akonadistoragetestbase.cpp b/tests/testlib/akonadistoragetestbase.cpp index a15458ec..2ab0661a 100644 --- a/tests/testlib/akonadistoragetestbase.cpp +++ b/tests/testlib/akonadistoragetestbase.cpp @@ -1,1473 +1,1254 @@ /* This file is part of Zanshin Copyright 2014-2015 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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 "akonadistoragetestbase.h" #include #include #include #include #include #include #include #include "utils/mem_fn.h" #include "AkonadiCore/qtest_akonadi.h" #include #include "akonadi/akonadiapplicationselectedattribute.h" #include "akonadi/akonadicollectionfetchjobinterface.h" -#include "akonadi/akonadicollectionsearchjobinterface.h" #include "akonadi/akonadiitemfetchjobinterface.h" #include "akonadi/akonadimonitorimpl.h" #include "akonadi/akonadistorage.h" #include "akonadi/akonadistoragesettings.h" #include "akonadi/akonaditagfetchjobinterface.h" #include "akonadi/akonaditimestampattribute.h" using namespace Testlib; AkonadiStorageTestBase::AkonadiStorageTestBase(QObject *parent) : QObject(parent) { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); } void AkonadiStorageTestBase::cleanupTestCase() { // Give a chance for jobs still waiting for an event loop // run to be deleted through deleteLater() QTest::qWait(10); } void AkonadiStorageTestBase::dumpTree() { TestLib::AkonadiDebug::dumpTree(createStorage()); } void AkonadiStorageTestBase::shouldListCollections_data() { QTest::addColumn("collection"); QTest::addColumn("expectedNames"); QTest::addColumn("depth"); QTest::addColumn("contentTypes"); - QTest::addColumn("referenceCalendar1"); - QTest::addColumn("enableCalendar1"); QTest::newRow("all") << Akonadi::Collection::root() << QStringList({ "Calendar1", "Calendar2", "Calendar3", "Change me!", "Destroy me!", "Notes" }) << Akonadi::Storage::Recursive - << int(Akonadi::StorageInterface::Notes|Akonadi::StorageInterface::Tasks) - << false << true; - - QTest::newRow("include referenced") << Akonadi::Collection::root() - << QStringList({ "Calendar1", "Calendar2", "Calendar3", "Change me!", "Destroy me!", "Notes" }) - << Akonadi::Storage::Recursive - << int(Akonadi::StorageInterface::Notes|Akonadi::StorageInterface::Tasks) - << true << false; - - QTest::newRow("include referenced + enabled") << Akonadi::Collection::root() - << QStringList({ "Calendar1", "Calendar2", "Calendar3", "Change me!", "Destroy me!", "Notes" }) - << Akonadi::Storage::Recursive - << int(Akonadi::StorageInterface::Notes|Akonadi::StorageInterface::Tasks) - << true << true; - - QTest::newRow("exclude !referenced + !enabled") << Akonadi::Collection::root() - << QStringList({ "Calendar2", "Calendar3", "Change me!", "Destroy me!", "Notes" }) - << Akonadi::Storage::Recursive - << int(Akonadi::StorageInterface::Notes|Akonadi::StorageInterface::Tasks) - << false << false; + << int(Akonadi::StorageInterface::Notes|Akonadi::StorageInterface::Tasks); QTest::newRow("notes") << Akonadi::Collection::root() << QStringList({ "Notes" }) << Akonadi::Storage::Recursive - << int(Akonadi::StorageInterface::Notes) - << false << true; + << int(Akonadi::StorageInterface::Notes); QTest::newRow("tasks") << Akonadi::Collection::root() << QStringList({ "Calendar1", "Calendar2", "Calendar3", "Change me!", "Destroy me!" }) << Akonadi::Storage::Recursive - << int(Akonadi::StorageInterface::Tasks) - << false << true; + << int(Akonadi::StorageInterface::Tasks); QTest::newRow("base type") << calendar2() << QStringList({"Calendar2"}) << Akonadi::Storage::Base - << int(Akonadi::StorageInterface::Tasks) - << false << true; + << int(Akonadi::StorageInterface::Tasks); QTest::newRow("firstLevel type") << calendar1() << QStringList({"Calendar2"}) << Akonadi::Storage::FirstLevel - << int(Akonadi::StorageInterface::Tasks) - << false << true; + << int(Akonadi::StorageInterface::Tasks); QTest::newRow("recursive type") << calendar1() << QStringList({"Calendar2", "Calendar3"}) << Akonadi::Storage::Recursive - << int(Akonadi::StorageInterface::Tasks) - << false << true; + << int(Akonadi::StorageInterface::Tasks); } void AkonadiStorageTestBase::shouldListCollections() { // GIVEN QFETCH(Akonadi::Collection, collection); QFETCH(QStringList, expectedNames); QFETCH(Akonadi::StorageInterface::FetchDepth, depth); QFETCH(int, contentTypes); - QFETCH(bool, referenceCalendar1); - QFETCH(bool, enableCalendar1); auto storage = createStorage(); - // Default is not referenced and enabled - // no need to feedle with the collection in that case - if (referenceCalendar1 || !enableCalendar1) { - Akonadi::Collection cal1 = calendar1(); - cal1.setReferenced(referenceCalendar1); - cal1.setEnabled(enableCalendar1); - auto update = storage->updateCollection(cal1); - AKVERIFYEXEC(update); - } - - // WHEN auto job = storage->fetchCollections(collection, depth, Akonadi::StorageInterface::FetchContentTypes(contentTypes)); AKVERIFYEXEC(job->kjob()); // THEN auto collections = job->collections(); QStringList collectionNames; collectionNames.reserve(collections.size()); foreach (const auto &collection, collections) { collectionNames << collection.name(); } collectionNames.sort(); - // Restore proper DB state - if (referenceCalendar1 || !enableCalendar1) { - Akonadi::Collection cal1 = calendar1(); - cal1.setReferenced(false); - cal1.setEnabled(true); - auto update = storage->updateCollection(cal1); - AKVERIFYEXEC(update); - } - QCOMPARE(collectionNames, expectedNames); } void AkonadiStorageTestBase::shouldRetrieveAllCollectionAncestors() { // GIVEN auto storage = createStorage(); // WHEN auto job = storage->fetchCollections(Akonadi::Collection::root(), Akonadi::Storage::Recursive, Akonadi::Storage::Tasks|Akonadi::Storage::Notes); AKVERIFYEXEC(job->kjob()); // THEN auto collections = job->collections(); foreach (const auto &collection, collections) { auto parent = collection.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); QVERIFY(!parent.displayName().isEmpty()); parent = parent.parentCollection(); } } } void AkonadiStorageTestBase::shouldListFullItemsInACollection() { // GIVEN auto storage = createStorage(); const QStringList expectedRemoteIds = { "{1d33862f-f274-4c67-ab6c-362d56521ff4}", "{1d33862f-f274-4c67-ab6c-362d56521ff5}", "{1d33862f-f274-4c67-ab6c-362d56521ff6}", "{7824df00-2fd6-47a4-8319-52659dc82005}", "{7824df00-2fd6-47a4-8319-52659dc82006}" }; // WHEN auto job = storage->fetchItems(calendar2()); AKVERIFYEXEC(job->kjob()); // THEN auto items = job->items(); QStringList itemRemoteIds; itemRemoteIds.reserve(items.size()); foreach (const auto &item, items) { itemRemoteIds << item.remoteId(); QVERIFY(item.loadedPayloadParts().contains(Akonadi::Item::FullPayload)); QVERIFY(!item.attributes().isEmpty()); QVERIFY(item.modificationTime().isValid()); QVERIFY(!item.flags().isEmpty()); Akonadi::Tag::List tags = item.tags(); QVERIFY(!item.tags().isEmpty()); foreach (const auto &tag, tags) { QVERIFY(tag.isValid()); QVERIFY(!tag.name().isEmpty()); QVERIFY(!tag.type().isEmpty()); } auto parent = item.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); parent = parent.parentCollection(); } } itemRemoteIds.sort(); QCOMPARE(itemRemoteIds, expectedRemoteIds); } void AkonadiStorageTestBase::shouldListTags() { // GIVEN auto storage = createStorage(); const QStringList expectedGids = { "change-me", "delete-me", "errands-context", "online-context", "philosophy-tag", "physics-tag" }; // WHEN auto job = storage->fetchTags(); AKVERIFYEXEC(job->kjob()); // THEN auto tags = job->tags(); QStringList tagGids; tagGids.reserve(tags.size()); foreach (const auto &tag, tags) { tagGids << tag.gid(); QVERIFY(!tag.name().isEmpty()); QVERIFY(!tag.type().isEmpty()); } tagGids.sort(); QCOMPARE(tagGids, expectedGids); } void AkonadiStorageTestBase::shouldListItemsAssociatedWithTag() { // GIVEN auto storage = createStorage(); Akonadi::Tag tag = fetchTagByGID(QStringLiteral("errands-context")); const QStringList expectedRemoteIds = { "{1d33862f-f274-4c67-ab6c-362d56521ff4}", "{7824df00-2fd6-47a4-8319-52659dc82005}" }; // WHEN auto job = storage->fetchTagItems(tag); AKVERIFYEXEC(job->kjob()); // THEN auto items = job->items(); QStringList itemRemoteIds; itemRemoteIds.reserve(items.size()); foreach (const auto &item, items) { itemRemoteIds << item.remoteId(); QVERIFY(item.loadedPayloadParts().contains(Akonadi::Item::FullPayload)); QVERIFY(!item.attributes().isEmpty()); QVERIFY(item.modificationTime().isValid()); QVERIFY(!item.flags().isEmpty()); auto parent = item.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); parent = parent.parentCollection(); } } itemRemoteIds.sort(); QCOMPARE(itemRemoteIds, expectedRemoteIds); } void AkonadiStorageTestBase::shouldNotifyCollectionAdded() { // GIVEN // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::collectionAdded); MonitorSpy monitorSpy(monitor.data()); // A collection Akonadi::Collection collection; collection.setParentCollection(calendar2()); collection.setName(QStringLiteral("Foo!")); collection.setContentMimeTypes(QStringList() << QStringLiteral("application/x-vnd.akonadi.calendar.todo")); // WHEN auto storage = createStorage(); auto job = storage->createCollection(collection); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedCollection = spy.takeFirst().at(0).value(); QCOMPARE(notifiedCollection.name(), collection.name()); auto parent = notifiedCollection.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); parent = parent.parentCollection(); } } void AkonadiStorageTestBase::shouldNotifyCollectionRemoved() { // GIVEN // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::collectionRemoved); MonitorSpy monitorSpy(monitor.data()); // An existing item (if we trust the test data) Akonadi::Collection collection = fetchCollectionByRID(QStringLiteral("{1f78b360-a01b-4785-9187-75450190342c}")); QVERIFY(collection.isValid()); // WHEN auto storage = createStorage(); auto job = storage->removeCollection(collection); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedCollection= spy.takeFirst().at(0).value(); QCOMPARE(notifiedCollection.id(), collection.id()); } void AkonadiStorageTestBase::shouldNotifyCollectionChanged() { // GIVEN // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::collectionChanged); MonitorSpy monitorSpy(monitor.data()); // A colection with an existing id (if we trust the test data) Akonadi::Collection collection = fetchCollectionByRID(QStringLiteral("{28ef9f03-4ebc-4e33-970f-f379775894f9}")); QVERIFY(collection.isValid()); collection.setName(QStringLiteral("Bar!")); // WHEN auto storage = createStorage(); auto job = storage->updateCollection(collection); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedCollection = spy.takeFirst().at(0).value(); QCOMPARE(notifiedCollection.id(), collection.id()); QCOMPARE(notifiedCollection.name(), collection.name()); auto parent = notifiedCollection.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); parent = parent.parentCollection(); } } void AkonadiStorageTestBase::shouldNotifyItemAdded() { // GIVEN // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemAdded); MonitorSpy monitorSpy(monitor.data()); // A todo... KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(QStringLiteral("summary")); todo->setDescription(QStringLiteral("content")); todo->setCompleted(false); todo->setDtStart(KDateTime(QDate(2013, 11, 24))); todo->setDtDue(KDateTime(QDate(2014, 03, 01))); // ... as payload of an item... Akonadi::Item item; item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); item.addAttribute(new Akonadi::EntityDisplayAttribute); // WHEN auto storage = createStorage(); auto job = storage->createItem(item, calendar2()); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedItem = spy.takeFirst().at(0).value(); QCOMPARE(*notifiedItem.payload(), *todo); QVERIFY(notifiedItem.hasAttribute()); auto parent = notifiedItem.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); parent = parent.parentCollection(); } } void AkonadiStorageTestBase::shouldNotifyItemRemoved() { // GIVEN // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemRemoved); MonitorSpy monitorSpy(monitor.data()); const Akonadi::Collection notesCol = fetchCollectionByRID(QStringLiteral("{f5e3f1be-b998-4c56-aa3d-e3a6e7e5493a}")); Akonadi::Item item = fetchItemByRID(QStringLiteral("{d0159c99-0d23-41fa-bb5f-436570140f8b}"), notesCol); QVERIFY(item.isValid()); // WHEN auto storage = createStorage(); auto job = storage->removeItem(item); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedItem = spy.takeFirst().at(0).value(); QCOMPARE(notifiedItem.id(), item.id()); auto parent = notifiedItem.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); parent = parent.parentCollection(); } } void AkonadiStorageTestBase::shouldNotifyItemChanged() { // GIVEN // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemChanged); MonitorSpy monitorSpy(monitor.data()); // A todo... KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(QStringLiteral("summary")); todo->setDescription(QStringLiteral("content")); todo->setCompleted(false); todo->setDtStart(KDateTime(QDate(2013, 11, 24))); todo->setDtDue(KDateTime(QDate(2014, 03, 01))); // ... as payload of an existing item (if we trust the test data)... Akonadi::Item item = fetchItemByRID(QStringLiteral("{1d33862f-f274-4c67-ab6c-362d56521ff6}"), calendar2()); QVERIFY(item.isValid()); item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); item.addAttribute(new Akonadi::EntityDisplayAttribute); // WHEN auto storage = createStorage(); auto job = storage->updateItem(item); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedItem = spy.takeFirst().at(0).value(); QCOMPARE(notifiedItem.id(), item.id()); QCOMPARE(*notifiedItem.payload(), *todo); QVERIFY(notifiedItem.hasAttribute()); auto parent = notifiedItem.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); parent = parent.parentCollection(); } } void AkonadiStorageTestBase::shouldNotifyItemTagAdded() { // GIVEN // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemChanged); MonitorSpy monitorSpy(monitor.data()); // An existing item (if we trust the test data)... Akonadi::Item item = fetchItemByRID(QStringLiteral("{1d33862f-f274-4c67-ab6c-362d56521ff5}"), calendar2()); QVERIFY(item.isValid()); item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); // An existing tag (if we trust the test data) Akonadi::Tag tag(5); // WHEN item.setTag(tag); auto storage = createStorage(); auto job = storage->updateItem(item); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedItem = spy.takeFirst().at(0).value(); QCOMPARE(notifiedItem.id(), item.id()); QVERIFY(notifiedItem.hasPayload()); Akonadi::Tag::List notifiedTags = notifiedItem.tags(); QVERIFY(notifiedTags.contains(tag)); foreach (const auto &tag, notifiedTags) { QVERIFY(tag.isValid()); QVERIFY(!tag.name().isEmpty()); QVERIFY(!tag.type().isEmpty()); } auto parent = notifiedItem.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); parent = parent.parentCollection(); } } void AkonadiStorageTestBase::shouldNotifyItemTagRemoved() // aka dissociate { // GIVEN auto storage = createStorage(); Akonadi::Tag tag = fetchTagByGID(QStringLiteral("philosophy-tag")); const QString expectedRemoteIds = {QStringLiteral("{7824df00-2fd6-47a4-8319-52659dc82006}")}; auto job = storage->fetchTagItems(tag); AKVERIFYEXEC(job->kjob()); auto item = job->items().at(0); QCOMPARE(item.remoteId(), expectedRemoteIds); QVERIFY(item.loadedPayloadParts().contains(Akonadi::Item::FullPayload)); QVERIFY(!item.attributes().isEmpty()); QVERIFY(item.modificationTime().isValid()); QVERIFY(!item.flags().isEmpty()); QVERIFY(!item.tags().isEmpty()); // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemChanged); MonitorSpy monitorSpy(monitor.data()); // WHEN item.clearTag(tag); auto jobUpdate = storage->updateItem(item); AKVERIFYEXEC(jobUpdate); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedItem = spy.takeFirst().at(0).value(); QCOMPARE(notifiedItem.id(), item.id()); QVERIFY(!notifiedItem.tags().contains(tag)); } void AkonadiStorageTestBase::shouldNotifyTagAdded() { // GIVEN // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::tagAdded); MonitorSpy monitorSpy(monitor.data()); // A tag Akonadi::Tag tag; tag.setGid("gid"); tag.setName(QStringLiteral("name")); tag.setType("type"); // WHEN auto storage = createStorage(); auto job = storage->createTag(tag); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedTag = spy.takeFirst().at(0).value(); QCOMPARE(notifiedTag.gid(), tag.gid()); QCOMPARE(notifiedTag.name(), tag.name()); QCOMPARE(notifiedTag.type(), tag.type()); } void AkonadiStorageTestBase::shouldNotifyTagRemoved() { // GIVEN // An existing tag (if we trust the test data) connected to an existing item tagged to it auto storage = createStorage(); Akonadi::Tag tag = fetchTagByGID(QStringLiteral("delete-me")); // NOTE : this item was linked to the delete-me tag during test time const QString expectedRemoteIds = {QStringLiteral("{1d33862f-f274-4c67-ab6c-362d56521ff5}")}; auto job = storage->fetchTagItems(tag); AKVERIFYEXEC(job->kjob()); QCOMPARE(job->items().size(), 1); auto itemTagged = job->items().at(0); QCOMPARE(itemTagged.remoteId(), expectedRemoteIds); // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::tagRemoved); QSignalSpy spyItemChanged(monitor.data(), &Akonadi::MonitorInterface::itemChanged); MonitorSpy monitorSpy(monitor.data()); // WHEN auto jobDelete = storage->removeTag(tag); AKVERIFYEXEC(jobDelete); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); QTRY_VERIFY(!spyItemChanged.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedTag = spy.takeFirst().at(0).value(); QCOMPARE(notifiedTag.id(), tag.id()); QCOMPARE(spyItemChanged.size(), 1); auto notifiedItem = spyItemChanged.takeFirst().at(0).value(); QCOMPARE(notifiedItem.id(), itemTagged.id()); } void AkonadiStorageTestBase::shouldNotifyTagChanged() { // GIVEN // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::tagChanged); MonitorSpy monitorSpy(monitor.data()); // An existing tag (if we trust the test data) Akonadi::Tag tag(6); tag.setName(QStringLiteral("Oh it changed!")); // WHEN auto storage = createStorage(); auto job = storage->updateTag(tag); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedTag = spy.takeFirst().at(0).value(); QCOMPARE(notifiedTag.id(), tag.id()); QCOMPARE(notifiedTag.name(), tag.name()); } void AkonadiStorageTestBase::shouldReadDefaultNoteCollectionFromSettings() { // GIVEN // A storage implementation auto storage = createStorage(); // WHEN Akonadi::StorageSettings::instance().setDefaultNoteCollection(Akonadi::Collection(24)); // THEN QCOMPARE(storage->defaultNoteCollection(), Akonadi::Collection(24)); } void AkonadiStorageTestBase::shouldReadDefaultTaskCollectionFromSettings() { // GIVEN // A storage implementation auto storage = createStorage(); // WHEN Akonadi::StorageSettings::instance().setDefaultTaskCollection(Akonadi::Collection(24)); // THEN QCOMPARE(storage->defaultTaskCollection(), Akonadi::Collection(24)); } void AkonadiStorageTestBase::shouldUpdateItem() { // GIVEN // A storage implementation auto storage = createStorage(); // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemChanged); MonitorSpy monitorSpy(monitor.data()); // A todo... KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(QStringLiteral("new summary")); todo->setDescription(QStringLiteral("new content")); // ... as payload of an existing item (if we trust the test data)... Akonadi::Item item = fetchItemByRID(QStringLiteral("{1d33862f-f274-4c67-ab6c-362d56521ff4}"), calendar2()); item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); // WHEN auto job = storage->updateItem(item); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedItem = spy.takeFirst().at(0).value(); QCOMPARE(notifiedItem.id(), item.id()); // KCalCore 4.83 fixes this bug #if KCALCORE_VERSION < 0x045300 QCOMPARE(notifiedItem.payload()->uid(), todo->uid()); QCOMPARE(notifiedItem.payload()->summary(), todo->summary()); QCOMPARE(notifiedItem.payload()->description(), todo->description()); QEXPECT_FAIL("", "Bug introduced by 76c686bc1de3a5d16956a627744ce352bc28d12a in KCalCore", Continue); QCOMPARE(*notifiedItem.payload(), *todo); QEXPECT_FAIL("", "Bug introduced by 76c686bc1de3a5d16956a627744ce352bc28d12a in KCalCore", Continue); QCOMPARE(notifiedItem.payload()->status(), todo->status()); #else QCOMPARE(*notifiedItem.payload(), *todo); #endif } void AkonadiStorageTestBase::shouldUseTransaction() { // GIVEN auto storage = createStorage(); Akonadi::Item item1 = fetchItemByRID(QStringLiteral("{0aa4dc30-a2c2-4e08-8241-033b3344debc}"), calendar1()); QVERIFY(item1.isValid()); Akonadi::Item item2 = fetchItemByRID(QStringLiteral("{5dc1aba7-eead-4254-ba7a-58e397de1179}"), calendar1()); QVERIFY(item2.isValid()); // create wrong item Akonadi::Item item3(10000); item3.setRemoteId(QStringLiteral("wrongId")); // A spied monitor auto monitor = createMonitor(); QSignalSpy spyUpdated(monitor.data(), &Akonadi::MonitorInterface::itemChanged); MonitorSpy monitorSpy(monitor.data()); // WHEN auto todo = item1.payload(); todo->setSummary(QStringLiteral("Buy tomatoes")); todo = item2.payload(); todo->setSummary(QStringLiteral("Buy chocolate")); auto transaction = storage->createTransaction(); storage->updateItem(item1, transaction); storage->updateItem(item3, transaction); // this job should fail storage->updateItem(item2, transaction); QVERIFY(!transaction->exec()); monitorSpy.waitForStableState(); // THEN QCOMPARE(spyUpdated.size(), 0); auto job = storage->fetchItem(item1); AKVERIFYEXEC(job->kjob()); QCOMPARE(job->items().size(), 1); item1 = job->items().at(0); job = storage->fetchItem(item2); AKVERIFYEXEC(job->kjob()); QCOMPARE(job->items().size(), 1); item2 = job->items().at(0); QCOMPARE(item1.payload()->summary(), QStringLiteral("Buy kiwis")); QCOMPARE(item2.payload()->summary(), QStringLiteral("Buy cheese")); } void AkonadiStorageTestBase::shouldCreateItem() { // GIVEN // A storage implementation auto storage = createStorage(); // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemAdded); MonitorSpy monitorSpy(monitor.data()); // A todo... KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(QStringLiteral("summary")); todo->setDescription(QStringLiteral("content")); todo->setCompleted(false); todo->setDtStart(KDateTime(QDate(2013, 11, 24))); todo->setDtDue(KDateTime(QDate(2014, 03, 01))); // ... as payload of a new item Akonadi::Item item; item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); // WHEN auto job = storage->createItem(item, calendar2()); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedItem = spy.takeFirst().at(0).value(); QCOMPARE(notifiedItem.parentCollection(), calendar2()); QCOMPARE(*notifiedItem.payload(), *todo); } void AkonadiStorageTestBase::shouldRetrieveItem() { // GIVEN auto storage = createStorage(); Akonadi::Item findItem = fetchItemByRID(QStringLiteral("{7824df00-2fd6-47a4-8319-52659dc82005}"), calendar2()); QVERIFY(findItem.isValid()); // WHEN auto job = storage->fetchItem(findItem); AKVERIFYEXEC(job->kjob()); // THEN auto items = job->items(); QCOMPARE(items.size(), 1); const auto &item = items[0]; QCOMPARE(item.id(), findItem.id()); QVERIFY(item.loadedPayloadParts().contains(Akonadi::Item::FullPayload)); QVERIFY(!item.attributes().isEmpty()); QVERIFY(item.modificationTime().isValid()); QVERIFY(!item.flags().isEmpty()); auto parent = item.parentCollection(); while (parent != Akonadi::Collection::root()) { QVERIFY(parent.isValid()); parent = parent.parentCollection(); } } void AkonadiStorageTestBase::shouldMoveItem() { // GIVEN auto storage = createStorage(); Akonadi::Item item = fetchItemByRID(QStringLiteral("{7824df00-2fd6-47a4-8319-52659dc82005}"), calendar2()); QVERIFY(item.isValid()); // A spied monitor auto monitor = createMonitor(); QSignalSpy spyMoved(monitor.data(), &Akonadi::MonitorInterface::itemMoved); MonitorSpy monitorSpy(monitor.data()); auto job = storage->moveItem(item, calendar1()); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spyMoved.isEmpty()); QCOMPARE(spyMoved.size(), 1); auto movedItem = spyMoved.takeFirst().at(0).value(); QCOMPARE(movedItem.id(), item.id()); } void AkonadiStorageTestBase::shouldMoveItems() { // GIVEN auto storage = createStorage(); Akonadi::Item item = fetchItemByRID(QStringLiteral("{1d33862f-f274-4c67-ab6c-362d56521ff4}"), calendar2()); QVERIFY(item.isValid()); Akonadi::Item::List list; list << item; // A spied monitor auto monitor = createMonitor(); QSignalSpy spyMoved(monitor.data(), &Akonadi::MonitorInterface::itemMoved); MonitorSpy monitorSpy(monitor.data()); auto job = storage->moveItems(list, calendar1()); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spyMoved.isEmpty()); QCOMPARE(spyMoved.size(), 1); auto movedItem = spyMoved.takeFirst().at(0).value(); QCOMPARE(movedItem.id(), item.id()); } void AkonadiStorageTestBase::shouldDeleteItem() { //GIVEN auto storage = createStorage(); // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemRemoved); MonitorSpy monitorSpy(monitor.data()); // An existing item (if we trust the test data) Akonadi::Item item = fetchItemByRID(QStringLiteral("{0aa4dc30-a2c2-4e08-8241-033b3344debc}"), calendar1()); QVERIFY(item.isValid()); //When auto job = storage->removeItem(item); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedItem = spy.takeFirst().at(0).value(); QCOMPARE(notifiedItem.id(), item.id()); } void AkonadiStorageTestBase::shouldDeleteItems() { //GIVEN auto storage = createStorage(); // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::itemRemoved); MonitorSpy monitorSpy(monitor.data()); // An existing item (if we trust the test data) Akonadi::Item item = fetchItemByRID(QStringLiteral("{6c7bf5b9-4136-4203-9f45-54e32ea0eacb}"), calendar1()); QVERIFY(item.isValid()); Akonadi::Item item2 = fetchItemByRID(QStringLiteral("{83cf0b15-8d61-436b-97ae-4bd88fb2fef9}"), calendar1()); QVERIFY(item2.isValid()); Akonadi::Item::List list; list << item << item2; //When auto job = storage->removeItems(list); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 2); auto notifiedItem = spy.takeFirst().at(0).value(); QCOMPARE(notifiedItem.id(), item.id()); notifiedItem = spy.takeFirst().at(0).value(); QCOMPARE(notifiedItem.id(), item2.id()); } void AkonadiStorageTestBase::shouldCreateTag() { // GIVEN // A storage implementation auto storage = createStorage(); // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::tagAdded); MonitorSpy monitorSpy(monitor.data()); // A tag Akonadi::Tag tag; QString name = QStringLiteral("Tag42"); const QByteArray type = QByteArray("Zanshin-Context"); const QByteArray gid = QByteArray(name.toLatin1()); tag.setName(name); tag.setType(QByteArray("Zanshin-Context")); tag.setGid(gid); // WHEN auto job = storage->createTag(tag); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedTag = spy.takeFirst().at(0).value(); QCOMPARE(notifiedTag.name(), name); QCOMPARE(notifiedTag.type(), type); QCOMPARE(notifiedTag.gid(), gid); } void AkonadiStorageTestBase::shouldRemoveTag() { // GIVEN auto storage = createStorage(); // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::tagRemoved); MonitorSpy monitorSpy(monitor.data()); // An existing tag Akonadi::Tag tag = fetchTagByGID(QStringLiteral("errands-context")); // WHEN auto job = storage->removeTag(tag); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedTag = spy.takeFirst().at(0).value(); QCOMPARE(notifiedTag.id(), tag.id()); } void AkonadiStorageTestBase::shouldUpdateTag() { // GIVEN auto storage = createStorage(); // A spied monitor auto monitor = createMonitor(); QSignalSpy spy(monitor.data(), &Akonadi::MonitorInterface::tagChanged); MonitorSpy monitorSpy(monitor.data()); // An existing tag Akonadi::Tag tag = fetchTagByGID(QStringLiteral("change-me")); // WHEN auto job = storage->updateTag(tag); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!spy.isEmpty()); // THEN QCOMPARE(spy.size(), 1); auto notifiedTag = spy.takeFirst().at(0).value(); QCOMPARE(notifiedTag.id(), tag.id()); } void AkonadiStorageTestBase::shouldUpdateCollection() { // GIVEN // A storage implementation auto storage = createStorage(); // An existing collection Akonadi::Collection collection = calendar2(); // A spied monitor auto monitor = createMonitor(); QSignalSpy changeSpy(monitor.data(), &Akonadi::MonitorInterface::collectionChanged); QSignalSpy selectionSpy(monitor.data(), &Akonadi::MonitorInterface::collectionSelectionChanged); MonitorSpy monitorSpy(monitor.data()); // WHEN auto attr = new Akonadi::EntityDisplayAttribute; attr->setDisplayName(QStringLiteral("Foo")); collection.addAttribute(attr); auto job = storage->updateCollection(collection); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!changeSpy.isEmpty()); // THEN QCOMPARE(changeSpy.size(), 1); QCOMPARE(selectionSpy.size(), 0); auto notifiedCollection = changeSpy.takeFirst().at(0).value(); QCOMPARE(notifiedCollection.id(), collection.id()); QVERIFY(notifiedCollection.hasAttribute()); QCOMPARE(notifiedCollection.attribute()->displayName(), attr->displayName()); } void AkonadiStorageTestBase::shouldNotifyCollectionTimestampChanges() { // GIVEN // A storage implementation auto storage = createStorage(); // An existing collection Akonadi::Collection collection = calendar2(); // A spied monitor auto monitor = createMonitor(); QSignalSpy changeSpy(monitor.data(), &Akonadi::MonitorInterface::collectionChanged); MonitorSpy monitorSpy(monitor.data()); // WHEN collection.attribute(Akonadi::Collection::AddIfMissing)->refreshTimestamp(); auto job = storage->updateCollection(collection); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!changeSpy.isEmpty()); // THEN QCOMPARE(changeSpy.size(), 1); auto notifiedCollection = changeSpy.takeFirst().at(0).value(); QCOMPARE(notifiedCollection.id(), collection.id()); QVERIFY(notifiedCollection.hasAttribute()); } void AkonadiStorageTestBase::shouldNotifyCollectionSelectionChanges() { // GIVEN // A storage implementation auto storage = createStorage(); // An existing collection Akonadi::Collection collection = calendar2(); // A spied monitor auto monitor = createMonitor(); QSignalSpy changeSpy(monitor.data(), &Akonadi::MonitorInterface::collectionChanged); QSignalSpy selectionSpy(monitor.data(), &Akonadi::MonitorInterface::collectionSelectionChanged); MonitorSpy monitorSpy(monitor.data()); // WHEN auto attr = new Akonadi::ApplicationSelectedAttribute; attr->setSelected(false); collection.addAttribute(attr); auto job = storage->updateCollection(collection); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!changeSpy.isEmpty()); // THEN QCOMPARE(changeSpy.size(), 1); QCOMPARE(selectionSpy.size(), 1); auto notifiedCollection = changeSpy.takeFirst().at(0).value(); QCOMPARE(notifiedCollection.id(), collection.id()); QVERIFY(notifiedCollection.hasAttribute()); QVERIFY(!notifiedCollection.attribute()->isSelected()); notifiedCollection = selectionSpy.takeFirst().at(0).value(); QCOMPARE(notifiedCollection.id(), collection.id()); QVERIFY(notifiedCollection.hasAttribute()); QVERIFY(!notifiedCollection.attribute()->isSelected()); } void AkonadiStorageTestBase::shouldNotNotifyCollectionSelectionChangesForIrrelevantCollections() { // GIVEN // A storage implementation auto storage = createStorage(); // An existing collection Akonadi::Collection collection = emails(); // A spied monitor auto monitor = createMonitor(); QSignalSpy changeSpy(monitor.data(), &Akonadi::MonitorInterface::collectionChanged); QSignalSpy selectionSpy(monitor.data(), &Akonadi::MonitorInterface::collectionSelectionChanged); MonitorSpy monitorSpy(monitor.data()); // WHEN auto attr = new Akonadi::ApplicationSelectedAttribute; attr->setSelected(false); collection.addAttribute(attr); auto job = storage->updateCollection(collection); AKVERIFYEXEC(job); monitorSpy.waitForStableState(); QTRY_VERIFY(!changeSpy.isEmpty()); // THEN QCOMPARE(changeSpy.size(), 1); QVERIFY(selectionSpy.isEmpty()); } -void AkonadiStorageTestBase::shouldNotifyCollectionSubscriptionChanges_data() -{ - QTest::addColumn("isEnabled"); - QTest::addColumn("isReferenced"); - - QTest::newRow("enabled and !referenced") << true << false; - // Fails randomly due to an akonadi bug... - //QTest::newRow("!enabled and referenced") << false << true; - QTest::newRow("!enabled and !referenced") << false << false; - QTest::newRow("!enabled and referenced (again)") << false << true; - QTest::newRow("enabled and !referenced (again)") << true << false; -} - -void AkonadiStorageTestBase::shouldNotifyCollectionSubscriptionChanges() -{ - // GIVEN - QFETCH(bool, isEnabled); - QFETCH(bool, isReferenced); - - // A storage implementation - auto storage = createStorage(); - - // An existing collection - Akonadi::Collection collection(calendar2().id()); - - // A spied monitor - auto monitor = createMonitor(); - QSignalSpy changeSpy(monitor.data(), &Akonadi::MonitorInterface::collectionChanged); - MonitorSpy monitorSpy(monitor.data()); - - // WHEN - static int run = 1; - collection.attribute(Akonadi::Collection::AddIfMissing) - ->setIconName(QStringLiteral("folder-%1").arg(run++)); - collection.setEnabled(isEnabled); - collection.setReferenced(isReferenced); - auto job = storage->updateCollection(collection); - AKVERIFYEXEC(job); - monitorSpy.waitForStableState(); - QTRY_VERIFY(!changeSpy.isEmpty()); - - // THEN - QCOMPARE(changeSpy.size(), 1); - - auto notifiedCollection = changeSpy.takeFirst().at(0).value(); - QCOMPARE(notifiedCollection.id(), collection.id()); - QCOMPARE(notifiedCollection.enabled(), isEnabled); - QCOMPARE(notifiedCollection.referenced(), isReferenced); -} - -void AkonadiStorageTestBase::shouldFindCollectionsByName_data() -{ - QTest::addColumn("name"); - QTest::addColumn("contentType"); - QTest::addColumn("expectedResults"); - QTest::addColumn("referenceCalendar1"); - QTest::addColumn("enableCalendar1"); - - QStringList expectedResults; - expectedResults << QStringLiteral("Calendar1"); - QTest::newRow("get a collection") << "Calendar1" << int(Akonadi::StorageInterface::Tasks) << expectedResults << false << true; - - expectedResults.clear(); - QTest::newRow("try with wrong type") << "Calendar1" << int(Akonadi::StorageInterface::Notes) << expectedResults << false << true; - - expectedResults << QStringLiteral("Notes"); - QTest::newRow("get a note collection") << "Not" << int(Akonadi::StorageInterface::Notes) << expectedResults << false << true; - - expectedResults.clear(); - QTest::newRow("try with unknown name") << "toto" << int(Akonadi::StorageInterface::Tasks) << expectedResults << false << true; - - expectedResults << QStringLiteral("Calendar3") << QStringLiteral("Calendar2") << QStringLiteral("Calendar1"); - QTest::newRow("try with a part of a name") << "Calendar" << int(Akonadi::StorageInterface::Tasks) << expectedResults << false << true; - - expectedResults.clear(); - expectedResults << QStringLiteral("Calendar2"); - QTest::newRow("make sure it is case insensitive") << "calendar2" << int(Akonadi::StorageInterface::Tasks) << expectedResults << false << true; - - expectedResults.clear(); - expectedResults << QStringLiteral("Calendar1"); - QTest::newRow("include referenced") << "Calendar1" << int(Akonadi::StorageInterface::Tasks) << expectedResults << true << false; - QTest::newRow("include referenced + enabled") << "Calendar1" << int(Akonadi::StorageInterface::Tasks) << expectedResults << true << true; - QTest::newRow("include !referenced + !enabled") << "Calendar1" << int(Akonadi::StorageInterface::Tasks) << expectedResults << false << false; -} - -void AkonadiStorageTestBase::shouldFindCollectionsByName() -{ - // GIVEN - auto storage = createStorage(); - - QFETCH(QString, name); - QFETCH(int, contentType); - QFETCH(QStringList, expectedResults); - QFETCH(bool, referenceCalendar1); - QFETCH(bool, enableCalendar1); - - // A spied monitor - auto monitor = createMonitor(); - MonitorSpy monitorSpy(monitor.data()); - - // Default is not referenced and enabled - // no need to feedle with the collection in that case - if (referenceCalendar1 || !enableCalendar1) { - Akonadi::Collection cal1 = calendar1(); - cal1.setReferenced(referenceCalendar1); - cal1.setEnabled(enableCalendar1); - auto update = storage->updateCollection(cal1); - AKVERIFYEXEC(update); - monitorSpy.waitForStableState(); - } - - // WHEN - auto job = storage->searchCollections(name, Akonadi::StorageInterface::FetchContentType(contentType)); - AKVERIFYEXEC(job->kjob()); - - // THEN - auto collections = job->collections(); - - // Restore proper DB state - if (referenceCalendar1 || !enableCalendar1) { - Akonadi::Collection cal1 = calendar1(); - cal1.setReferenced(false); - cal1.setEnabled(true); - auto update = storage->updateCollection(cal1); - AKVERIFYEXEC(update); - monitorSpy.waitForStableState(); - } - - auto collectionNames = QStringList(); - std::transform(collections.constBegin(), collections.constEnd(), - std::back_inserter(collectionNames), - Utils::mem_fn(&Akonadi::Collection::name)); - QCOMPARE(collectionNames.toSet(), expectedResults.toSet()); -} - -void AkonadiStorageTestBase::shouldFindCollectionsByNameIncludingTheirAncestors_data() -{ - QTest::addColumn("searchTerm"); - QTest::addColumn("contentType"); - - QTest::newRow("task search") << "Calendar3" << int(Akonadi::StorageInterface::Tasks); - QTest::newRow("note search") << "Notes" << int(Akonadi::StorageInterface::Notes); -} - -void AkonadiStorageTestBase::shouldFindCollectionsByNameIncludingTheirAncestors() -{ - // GIVEN - auto storage = createStorage(); - - // WHEN - QFETCH(QString, searchTerm); - QFETCH(int, contentType); - auto job = storage->searchCollections(searchTerm, Akonadi::StorageInterface::FetchContentType(contentType)); - AKVERIFYEXEC(job->kjob()); - - // THEN - auto collections = job->collections(); - foreach (const auto &collection, collections) { - auto parent = collection.parentCollection(); - while (parent != Akonadi::Collection::root()) { - QVERIFY(parent.isValid()); - QVERIFY(!parent.displayName().isEmpty()); - parent = parent.parentCollection(); - } - } -} - - - Akonadi::Item AkonadiStorageTestBase::fetchItemByRID(const QString &remoteId, const Akonadi::Collection &collection) { Akonadi::Item item; item.setRemoteId(remoteId); auto job = createStorage()->fetchItem(item); job->setCollection(collection); if (!job->kjob()->exec()) { qWarning() << job->kjob()->errorString(); return Akonadi::Item(); } if (job->items().count() != 1) { qWarning() << "Received unexpected amount of items: " << job->items().count(); return Akonadi::Item(); } return job->items().at(0); } Akonadi::Collection AkonadiStorageTestBase::fetchCollectionByRID(const QString &remoteId) { Akonadi::Collection collection; collection.setRemoteId(remoteId); auto job = createStorage()->fetchCollections(collection, Akonadi::StorageInterface::Base, Akonadi::StorageInterface::AllContent); job->setResource(QStringLiteral("akonadi_knut_resource_0")); - job->setFiltered(false); if (!job->kjob()->exec()) { qWarning() << job->kjob()->errorString() << remoteId; return Akonadi::Collection(); } if (job->collections().count() != 1) { qWarning() << "Received unexpected amount of collections: " << job->collections().count(); return Akonadi::Collection(); } return job->collections().at(0); } Akonadi::Tag AkonadiStorageTestBase::fetchTagByGID(const QString &gid) { auto job = createStorage()->fetchTags(); if (!job->kjob()->exec()) { qWarning() << job->kjob()->errorString(); return Akonadi::Tag(); } auto tags = job->tags(); foreach (const auto &tag, tags) { if (tag.gid() == gid) return tag; } return Akonadi::Tag(); } Akonadi::Collection AkonadiStorageTestBase::calendar1() { return fetchCollectionByRID(QStringLiteral("{cdc229c7-a9b5-4d37-989d-a28e372be2a9}")); } Akonadi::Collection AkonadiStorageTestBase::calendar2() { return fetchCollectionByRID(QStringLiteral("{e682b8b5-b67c-4538-8689-6166f64177f0}")); } Akonadi::Collection AkonadiStorageTestBase::emails() { return fetchCollectionByRID(QStringLiteral("{14096930-7bfe-46ca-8fba-7c04d3b62ec8}")); } diff --git a/tests/testlib/akonadistoragetestbase.h b/tests/testlib/akonadistoragetestbase.h index a2ae1593..d50c54f2 100644 --- a/tests/testlib/akonadistoragetestbase.h +++ b/tests/testlib/akonadistoragetestbase.h @@ -1,110 +1,104 @@ /* This file is part of Zanshin Copyright 2014-2015 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #ifndef TESTLIB_AKONADISTORAGETESTBASE_H #define TESTLIB_AKONADISTORAGETESTBASE_H #include "akonadi/akonadimonitorinterface.h" #include "akonadi/akonadistorageinterface.h" Q_DECLARE_METATYPE(Akonadi::StorageInterface::FetchDepth) namespace Testlib { class AkonadiStorageTestBase : public QObject { Q_OBJECT public: explicit AkonadiStorageTestBase(QObject *parent = Q_NULLPTR); protected: virtual Akonadi::StorageInterface::Ptr createStorage() = 0; virtual Akonadi::MonitorInterface::Ptr createMonitor() = 0; private slots: void cleanupTestCase(); void dumpTree(); void shouldListCollections_data(); void shouldListCollections(); void shouldRetrieveAllCollectionAncestors(); void shouldListFullItemsInACollection(); void shouldListTags(); void shouldListItemsAssociatedWithTag(); void shouldNotifyCollectionAdded(); void shouldNotifyCollectionRemoved(); void shouldNotifyCollectionChanged(); void shouldNotifyItemAdded(); void shouldNotifyItemRemoved(); void shouldNotifyItemChanged(); void shouldNotifyItemTagAdded(); void shouldNotifyItemTagRemoved(); void shouldNotifyTagAdded(); void shouldNotifyTagRemoved(); void shouldNotifyTagChanged(); void shouldReadDefaultNoteCollectionFromSettings(); void shouldReadDefaultTaskCollectionFromSettings(); // This test must be run before shouldCreateItem because createItem // sometimes notifies an itemChanged with a delay. So this test might // receive this notification in addition to the itemChanged generated by updateItem. void shouldUpdateItem(); // This test must be run before shouldCreateItem because createItem // sometimes notifies an itemChanged with a delay. So this test might // receive this notification in addition to the itemChanged generated by updateItem. void shouldUseTransaction(); void shouldCreateItem(); void shouldRetrieveItem(); void shouldMoveItem(); void shouldMoveItems(); void shouldDeleteItem(); void shouldDeleteItems(); void shouldCreateTag(); void shouldRemoveTag(); void shouldUpdateTag(); void shouldUpdateCollection(); void shouldNotifyCollectionTimestampChanges(); void shouldNotifyCollectionSelectionChanges(); void shouldNotNotifyCollectionSelectionChangesForIrrelevantCollections(); - void shouldNotifyCollectionSubscriptionChanges_data(); - void shouldNotifyCollectionSubscriptionChanges(); - void shouldFindCollectionsByName_data(); - void shouldFindCollectionsByName(); - void shouldFindCollectionsByNameIncludingTheirAncestors_data(); - void shouldFindCollectionsByNameIncludingTheirAncestors(); private: Akonadi::Item fetchItemByRID(const QString &remoteId, const Akonadi::Collection &collection); Akonadi::Collection fetchCollectionByRID(const QString &remoteId); Akonadi::Tag fetchTagByGID(const QString &gid); Akonadi::Collection calendar1(); Akonadi::Collection calendar2(); Akonadi::Collection emails(); }; } #endif // TESTLIB_AKONADISTORAGETESTBASE_H diff --git a/tests/testlib/gencollection.cpp b/tests/testlib/gencollection.cpp index a9fc722c..1fec4279 100644 --- a/tests/testlib/gencollection.cpp +++ b/tests/testlib/gencollection.cpp @@ -1,121 +1,109 @@ /* This file is part of Zanshin Copyright 2015 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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 "gencollection.h" #include #include #include #include "akonadi/akonadiapplicationselectedattribute.h" using namespace Testlib; GenCollection::GenCollection(const Akonadi::Collection &collection) : m_collection(collection) { } GenCollection::operator Akonadi::Collection() { return m_collection; } GenCollection &GenCollection::withId(Akonadi::Collection::Id id) { m_collection.setId(id); return *this; } GenCollection &GenCollection::withParent(Akonadi::Collection::Id id) { m_collection.setParentCollection(Akonadi::Collection(id)); return *this; } GenCollection &GenCollection::withRootAsParent() { m_collection.setParentCollection(Akonadi::Collection::root()); return *this; } GenCollection &GenCollection::withName(const QString &name) { m_collection.setName(name); return *this; } GenCollection &GenCollection::withIcon(const QString &iconName) { auto attr = m_collection.attribute(Akonadi::Collection::AddIfMissing); attr->setIconName(iconName); return *this; } -GenCollection &GenCollection::referenced(bool value) -{ - m_collection.setReferenced(value); - return *this; -} - -GenCollection &GenCollection::enabled(bool value) -{ - m_collection.setEnabled(value); - return *this; -} - GenCollection &GenCollection::selected(bool value) { if (!value) { auto attr = m_collection.attribute(Akonadi::Collection::AddIfMissing); attr->setSelected(false); } else { m_collection.removeAttribute(); } return *this; } GenCollection &GenCollection::withTaskContent(bool value) { auto mimeTypes = m_collection.contentMimeTypes(); if (!value) { mimeTypes.removeAll(KCalCore::Todo::todoMimeType()); } else if (!mimeTypes.contains(KCalCore::Todo::todoMimeType())) { mimeTypes.append(KCalCore::Todo::todoMimeType()); } m_collection.setContentMimeTypes(mimeTypes); return *this; } GenCollection &GenCollection::withNoteContent(bool value) { auto mimeTypes = m_collection.contentMimeTypes(); if (!value) { mimeTypes.removeAll(Akonadi::NoteUtils::noteMimeType()); } else if (!mimeTypes.contains(Akonadi::NoteUtils::noteMimeType())) { mimeTypes.append(Akonadi::NoteUtils::noteMimeType()); } m_collection.setContentMimeTypes(mimeTypes); return *this; } diff --git a/tests/testlib/gencollection.h b/tests/testlib/gencollection.h index 7e73e799..70d712fe 100644 --- a/tests/testlib/gencollection.h +++ b/tests/testlib/gencollection.h @@ -1,57 +1,55 @@ /* This file is part of Zanshin Copyright 2015 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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. */ #ifndef TESTLIB_GENCOLLECTION_H #define TESTLIB_GENCOLLECTION_H #include #include namespace Testlib { class GenCollection { public: explicit GenCollection(const Akonadi::Collection &collection = Akonadi::Collection()); operator Akonadi::Collection(); GenCollection &withId(Akonadi::Collection::Id id); GenCollection &withParent(Akonadi::Collection::Id id); GenCollection &withRootAsParent(); GenCollection &withName(const QString &name); GenCollection &withIcon(const QString &iconName); - GenCollection &referenced(bool value = true); - GenCollection &enabled(bool value = true); GenCollection &selected(bool value = true); GenCollection &withTaskContent(bool value = true); GenCollection &withNoteContent(bool value = true); private: Akonadi::Collection m_collection; }; } #endif // TESTLIB_GENCOLLECTION_H diff --git a/tests/units/akonadi/akonadidatasourcequeriestest.cpp b/tests/units/akonadi/akonadidatasourcequeriestest.cpp index 1f028269..1a069c49 100644 --- a/tests/units/akonadi/akonadidatasourcequeriestest.cpp +++ b/tests/units/akonadi/akonadidatasourcequeriestest.cpp @@ -1,1210 +1,592 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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 "akonadi/akonadidatasourcequeries.h" #include "akonadi/akonadiserializer.h" #include "akonadi/akonadistoragesettings.h" #include "utils/jobhandler.h" #include "utils/mem_fn.h" #include "testlib/akonadifakedata.h" #include "testlib/gencollection.h" #include "testlib/testhelpers.h" using namespace Testlib; typedef std::function::Ptr(Domain::DataSourceQueries*)> QueryFunction; Q_DECLARE_METATYPE(QueryFunction) typedef std::function SetDefaultCollectionFunction; Q_DECLARE_METATYPE(SetDefaultCollectionFunction) typedef std::function GetDefaultCollectionFunction; Q_DECLARE_METATYPE(GetDefaultCollectionFunction) Q_DECLARE_METATYPE(Akonadi::StorageInterface::FetchContentType) class AkonadiDataSourceQueriesTest : public QObject { Q_OBJECT public: explicit AkonadiDataSourceQueriesTest(QObject *parent = Q_NULLPTR) : QObject(parent) { qRegisterMetaType(); } private slots: void shouldCheckIfASourceIsDefaultFromSettings_data() { QTest::addColumn("contentType"); QTest::addColumn("setDefaultCollection"); QTest::addColumn("getDefaultCollection"); { SetDefaultCollectionFunction setFunction = Utils::mem_fn(&Akonadi::StorageSettings::setDefaultNoteCollection); GetDefaultCollectionFunction getFunction = Utils::mem_fn(&Akonadi::StorageSettings::defaultNoteCollection); QTest::newRow("notes") << Akonadi::StorageInterface::Notes << setFunction << getFunction; } { SetDefaultCollectionFunction setFunction = Utils::mem_fn(&Akonadi::StorageSettings::setDefaultTaskCollection); GetDefaultCollectionFunction getFunction = Utils::mem_fn(&Akonadi::StorageSettings::defaultTaskCollection); QTest::newRow("tasks") << Akonadi::StorageInterface::Tasks << setFunction << getFunction; } } void shouldCheckIfASourceIsDefaultFromSettings() { // GIVEN const auto minId = qint64(42); const auto maxId = qint64(44); const auto defaultId = qint64(43); QFETCH(Akonadi::StorageInterface::FetchContentType, contentType); QFETCH(SetDefaultCollectionFunction, setDefaultCollection); // A default collection for saving setDefaultCollection(&Akonadi::StorageSettings::instance(), Akonadi::Collection(defaultId)); // A few data sources AkonadiFakeData data; for (auto id = minId; id <= maxId; ++id) { auto col = GenCollection().withId(id).withName(QString::number(id)).withRootAsParent(); if (contentType == Akonadi::StorageInterface::Tasks) data.createCollection(col.withTaskContent()); else data.createCollection(col.withTaskContent()); } // WHEN auto serializer = Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer); QScopedPointer queries(new Akonadi::DataSourceQueries(contentType, Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); // THEN for (auto id = minId; id <= maxId; ++id) { auto source = serializer->createDataSourceFromCollection(data.collection(id), Akonadi::SerializerInterface::BaseName); QCOMPARE(queries->isDefaultSource(source), id == defaultId); } } void shouldStoreDefaultSourceInTheSettingsAndNotify_data() { shouldCheckIfASourceIsDefaultFromSettings_data(); } void shouldStoreDefaultSourceInTheSettingsAndNotify() { // GIVEN const auto minId = qint64(42); const auto maxId = qint64(44); const auto defaultId = qint64(43); QFETCH(Akonadi::StorageInterface::FetchContentType, contentType); QFETCH(SetDefaultCollectionFunction, setDefaultCollection); // A default collection for saving setDefaultCollection(&Akonadi::StorageSettings::instance(), Akonadi::Collection(minId)); // A few data sources AkonadiFakeData data; for (auto id = minId; id <= maxId; ++id) { auto col = GenCollection().withId(id).withName(QString::number(id)).withRootAsParent(); if (contentType == Akonadi::StorageInterface::Tasks) data.createCollection(col.withTaskContent()); else data.createCollection(col.withTaskContent()); } // WHEN auto serializer = Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer); QScopedPointer queries(new Akonadi::DataSourceQueries(contentType, Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); QSignalSpy spy(queries->notifier(), &Domain::DataSourceQueriesNotifier::defaultSourceChanged); auto defaultSource = serializer->createDataSourceFromCollection(data.collection(defaultId), Akonadi::SerializerInterface::BaseName); queries->setDefaultSource(defaultSource); // THEN QFETCH(GetDefaultCollectionFunction, getDefaultCollection); QCOMPARE(getDefaultCollection(&Akonadi::StorageSettings::instance()).id(), defaultId); QCOMPARE(spy.count(), 1); } void shouldLookInAllReportedForTopLevelSources_data() { QTest::addColumn("contentTypes"); QTest::addColumn("expectedNames"); auto expectedNames = QStringList(); expectedNames << QStringLiteral("42Task") << QStringLiteral("44Note"); QTest::newRow("tasks and notes") << int(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes) << expectedNames; expectedNames.clear(); expectedNames << QStringLiteral("42Task"); QTest::newRow("tasks") << int(Akonadi::StorageInterface::Tasks) << expectedNames; expectedNames.clear(); expectedNames << QStringLiteral("44Note"); QTest::newRow("notes") << int(Akonadi::StorageInterface::Notes) << expectedNames; } void shouldLookInAllReportedForTopLevelSources() { // GIVEN AkonadiFakeData data; // Two top level collections, one with tasks, one with notes data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44Note")).withRootAsParent().withNoteContent()); // One with a note child collection data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43TaskChild")).withParent(42).withTaskContent()); // One with a task child collection data.createCollection(GenCollection().withId(45).withName(QStringLiteral("45NoteChild")).withParent(44).withNoteContent()); // WHEN QFETCH(int, contentTypes); QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::FetchContentTypes(contentTypes), Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult::Ptr result = queries->findTopLevel(); result->data(); result = queries->findTopLevel(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); const auto sources = result->data(); auto actualNames = QStringList(); std::transform(sources.constBegin(), sources.constEnd(), std::back_inserter(actualNames), [] (const Domain::DataSource::Ptr &source) { return source->name(); }); actualNames.sort(); QFETCH(QStringList, expectedNames); expectedNames.sort(); QCOMPARE(actualNames, expectedNames); } void shouldReactToCollectionAddsForTopLevelSources() { // GIVEN AkonadiFakeData data; QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult::Ptr result = queries->findTopLevel(); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); // WHEN data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Note")).withRootAsParent().withNoteContent()); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42Task")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43Note")); } void shouldReactToCollectionRemovesForTopLevelSources() { // GIVEN AkonadiFakeData data; // Two top level collections and two child collections data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Note")).withRootAsParent().withNoteContent()); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("43TaskChild")).withParent(42).withTaskContent()); data.createCollection(GenCollection().withId(45).withName(QStringLiteral("43NoteChild")).withParent(43).withNoteContent()); QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult::Ptr result = queries->findTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.removeCollection(Akonadi::Collection(42)); data.removeCollection(Akonadi::Collection(43)); // THEN QCOMPARE(result->data().size(), 0); } void shouldReactToItemChangesForTopLevelTasks() { // GIVEN AkonadiFakeData data; // Two top level collections and one child collection data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Note")).withRootAsParent().withNoteContent()); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44NoteChild")).withParent(43).withNoteContent()); QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult::Ptr result = queries->findTopLevel(); bool replaceHandlerCalled = false; result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::DataSource::Ptr &, int) { replaceHandlerCalled = true; }); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.modifyCollection(GenCollection(data.collection(42)).withName(QStringLiteral("42TaskBis"))); data.modifyCollection(GenCollection(data.collection(43)).withName(QStringLiteral("43NoteBis"))); TestHelpers::waitForEmptyJobQueue(); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42TaskBis")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43NoteBis")); QVERIFY(replaceHandlerCalled); } - void shouldRemoveUnlistedCollectionFromTopLevelTasks() - { - // GIVEN - AkonadiFakeData data; - - // Two top level collections and one child collection - data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); - data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Note")).withRootAsParent().withNoteContent()); - data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44NoteChild")).withParent(43).withNoteContent()); - - QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, - Akonadi::StorageInterface::Ptr(data.createStorage()), - Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - - Domain::QueryResult::Ptr result = queries->findTopLevel(); - TestHelpers::waitForEmptyJobQueue(); - QCOMPARE(result->data().size(), 2); - - // WHEN - data.modifyCollection(GenCollection(data.collection(43)).referenced(false).enabled(false)); // datasource unlisted now - - // THEN - QCOMPARE(result->data().size(), 1); - QCOMPARE(result->data().first()->name(), QStringLiteral("42Task")); - } - void shouldNotCrashDuringFindTopLevelWhenFetchJobFailedOrEmpty_data() { QTest::addColumn("colErrorCode"); QTest::addColumn("colFetchBehavior"); QTest::addColumn("deleteQuery"); QTest::newRow("No error with empty collection list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("No error with empty collection list (+ query delete)") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << true; QTest::newRow("Error with empty collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("Error with empty collection list (+ query delete)") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << true; QTest::newRow("Error with collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << false; QTest::newRow("Error with collection list (+ query delete)") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << true; } void shouldNotCrashDuringFindTopLevelWhenFetchJobFailedOrEmpty() { // GIVEN AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withNoteContent()); QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); QFETCH(int, colErrorCode); QFETCH(int, colFetchBehavior); data.storageBehavior().setFetchCollectionsErrorCode(Akonadi::Collection::root().id(), colErrorCode); data.storageBehavior().setFetchCollectionsBehavior(Akonadi::Collection::root().id(), AkonadiFakeStorageBehavior::FetchBehavior(colFetchBehavior)); QFETCH(bool, deleteQuery); // WHEN Domain::QueryResult::Ptr result = queries->findTopLevel(); if (deleteQuery) delete queries.take(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 0); } void shouldLookInAllReportedForChildSources_data() { QTest::addColumn("contentTypes"); QTest::addColumn("expectedNames"); auto expectedNames = QStringList(); expectedNames << QStringLiteral("43TaskFirstChild") << QStringLiteral("45NoteSecondChild"); QTest::newRow("tasks and notes") << int(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes) << expectedNames; expectedNames.clear(); expectedNames << QStringLiteral("43TaskFirstChild"); QTest::newRow("tasks") << int(Akonadi::StorageInterface::Tasks) << expectedNames; expectedNames.clear(); expectedNames << QStringLiteral("45NoteSecondChild"); QTest::newRow("notes") << int(Akonadi::StorageInterface::Notes) << expectedNames; } void shouldLookInAllReportedForChildSources() { // GIVEN AkonadiFakeData data; // One top level collection with two children (one of them also having a child) data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43TaskFirstChild")).withParent(42).withTaskContent()); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44TaskFirstChildChild")).withParent(43).withTaskContent()); data.createCollection(GenCollection().withId(45).withName(QStringLiteral("45NoteSecondChild")).withParent(42).withNoteContent()); // Serializer auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); Domain::DataSource::Ptr topLevelDataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); // WHEN QFETCH(int, contentTypes); QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::FetchContentTypes(contentTypes), Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult::Ptr result = queries->findChildren(topLevelDataSource); result->data(); result = queries->findChildren(topLevelDataSource); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); const auto sources = result->data(); auto actualNames = QStringList(); std::transform(sources.constBegin(), sources.constEnd(), std::back_inserter(actualNames), [] (const Domain::DataSource::Ptr &source) { return source->name(); }); actualNames.sort(); QFETCH(QStringList, expectedNames); expectedNames.sort(); QCOMPARE(actualNames, expectedNames); } void shouldReactToCollectionAddsForChildSources() { // GIVEN AkonadiFakeData data; // One top level collection with no child yet data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); // Serializer auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); Domain::DataSource::Ptr topLevelDataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult::Ptr result = queries->findChildren(topLevelDataSource); result->data(); result = queries->findChildren(topLevelDataSource); // Should not cause any problem or wrong data TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 0); // WHEN data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43TaskChild")).withParent(42).withTaskContent()); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().first()->name(), QStringLiteral("43TaskChild")); } void shouldReactToCollectionRemovesForChildSources() { // GIVEN AkonadiFakeData data; // One top level collection with two children data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43TaskFirstChild")).withParent(42).withTaskContent()); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("45NoteSecondChild")).withParent(42).withNoteContent()); // Serializer auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); Domain::DataSource::Ptr topLevelDataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult::Ptr result = queries->findChildren(topLevelDataSource); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.removeCollection(Akonadi::Collection(44)); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().first()->name(), QStringLiteral("43TaskFirstChild")); } void shouldReactToCollectionChangesForChildSources() { // GIVEN AkonadiFakeData data; // One top level collection with two children data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43TaskFirstChild")).withParent(42).withTaskContent()); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44NoteSecondChild")).withParent(42).withNoteContent()); // Serializer auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); Domain::DataSource::Ptr topLevelDataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult::Ptr result = queries->findChildren(topLevelDataSource); bool replaceHandlerCalled = false; result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::DataSource::Ptr &, int) { replaceHandlerCalled = true; }); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.modifyCollection(GenCollection(data.collection(43)).withName(QStringLiteral("43TaskFirstChildBis"))); // THEN TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().first()->name(), QStringLiteral("43TaskFirstChildBis")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("44NoteSecondChild")); QVERIFY(replaceHandlerCalled); } - void shouldRemoveUnlistedCollectionsFromChildSources() - { - // GIVEN - AkonadiFakeData data; - - // One top level collection with two children - data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); - data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43TaskFirstChild")).withParent(42).withTaskContent()); - data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44NoteSecondChild")).withParent(42).withNoteContent()); - - // Serializer - auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); - Domain::DataSource::Ptr topLevelDataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); - - QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, - Akonadi::StorageInterface::Ptr(data.createStorage()), - serializer, - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - - Domain::QueryResult::Ptr result = queries->findChildren(topLevelDataSource); - TestHelpers::waitForEmptyJobQueue(); - QCOMPARE(result->data().size(), 2); - - // WHEN - data.modifyCollection(GenCollection(data.collection(43)).referenced(false).enabled(false)); // unlist the child collection - - // THEN - QCOMPARE(result->data().size(), 1); - QCOMPARE(result->data().first()->name(), QStringLiteral("44NoteSecondChild")); - } - void shouldNotCrashDuringFindChildrenWhenFetchJobFailedOrEmpty_data() { QTest::addColumn("colErrorCode"); QTest::addColumn("colFetchBehavior"); QTest::addColumn("deleteQuery"); QTest::newRow("No error with empty collection list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("No error with empty collection list (+ query delete)") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << true; QTest::newRow("Error with empty collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("Error with empty collection list (+ query delete)") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << true; QTest::newRow("Error with collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << false; QTest::newRow("Error with collection list (+ query delete)") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << true; } void shouldNotCrashDuringFindChildrenWhenFetchJobFailedOrEmpty() { // GIVEN AkonadiFakeData data; // One top level collection with two children data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43TaskFirstChild")).withParent(42).withTaskContent()); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44NoteSecondChild")).withParent(42).withNoteContent()); // Serializer auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); Domain::DataSource::Ptr topLevelDataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); QFETCH(bool, deleteQuery); QFETCH(int, colErrorCode); QFETCH(int, colFetchBehavior); data.storageBehavior().setFetchCollectionsErrorCode(data.collection(42).id(), colErrorCode); data.storageBehavior().setFetchCollectionsBehavior(data.collection(42).id(), AkonadiFakeStorageBehavior::FetchBehavior(colFetchBehavior)); // WHEN QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult::Ptr result = queries->findChildren(topLevelDataSource); if (deleteQuery) delete queries.take(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 0); } - - void shouldLookInAllReportedForSearchTopLevelSources_data() - { - QTest::addColumn("contentTypes"); - QTest::addColumn("expectedColNames"); - QTest::addColumn("expectedTitiNames"); - - auto expectedColNames = QStringList(); - auto expectedTitiNames = QStringList(); - expectedColNames << QStringLiteral("TaskToto") << QStringLiteral("NoteCol"); - expectedTitiNames << QStringLiteral("NoteTiti") << QStringLiteral("TaskTiti"); - QTest::newRow("tasks and notes") << int(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes) - << expectedColNames << expectedTitiNames; - - expectedColNames.clear(); - expectedTitiNames.clear(); - expectedColNames << QStringLiteral("TaskToto"); - expectedTitiNames << QStringLiteral("TaskTiti"); - QTest::newRow("tasks") << int(Akonadi::StorageInterface::Tasks) - << expectedColNames << expectedTitiNames; - - expectedColNames.clear(); - expectedTitiNames.clear(); - expectedColNames << QStringLiteral("NoteCol"); - expectedTitiNames << QStringLiteral("NoteTiti"); - QTest::newRow("notes") << int(Akonadi::StorageInterface::Notes) - << expectedColNames << expectedTitiNames; - } - - void shouldLookInAllReportedForSearchTopLevelSources() - { - // GIVEN - AkonadiFakeData data; - - // Four top level collections, two with tasks, two with notes - data.createCollection(GenCollection().withId(42).withName(QStringLiteral("TaskToto")).withRootAsParent().withTaskContent()); - data.createCollection(GenCollection().withId(43).withName(QStringLiteral("NoteTiti")).withRootAsParent().withNoteContent()); - data.createCollection(GenCollection().withId(44).withName(QStringLiteral("TaskTiti")).withRootAsParent().withTaskContent()); - data.createCollection(GenCollection().withId(46).withName(QStringLiteral("NoteCol")).withRootAsParent().withNoteContent()); - data.createCollection(GenCollection().withId(47).withName(QStringLiteral("NoCol")).withRootAsParent()); - - // One child collection with tasks - data.createCollection(GenCollection().withId(45).withName(QStringLiteral("TaskTotoCol")).withParent(42).withTaskContent()); - - QString searchTerm(QStringLiteral("Col")); - - // WHEN - QFETCH(int, contentTypes); - QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::FetchContentTypes(contentTypes), - Akonadi::StorageInterface::Ptr(data.createStorage()), - Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - - queries->setSearchTerm(searchTerm); - Domain::QueryResult::Ptr result = queries->findSearchTopLevel(); - result->data(); - result = queries->findSearchTopLevel(); // Should not cause any problem or wrong data - - // THEN - QVERIFY(result->data().isEmpty()); - TestHelpers::waitForEmptyJobQueue(); - - auto sources = result->data(); - auto actualNames = QStringList(); - std::transform(sources.constBegin(), sources.constEnd(), - std::back_inserter(actualNames), - [] (const Domain::DataSource::Ptr &source) { return source->name(); }); - actualNames.sort(); - - QFETCH(QStringList, expectedColNames); - expectedColNames.sort(); - QCOMPARE(actualNames, expectedColNames); - - // WHEN - QString searchTerm2(QStringLiteral("Titi")); - queries->setSearchTerm(searchTerm2); - - // THEN - TestHelpers::waitForEmptyJobQueue(); - - sources = result->data(); - actualNames.clear(); - std::transform(sources.constBegin(), sources.constEnd(), - std::back_inserter(actualNames), - [] (const Domain::DataSource::Ptr &source) { return source->name(); }); - actualNames.sort(); - - QFETCH(QStringList, expectedTitiNames); - expectedTitiNames.sort(); - QCOMPARE(actualNames, expectedTitiNames); - } - - void shouldReactToCollectionAddsForSearchTopLevelSources() - { - // GIVEN - AkonadiFakeData data; - - QString searchTerm(QStringLiteral("col")); - - QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, - Akonadi::StorageInterface::Ptr(data.createStorage()), - Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - queries->setSearchTerm(searchTerm); - Domain::QueryResult::Ptr result = queries->findSearchTopLevel(); - TestHelpers::waitForEmptyJobQueue(); - QVERIFY(result->data().isEmpty()); - - // WHEN - data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); - - // THEN - QCOMPARE(result->data().size(), 1); - QCOMPARE(result->data().first()->name(), QStringLiteral("42Task")); - } - - void shouldReactToCollectionRemovesForSearchTopLevelSources() - { - // GIVEN - AkonadiFakeData data; - - // Two top level collections - data.createCollection(GenCollection().withId(42).withName(QStringLiteral("TaskToto")).withRootAsParent().withTaskContent()); - data.createCollection(GenCollection().withId(43).withName(QStringLiteral("NoteToto")).withRootAsParent().withNoteContent()); - - QString searchTerm(QStringLiteral("Toto")); - - QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, - Akonadi::StorageInterface::Ptr(data.createStorage()), - Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - queries->setSearchTerm(searchTerm); - Domain::QueryResult::Ptr result = queries->findSearchTopLevel(); - TestHelpers::waitForEmptyJobQueue(); - QCOMPARE(result->data().size(), 2); - - // WHEN - data.removeCollection(Akonadi::Collection(42)); - - // THEN - QCOMPARE(result->data().size(), 1); - QCOMPARE(result->data().first()->name(), QStringLiteral("NoteToto")); - } - - void shouldReactToItemChangesForSearchTopLevelTasks() - { - // GIVEN - AkonadiFakeData data; - - // Two top level collections - data.createCollection(GenCollection().withId(42).withName(QStringLiteral("TaskCol1")).withRootAsParent().withTaskContent()); - data.createCollection(GenCollection().withId(43).withName(QStringLiteral("NoteCol2")).withRootAsParent().withNoteContent()); - - // One child collection - data.createCollection(GenCollection().withId(44).withName(QStringLiteral("NoteCol2Child")).withParent(43).withNoteContent()); - - QString searchTerm(QStringLiteral("Col")); - - // WHEN - QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, - Akonadi::StorageInterface::Ptr(data.createStorage()), - Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - queries->setSearchTerm(searchTerm); - Domain::QueryResult::Ptr result = queries->findSearchTopLevel(); - bool replaceHandlerCalled = false; - result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::DataSource::Ptr &, int) { - replaceHandlerCalled = true; - }); - TestHelpers::waitForEmptyJobQueue(); - QCOMPARE(result->data().size(), 2); - - // WHEN - data.modifyCollection(GenCollection(data.collection(43)).withName(QStringLiteral("NoteCol2Bis"))); - data.modifyCollection(GenCollection(data.collection(44)).withName(QStringLiteral("NoteCol2ChildBis"))); - - // THEN - QCOMPARE(result->data().size(), 2); - QCOMPARE(result->data().at(0)->name(), QStringLiteral("TaskCol1")); - QCOMPARE(result->data().at(1)->name(), QStringLiteral("NoteCol2Bis")); - QVERIFY(replaceHandlerCalled); - } - - void shouldNotCrashDuringFindSearchTopLevelWhenFetchJobFailedOrEmpty_data() - { - QTest::addColumn("colErrorCode"); - QTest::addColumn("colFetchBehavior"); - QTest::addColumn("deleteQuery"); - - QTest::newRow("No error with empty collection list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) - << false; - - QTest::newRow("No error with empty collection list (+ query delete)") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) - << true; - - QTest::newRow("Error with empty collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) - << false; - - QTest::newRow("Error with empty collection list (+ query delete)") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) - << true; - - QTest::newRow("Error with collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) - << false; - - QTest::newRow("Error with collection list (+ query delete)") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) - << true; - } - - void shouldNotCrashDuringFindSearchTopLevelWhenFetchJobFailedOrEmpty() - { - // GIVEN - AkonadiFakeData data; - - // Two top level collections - data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); - data.createCollection(GenCollection().withId(43).withRootAsParent().withNoteContent()); - - - auto storage = Akonadi::StorageInterface::Ptr(data.createStorage()); - auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); - auto monitor = Akonadi::MonitorInterface::Ptr(data.createMonitor()); - - QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, - storage, - serializer, - monitor)); - - QFETCH(bool, deleteQuery); - QFETCH(int, colErrorCode); - QFETCH(int, colFetchBehavior); - data.storageBehavior().setFetchCollectionsErrorCode(Akonadi::Collection::root().id(), colErrorCode); - data.storageBehavior().setFetchCollectionsBehavior(Akonadi::Collection::root().id(), - AkonadiFakeStorageBehavior::FetchBehavior(colFetchBehavior)); - - // WHEN - Domain::QueryResult::Ptr result = queries->findSearchTopLevel(); - - if (deleteQuery) - delete queries.take(); - - // THEN - QVERIFY(result->data().isEmpty()); - TestHelpers::waitForEmptyJobQueue(); - QCOMPARE(result->data().size(), 0); - } - - void shouldNotStartJobDuringFindSearchTopLevelWhenSearchTermIsEmpty() - { - // GIVEN - AkonadiFakeData data; - - // one top level collection - data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("parent"))); - - // WHEN - QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, - Akonadi::StorageInterface::Ptr(data.createStorage()), - Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - - Domain::QueryResult::Ptr result = queries->findSearchTopLevel(); - result->data(); - result = queries->findSearchTopLevel(); // Should not cause any problem or wrong data - - // THEN - QVERIFY(result->data().isEmpty()); - QCOMPARE(Utils::JobHandler::jobCount(), 0); - } - - void shouldLookInAllReportedForSearchChildSources_data() - { - QTest::addColumn("contentTypes"); - QTest::addColumn("expectedColNames"); - QTest::addColumn("expectedTotoNames"); - - auto expectedColNames = QStringList(); - auto expectedTotoNames = QStringList(); - expectedColNames << QStringLiteral("NoteCol1"); - expectedTotoNames << QStringLiteral("TaskToto"); - QTest::newRow("tasks and notes") << int(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes) - << expectedColNames << expectedTotoNames; - - expectedColNames.clear(); - expectedTotoNames.clear(); - expectedColNames << QStringLiteral("NoteCol1"); - expectedTotoNames << QStringLiteral("TaskToto"); - QTest::newRow("tasks") << int(Akonadi::StorageInterface::Tasks) - << expectedColNames << expectedTotoNames; - - expectedColNames.clear(); - expectedTotoNames.clear(); - expectedColNames << QStringLiteral("NoteCol1"); - QTest::newRow("notes") << int(Akonadi::StorageInterface::Notes) - << expectedColNames << expectedTotoNames; - } - - void shouldLookInAllReportedForSearchChildSources() - { - // GIVEN - AkonadiFakeData data; - - // Two top level collections - data.createCollection(GenCollection().withId(42).withName(QStringLiteral("parent")).withRootAsParent().withTaskContent()); - data.createCollection(GenCollection().withId(46).withName(QStringLiteral("Col46")).withRootAsParent().withNoteContent()); - - // two child of parent - data.createCollection(GenCollection().withId(43).withName(QStringLiteral("NoteCol1")).withParent(42).withNoteContent()); - data.createCollection(GenCollection().withId(44).withName(QStringLiteral("TaskToto")).withParent(42).withTaskContent()); - data.createCollection(GenCollection().withId(47).withName(QStringLiteral("NoCol")).withParent(42)); - - // One child of the first child - data.createCollection(GenCollection().withId(45).withName(QStringLiteral("TaskCol43Child")).withParent(43).withTaskContent()); - - QString searchTerm(QStringLiteral("Col")); - - auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); - Domain::DataSource::Ptr parentSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); - - // WHEN - QFETCH(int, contentTypes); - QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::FetchContentTypes(contentTypes), - Akonadi::StorageInterface::Ptr(data.createStorage()), - serializer, - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - - queries->setSearchTerm(searchTerm); - Domain::QueryResult::Ptr result = queries->findSearchChildren(parentSource); - result->data(); - result = queries->findSearchChildren(parentSource); // Should not cause any problem or wrong data - - // THEN - QVERIFY(result->data().isEmpty()); - TestHelpers::waitForEmptyJobQueue(); - - auto sources = result->data(); - auto actualNames = QStringList(); - std::transform(sources.constBegin(), sources.constEnd(), - std::back_inserter(actualNames), - [] (const Domain::DataSource::Ptr &source) { return source->name(); }); - actualNames.sort(); - - QFETCH(QStringList, expectedColNames); - expectedColNames.sort(); - QCOMPARE(actualNames, expectedColNames); - - // WHEN - QString searchTerm2(QStringLiteral("toto")); - queries->setSearchTerm(searchTerm2); - - // THEN - TestHelpers::waitForEmptyJobQueue(); - - sources = result->data(); - actualNames.clear(); - std::transform(sources.constBegin(), sources.constEnd(), - std::back_inserter(actualNames), - [] (const Domain::DataSource::Ptr &source) { return source->name(); }); - actualNames.sort(); - - QFETCH(QStringList, expectedTotoNames); - expectedTotoNames.sort(); - QCOMPARE(actualNames, expectedTotoNames); - } - - void shouldReactToCollectionAddsForSearchChildSources() - { - // GIVEN - AkonadiFakeData data; - - // One top level collections - data.createCollection(GenCollection().withId(42).withName(QStringLiteral("Col1")).withRootAsParent().withTaskContent()); - - QString searchTerm(QStringLiteral("Col")); - - // Serializer - auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); - Domain::DataSource::Ptr parentSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); - - QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, - Akonadi::StorageInterface::Ptr(data.createStorage()), - serializer, - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - - queries->setSearchTerm(searchTerm); - Domain::QueryResult::Ptr result = queries->findSearchChildren(parentSource); - TestHelpers::waitForEmptyJobQueue(); - QVERIFY(result->data().isEmpty()); - - // WHEN - data.createCollection(GenCollection().withId(43).withName(QStringLiteral("Col2")).withParent(42).withNoteContent()); - - // THEN - QCOMPARE(result->data().size(), 1); - QCOMPARE(result->data().first()->name(), QStringLiteral("Col2")); - } - - void shouldReactToCollectionRemovesForSearchChildSources() - { - // GIVEN - AkonadiFakeData data; - - // One top level collection - data.createCollection(GenCollection().withId(42).withName(QStringLiteral("parent")).withRootAsParent().withTaskContent()); - - // two children of parent - data.createCollection(GenCollection().withId(43).withName(QStringLiteral("NoteCol1")).withParent(42).withNoteContent()); - data.createCollection(GenCollection().withId(44).withName(QStringLiteral("TaskCol2")).withParent(42).withTaskContent()); - - QString searchTerm(QStringLiteral("Col")); - - auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); - Domain::DataSource::Ptr parentSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); - - QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, - Akonadi::StorageInterface::Ptr(data.createStorage()), - serializer, - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - - queries->setSearchTerm(searchTerm); - Domain::QueryResult::Ptr result = queries->findSearchChildren(parentSource); - TestHelpers::waitForEmptyJobQueue(); - QCOMPARE(result->data().size(), 2); - - // WHEN - data.removeCollection(Akonadi::Collection(43)); - - // THEN - QCOMPARE(result->data().size(), 1); - QCOMPARE(result->data().first()->name(), QStringLiteral("TaskCol2")); - } - - void shouldReactToCollectionChangesForSearchChildSources() - { - // GIVEN - AkonadiFakeData data; - - // One top level collection - data.createCollection(GenCollection().withId(42).withName(QStringLiteral("parent")).withRootAsParent().withTaskContent()); - - // two children - data.createCollection(GenCollection().withId(43).withName(QStringLiteral("NoteCol1")).withParent(42).withNoteContent()); - data.createCollection(GenCollection().withId(44).withName(QStringLiteral("TaskCol2")).withParent(42).withTaskContent()); - - QString searchTerm(QStringLiteral("Col")); - - auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); - Domain::DataSource::Ptr parentSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); - - // WHEN - QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, - Akonadi::StorageInterface::Ptr(data.createStorage()), - serializer, - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - queries->setSearchTerm(searchTerm); - Domain::QueryResult::Ptr result = queries->findSearchChildren(parentSource); - bool replaceHandlerCalled = false; - result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::DataSource::Ptr &, int) { - replaceHandlerCalled = true; - }); - - TestHelpers::waitForEmptyJobQueue(); - QCOMPARE(result->data().size(), 2); - - // WHEN - data.modifyCollection(GenCollection(data.collection(43)).withName(QStringLiteral("NoteCol1Bis"))); - - // THEN - QCOMPARE(result->data().size(), 2); - QCOMPARE(result->data().at(0)->name(), QStringLiteral("NoteCol1Bis")); - QCOMPARE(result->data().at(1)->name(), QStringLiteral("TaskCol2")); - QVERIFY(replaceHandlerCalled); - } - - void shouldNotCrashDuringFindSearchChildrenWhenFetchJobFailedOrEmpty_data() - { - QTest::addColumn("colErrorCode"); - QTest::addColumn("colFetchBehavior"); - QTest::addColumn("deleteQuery"); - - QTest::newRow("No error with empty collection list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) - << false; - - QTest::newRow("No error with empty collection list (+ query delete)") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) - << true; - - QTest::newRow("Error with empty collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) - << false; - - QTest::newRow("Error with empty collection list (+ query delete)") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) - << true; - - QTest::newRow("Error with collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) - << false; - - QTest::newRow("Error with collection list (+ query delete)") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) - << true; - } - - void shouldNotCrashDuringFindSearchChildrenWhenFetchJobFailedOrEmpty() - { - // GIVEN - AkonadiFakeData data; - - // One top level collection with two children - data.createCollection(GenCollection().withId(42).withName(QStringLiteral("parent")).withRootAsParent().withTaskContent()); - data.createCollection(GenCollection().withId(43).withName(QStringLiteral("TaskChild")).withParent(42).withTaskContent()); - data.createCollection(GenCollection().withId(44).withName(QStringLiteral("TaskChild")).withParent(42).withNoteContent()); - - auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); - Domain::DataSource::Ptr parentSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); - - QFETCH(bool, deleteQuery); - QFETCH(int, colErrorCode); - QFETCH(int, colFetchBehavior); - data.storageBehavior().setFetchCollectionsErrorCode(data.collection(42).id(), colErrorCode); - data.storageBehavior().setFetchCollectionsBehavior(data.collection(42).id(), - AkonadiFakeStorageBehavior::FetchBehavior(colFetchBehavior)); - - QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, - Akonadi::StorageInterface::Ptr(data.createStorage()), - serializer, - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - - Domain::QueryResult::Ptr result = queries->findSearchChildren(parentSource); - - if (deleteQuery) - delete queries.take(); - - // THEN - QVERIFY(result->data().isEmpty()); - TestHelpers::waitForEmptyJobQueue(); - QCOMPARE(result->data().size(), 0); - } - - void shouldNotStartJobDuringFindSearchChildrenWhenSearchTermIsEmpty() - { - // GIVEN - AkonadiFakeData data; - - // One top level collection and its child - data.createCollection(GenCollection().withId(42).withName(QStringLiteral("parent")).withRootAsParent().withTaskContent()); - data.createCollection(GenCollection().withId(43).withName(QStringLiteral("child")).withParent(42).withTaskContent()); - - auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); - Domain::DataSource::Ptr parentSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); - - // WHEN - QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, - Akonadi::StorageInterface::Ptr(data.createStorage()), - serializer, - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - - Domain::QueryResult::Ptr result = queries->findSearchChildren(parentSource); - - // THEN - QVERIFY(result->data().isEmpty()); - TestHelpers::waitForEmptyJobQueue(); - - QVERIFY(result->data().isEmpty()); - } }; ZANSHIN_TEST_MAIN(AkonadiDataSourceQueriesTest) #include "akonadidatasourcequeriestest.moc" diff --git a/tests/units/akonadi/akonadilivequeryhelperstest.cpp b/tests/units/akonadi/akonadilivequeryhelperstest.cpp index d08abc8f..17fe614f 100644 --- a/tests/units/akonadi/akonadilivequeryhelperstest.cpp +++ b/tests/units/akonadi/akonadilivequeryhelperstest.cpp @@ -1,822 +1,588 @@ /* This file is part of Zanshin Copyright 2015 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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 "akonadi/akonadilivequeryhelpers.h" #include #include #include #include #include "akonadi/akonadiserializer.h" #include "testlib/akonadifakedata.h" #include "testlib/gencollection.h" #include "testlib/gennote.h" #include "testlib/gentag.h" #include "testlib/gentodo.h" #include "testlib/testhelpers.h" using namespace Testlib; using namespace std::placeholders; Q_DECLARE_METATYPE(Akonadi::StorageInterface::FetchContentTypes) static QString titleFromItem(const Akonadi::Item &item) { if (item.hasPayload()) { const auto todo = item.payload(); return todo->summary(); } else if (item.hasPayload()) { const auto message = item.payload(); const Akonadi::NoteUtils::NoteMessageWrapper note(message); return note.title(); } else { return QString(); } } class AkonadiLiveQueryHelpersTest : public QObject { Q_OBJECT private: Akonadi::LiveQueryHelpers::Ptr createHelpers(AkonadiFakeData &data) { return Akonadi::LiveQueryHelpers::Ptr(new Akonadi::LiveQueryHelpers(createSerializer(), createStorage(data))); } Akonadi::StorageInterface::Ptr createStorage(AkonadiFakeData &data) { return Akonadi::StorageInterface::Ptr(data.createStorage()); } Akonadi::SerializerInterface::Ptr createSerializer() { return Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer); } private slots: void shouldFetchAllCollectionsForType_data() { QTest::addColumn("contentTypes"); QTest::newRow("task collections only") << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Tasks); QTest::newRow("note collections only") << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Notes); QTest::newRow("task and note collections only") << (Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes); QTest::newRow("all collections") << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::AllContent); } void shouldFetchAllCollectionsForType() { // GIVEN auto data = AkonadiFakeData(); auto helpers = createHelpers(data); // Three top level collections (any content, tasks and notes) data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42"))); data.createCollection(GenCollection().withId(43).withRootAsParent().withName(QStringLiteral("43")).withTaskContent()); data.createCollection(GenCollection().withId(44).withRootAsParent().withName(QStringLiteral("44")).withNoteContent()); // Three children under each of the top level for each content type data.createCollection(GenCollection().withId(45).withParent(42).withName(QStringLiteral("45"))); data.createCollection(GenCollection().withId(46).withParent(42).withName(QStringLiteral("46")).withTaskContent()); data.createCollection(GenCollection().withId(47).withParent(42).withName(QStringLiteral("47")).withNoteContent()); data.createCollection(GenCollection().withId(48).withParent(43).withName(QStringLiteral("48"))); data.createCollection(GenCollection().withId(49).withParent(43).withName(QStringLiteral("49")).withTaskContent()); data.createCollection(GenCollection().withId(50).withParent(43).withName(QStringLiteral("50")).withNoteContent()); data.createCollection(GenCollection().withId(51).withParent(44).withName(QStringLiteral("51"))); data.createCollection(GenCollection().withId(52).withParent(44).withName(QStringLiteral("52")).withTaskContent()); data.createCollection(GenCollection().withId(53).withParent(44).withName(QStringLiteral("53")).withNoteContent()); // The list which will be filled by the fetch function auto collections = Akonadi::Collection::List(); auto add = [&collections] (const Akonadi::Collection &collection) { collections.append(collection); }; // WHEN QFETCH(Akonadi::StorageInterface::FetchContentTypes, contentTypes); auto fetch = helpers->fetchAllCollections(contentTypes); fetch(add); TestHelpers::waitForEmptyJobQueue(); auto result = QStringList(); std::transform(collections.constBegin(), collections.constEnd(), std::back_inserter(result), std::bind(&Akonadi::Collection::displayName, _1)); result.sort(); // THEN auto expected = QStringList(); if (contentTypes == Akonadi::StorageInterface::AllContent) { expected << QStringLiteral("42") << QStringLiteral("45") << QStringLiteral("48") << QStringLiteral("51"); } if ((contentTypes & Akonadi::StorageInterface::Tasks) || contentTypes == Akonadi::StorageInterface::AllContent) { expected << QStringLiteral("43") << QStringLiteral("46") << QStringLiteral("49") << QStringLiteral("52"); } if ((contentTypes & Akonadi::StorageInterface::Notes) || contentTypes == Akonadi::StorageInterface::AllContent) { expected << QStringLiteral("44") << QStringLiteral("47") << QStringLiteral("50") << QStringLiteral("53"); } expected.sort(); QCOMPARE(result, expected); // WHEN (should not crash when the helpers object is deleted) helpers.clear(); collections.clear(); fetch(add); TestHelpers::waitForEmptyJobQueue(); // THEN result.clear(); std::transform(collections.constBegin(), collections.constEnd(), std::back_inserter(result), std::bind(&Akonadi::Collection::displayName, _1)); result.sort(); QCOMPARE(result, expected); } void shouldFetchCollectionsForRootAndType_data() { QTest::addColumn("root"); QTest::addColumn("contentTypes"); QTest::newRow("task collections only from root") << Akonadi::Collection::root() << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Tasks); QTest::newRow("note collections only from root") << Akonadi::Collection::root() << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Notes); QTest::newRow("task and note collections only from root") << Akonadi::Collection::root() << (Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes); QTest::newRow("all collections from root") << Akonadi::Collection::root() << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::AllContent); QTest::newRow("task collections only from 'all branch'") << Akonadi::Collection(42) << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Tasks); QTest::newRow("note collections only from 'all branch'") << Akonadi::Collection(42) << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Notes); QTest::newRow("task and note collections only from 'all branch'") << Akonadi::Collection(42) << (Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes); QTest::newRow("all collections from 'all branch'") << Akonadi::Collection(42) << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::AllContent); QTest::newRow("task collections only from 'task branch'") << Akonadi::Collection(43) << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Tasks); QTest::newRow("note collections only from 'task branch'") << Akonadi::Collection(43) << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Notes); QTest::newRow("task and note collections only from 'task branch'") << Akonadi::Collection(43) << (Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes); QTest::newRow("all collections from 'task branch'") << Akonadi::Collection(43) << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::AllContent); QTest::newRow("task collections only from 'note branch'") << Akonadi::Collection(44) << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Tasks); QTest::newRow("note collections only from 'note branch'") << Akonadi::Collection(44) << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Notes); QTest::newRow("task and note collections only from 'note branch'") << Akonadi::Collection(44) << (Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes); QTest::newRow("all collections from 'note branch'") << Akonadi::Collection(44) << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::AllContent); } void shouldFetchCollectionsForRootAndType() { // GIVEN auto data = AkonadiFakeData(); auto helpers = createHelpers(data); // Three top level collections (any content, tasks and notes) data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42"))); data.createCollection(GenCollection().withId(43).withRootAsParent().withName(QStringLiteral("43")).withTaskContent()); data.createCollection(GenCollection().withId(44).withRootAsParent().withName(QStringLiteral("44")).withNoteContent()); // Three children under each of the top level for each content type data.createCollection(GenCollection().withId(45).withParent(42).withName(QStringLiteral("45"))); data.createCollection(GenCollection().withId(46).withParent(42).withName(QStringLiteral("46")).withTaskContent()); data.createCollection(GenCollection().withId(47).withParent(42).withName(QStringLiteral("47")).withNoteContent()); data.createCollection(GenCollection().withId(48).withParent(43).withName(QStringLiteral("48"))); data.createCollection(GenCollection().withId(49).withParent(43).withName(QStringLiteral("49")).withTaskContent()); data.createCollection(GenCollection().withId(50).withParent(43).withName(QStringLiteral("50")).withNoteContent()); data.createCollection(GenCollection().withId(51).withParent(44).withName(QStringLiteral("51"))); data.createCollection(GenCollection().withId(52).withParent(44).withName(QStringLiteral("52")).withTaskContent()); data.createCollection(GenCollection().withId(53).withParent(44).withName(QStringLiteral("53")).withNoteContent()); // The list which will be filled by the fetch function auto collections = Akonadi::Collection::List(); auto add = [&collections] (const Akonadi::Collection &collection) { collections.append(collection); }; // WHEN QFETCH(Akonadi::Collection, root); QFETCH(Akonadi::StorageInterface::FetchContentTypes, contentTypes); auto fetch = helpers->fetchCollections(root, contentTypes); fetch(add); TestHelpers::waitForEmptyJobQueue(); auto result = QStringList(); std::transform(collections.constBegin(), collections.constEnd(), std::back_inserter(result), std::bind(&Akonadi::Collection::displayName, _1)); result.sort(); // THEN auto expected = QStringList(); if (root == Akonadi::Collection::root()) { expected << QStringLiteral("42") << QStringLiteral("43") << QStringLiteral("44"); } else { const qint64 baseId = root.id() == 42 ? 45 : root.id() == 43 ? 48 : root.id() == 44 ? 51 : -1; QVERIFY(baseId > 0); if (contentTypes == Akonadi::StorageInterface::AllContent) { expected << QString::number(baseId); } if ((contentTypes & Akonadi::StorageInterface::Tasks) || contentTypes == Akonadi::StorageInterface::AllContent) { expected << QString::number(baseId + 1); } if ((contentTypes & Akonadi::StorageInterface::Notes) || contentTypes == Akonadi::StorageInterface::AllContent) { expected << QString::number(baseId + 2); } } expected.sort(); QCOMPARE(result, expected); // WHEN (should not crash when the helpers object is deleted) helpers.clear(); collections.clear(); fetch(add); TestHelpers::waitForEmptyJobQueue(); // THEN result.clear(); std::transform(collections.constBegin(), collections.constEnd(), std::back_inserter(result), std::bind(&Akonadi::Collection::displayName, _1)); result.sort(); QCOMPARE(result, expected); } - void shouldSearchCollectionsForRootAndTerm_data() - { - QTest::addColumn("root"); - - QTest::newRow("search from root") << Akonadi::Collection::root(); - QTest::newRow("search from no content branch") << Akonadi::Collection(42); - QTest::newRow("search from in branch") << Akonadi::Collection(43); - QTest::newRow("search from ex branch") << Akonadi::Collection(44); - } - - void shouldSearchCollectionsForRootAndTerm() - { - // GIVEN - auto data = AkonadiFakeData(); - auto helpers = createHelpers(data); - - // Three top level collections (any content, tasks and notes) - data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42in"))); - data.createCollection(GenCollection().withId(43).withRootAsParent().withName(QStringLiteral("43in")).withTaskContent()); - data.createCollection(GenCollection().withId(44).withRootAsParent().withName(QStringLiteral("44ex")).withNoteContent()); - - // Three children under each of the top level for each content type - data.createCollection(GenCollection().withId(45).withParent(42).withName(QStringLiteral("45in"))); - data.createCollection(GenCollection().withId(46).withParent(42).withName(QStringLiteral("46in")).withNoteContent()); - data.createCollection(GenCollection().withId(47).withParent(42).withName(QStringLiteral("47ex")).withTaskContent()); - data.createCollection(GenCollection().withId(48).withParent(43).withName(QStringLiteral("48in"))); - data.createCollection(GenCollection().withId(49).withParent(43).withName(QStringLiteral("49in")).withTaskContent()); - data.createCollection(GenCollection().withId(50).withParent(43).withName(QStringLiteral("50ex")).withNoteContent()); - data.createCollection(GenCollection().withId(51).withParent(44).withName(QStringLiteral("51in"))); - data.createCollection(GenCollection().withId(52).withParent(44).withName(QStringLiteral("52in")).withTaskContent()); - data.createCollection(GenCollection().withId(53).withParent(44).withName(QStringLiteral("53ex")).withNoteContent()); - - // The list which will be filled by the fetch function - auto collections = Akonadi::Collection::List(); - auto add = [&collections] (const Akonadi::Collection &collection) { - collections.append(collection); - }; - - // WHEN (no search term) - QFETCH(Akonadi::Collection, root); - auto term = QString(); - auto fetch = helpers->searchCollections(root, &term, Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes); - fetch(add); - TestHelpers::waitForEmptyJobQueue(); - - auto result = QStringList(); - std::transform(collections.constBegin(), collections.constEnd(), - std::back_inserter(result), - std::bind(&Akonadi::Collection::displayName, _1)); - result.sort(); - - // THEN - auto expected = QStringList(); - QCOMPARE(result, expected); - - - // WHEN ("in" search term) - collections.clear(); - result.clear(); - expected.clear(); - - term = QStringLiteral("in"); - fetch(add); - TestHelpers::waitForEmptyJobQueue(); - - std::transform(collections.constBegin(), collections.constEnd(), - std::back_inserter(result), - std::bind(&Akonadi::Collection::displayName, _1)); - result.sort(); - - // THEN - if (root == Akonadi::Collection::root()) { - expected << QStringLiteral("42in") << QStringLiteral("43in") << QStringLiteral("44ex"); - } else { - switch (root.id()) { - case 42: - expected << QStringLiteral("46in"); - break; - case 43: - expected << QStringLiteral("49in"); - break; - case 44: - expected << QStringLiteral("52in"); - break; - } - - QVERIFY(!expected.isEmpty()); - } - - expected.sort(); - QCOMPARE(result, expected); - - - // WHEN ("ex" search term) - collections.clear(); - result.clear(); - expected.clear(); - - term = QStringLiteral("ex"); - fetch(add); - TestHelpers::waitForEmptyJobQueue(); - - std::transform(collections.constBegin(), collections.constEnd(), - std::back_inserter(result), - std::bind(&Akonadi::Collection::displayName, _1)); - result.sort(); - - // THEN - if (root == Akonadi::Collection::root()) { - expected << QStringLiteral("42in") << QStringLiteral("43in") << QStringLiteral("44ex"); - } else { - switch (root.id()) { - case 42: - expected << QStringLiteral("47ex"); - break; - case 43: - expected << QStringLiteral("50ex"); - break; - case 44: - expected << QStringLiteral("53ex"); - break; - } - - QVERIFY(!expected.isEmpty()); - } - - expected.sort(); - QCOMPARE(result, expected); - - // WHEN (should not crash when the helpers object is deleted) - helpers.clear(); - collections.clear(); - fetch(add); - TestHelpers::waitForEmptyJobQueue(); - - // THEN - result.clear(); - std::transform(collections.constBegin(), collections.constEnd(), - std::back_inserter(result), - std::bind(&Akonadi::Collection::displayName, _1)); - result.sort(); - QCOMPARE(result, expected); - } - - void shouldSearchCollectionsForContentTypes_data() - { - QTest::addColumn("contentTypes"); - QTest::addColumn("expected"); - - auto expected = QStringList(); - expected << QStringLiteral("42-foo-all") << QStringLiteral("43-foo-task") << QStringLiteral("44-foo-note"); - QTest::newRow("all") << int(Akonadi::StorageInterface::AllContent) << expected; - - expected.clear(); - expected << QStringLiteral("43-foo-task") << QStringLiteral("44-foo-note"); - QTest::newRow("task + note") << int(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes) << expected; - - expected.clear(); - expected << QStringLiteral("43-foo-task"); - QTest::newRow("task") << int(Akonadi::StorageInterface::Tasks) << expected; - - expected.clear(); - expected << QStringLiteral("44-foo-note"); - QTest::newRow("note") << int(Akonadi::StorageInterface::Notes) << expected; - } - - void shouldSearchCollectionsForContentTypes() - { - // GIVEN - auto data = AkonadiFakeData(); - auto helpers = createHelpers(data); - - // Three top level collections (any content, tasks and notes) - data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42-foo-all"))); - data.createCollection(GenCollection().withId(43).withRootAsParent().withName(QStringLiteral("43-foo-task")).withTaskContent()); - data.createCollection(GenCollection().withId(44).withRootAsParent().withName(QStringLiteral("44-foo-note")).withNoteContent()); - - // The list which will be filled by the fetch function - auto collections = Akonadi::Collection::List(); - auto add = [&collections] (const Akonadi::Collection &collection) { - collections.append(collection); - }; - - // WHEN (no search term) - QFETCH(int, contentTypes); - auto term = QString(); - auto fetch = helpers->searchCollections(Akonadi::Collection::root(), &term, - Akonadi::StorageInterface::FetchContentTypes(contentTypes)); - fetch(add); - TestHelpers::waitForEmptyJobQueue(); - - auto result = QStringList(); - std::transform(collections.constBegin(), collections.constEnd(), - std::back_inserter(result), - std::bind(&Akonadi::Collection::displayName, _1)); - result.sort(); - - // THEN - QCOMPARE(result, QStringList()); - - - // WHEN ("foo" search term) - collections.clear(); - result.clear(); - - term = QStringLiteral("foo"); - fetch(add); - TestHelpers::waitForEmptyJobQueue(); - - std::transform(collections.constBegin(), collections.constEnd(), - std::back_inserter(result), - std::bind(&Akonadi::Collection::displayName, _1)); - result.sort(); - - // THEN - QFETCH(QStringList, expected); - expected.sort(); - QCOMPARE(result, expected); - - // WHEN (should not crash when the helpers object is deleted) - helpers.clear(); - collections.clear(); - fetch(add); - TestHelpers::waitForEmptyJobQueue(); - - // THEN - result.clear(); - std::transform(collections.constBegin(), collections.constEnd(), - std::back_inserter(result), - std::bind(&Akonadi::Collection::displayName, _1)); - result.sort(); - QCOMPARE(result, expected); - } - void shouldFetchItemsByContentTypes_data() { QTest::addColumn("contentTypes"); QTest::newRow("task collections only") << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Tasks); QTest::newRow("note collections only") << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Notes); QTest::newRow("task and note collections only") << (Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes); QTest::newRow("all collections") << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::AllContent); } void shouldFetchItemsByContentTypes() { // GIVEN auto data = AkonadiFakeData(); auto helpers = createHelpers(data); // Two top level collections, one with no particular content, one with tasks data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42"))); data.createCollection(GenCollection().withId(43).withRootAsParent().withName(QStringLiteral("43")).withTaskContent()); // One note collection as child of the first one data.createCollection(GenCollection().withId(44).withParent(42).withName(QStringLiteral("44")).withNoteContent()); // One task collection as child of the note collection data.createCollection(GenCollection().withId(45).withParent(44).withName(QStringLiteral("45")).withTaskContent()); // One task in the first collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); // Two items (tasks or notes) in all the other collections data.createItem(GenTodo().withId(43).withParent(43).withTitle(QStringLiteral("43"))); data.createItem(GenTodo().withId(44).withParent(43).withTitle(QStringLiteral("44"))); data.createItem(GenNote().withId(45).withParent(44).withTitle(QStringLiteral("45"))); data.createItem(GenNote().withId(46).withParent(44).withTitle(QStringLiteral("46"))); data.createItem(GenTodo().withId(47).withParent(45).withTitle(QStringLiteral("47"))); data.createItem(GenTodo().withId(48).withParent(45).withTitle(QStringLiteral("48"))); // The list which will be filled by the fetch function auto items = Akonadi::Item::List(); auto add = [&items] (const Akonadi::Item &item) { items.append(item); }; // WHEN QFETCH(Akonadi::StorageInterface::FetchContentTypes, contentTypes); auto fetch = helpers->fetchItems(contentTypes); fetch(add); TestHelpers::waitForEmptyJobQueue(); auto result = QStringList(); std::transform(items.constBegin(), items.constEnd(), std::back_inserter(result), titleFromItem); result.sort(); // THEN auto expected = QStringList(); if ((contentTypes & Akonadi::StorageInterface::Tasks) || contentTypes == Akonadi::StorageInterface::AllContent) { expected << QStringLiteral("43") << QStringLiteral("44") << QStringLiteral("47") << QStringLiteral("48"); } if ((contentTypes & Akonadi::StorageInterface::Notes) || contentTypes == Akonadi::StorageInterface::AllContent) { expected << QStringLiteral("45") << QStringLiteral("46"); } expected.sort(); QCOMPARE(result, expected); // WHEN (should not crash when the helpers object is deleted) helpers.clear(); items.clear(); fetch(add); TestHelpers::waitForEmptyJobQueue(); // THEN result.clear(); std::transform(items.constBegin(), items.constEnd(), std::back_inserter(result), titleFromItem); result.sort(); QCOMPARE(result, expected); } void shouldFetchItemsByTag_data() { QTest::addColumn("tag"); QTest::newRow("first tag") << Akonadi::Tag(42); QTest::newRow("second tag") << Akonadi::Tag(43); } void shouldFetchItemsByTag() { // GIVEN auto data = AkonadiFakeData(); auto helpers = createHelpers(data); // Two top level collections with tasks data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42")).withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withName(QStringLiteral("43")).withTaskContent()); // Two tags data.createTag(GenTag().withId(42)); data.createTag(GenTag().withId(43)); // Four items in each collection, one with no tag, one with the first tag, // one with the second tag, last one with both tags data.createItem(GenTodo().withId(42).withParent(42).withTags({}).withTitle(QStringLiteral("42"))); data.createItem(GenTodo().withId(43).withParent(42).withTags({42}).withTitle(QStringLiteral("43"))); data.createItem(GenTodo().withId(44).withParent(42).withTags({43}).withTitle(QStringLiteral("44"))); data.createItem(GenTodo().withId(45).withParent(42).withTags({42, 43}).withTitle(QStringLiteral("45"))); data.createItem(GenTodo().withId(46).withParent(43).withTags({}).withTitle(QStringLiteral("46"))); data.createItem(GenTodo().withId(47).withParent(43).withTags({42}).withTitle(QStringLiteral("47"))); data.createItem(GenTodo().withId(48).withParent(43).withTags({43}).withTitle(QStringLiteral("48"))); data.createItem(GenTodo().withId(49).withParent(43).withTags({42, 43}).withTitle(QStringLiteral("49"))); // The list which will be filled by the fetch function auto items = Akonadi::Item::List(); auto add = [&items] (const Akonadi::Item &item) { items.append(item); }; // WHEN QFETCH(Akonadi::Tag, tag); auto fetch = helpers->fetchItems(tag); fetch(add); TestHelpers::waitForEmptyJobQueue(); auto result = QStringList(); std::transform(items.constBegin(), items.constEnd(), std::back_inserter(result), titleFromItem); result.sort(); // THEN auto expected = QStringList(); switch (tag.id()) { case 42: expected << QStringLiteral("43") << QStringLiteral("45") << QStringLiteral("47") << QStringLiteral("49"); break; case 43: expected << QStringLiteral("44") << QStringLiteral("45") << QStringLiteral("48") << QStringLiteral("49"); break; } QVERIFY(!expected.isEmpty()); expected.sort(); QCOMPARE(result, expected); // WHEN (should not crash when the helpers object is deleted) helpers.clear(); items.clear(); fetch(add); TestHelpers::waitForEmptyJobQueue(); // THEN result.clear(); std::transform(items.constBegin(), items.constEnd(), std::back_inserter(result), titleFromItem); result.sort(); QCOMPARE(result, expected); } void shouldFetchSiblings_data() { QTest::addColumn("item"); QTest::newRow("item in first collection") << Akonadi::Item(43); QTest::newRow("item in second collection") << Akonadi::Item(48); } void shouldFetchSiblings() { // GIVEN auto data = AkonadiFakeData(); auto helpers = createHelpers(data); // Two top level collections (one with notes, one with tasks) data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42")).withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withName(QStringLiteral("43")).withNoteContent()); // Four items in each collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43"))); data.createItem(GenTodo().withId(44).withParent(42).withTitle(QStringLiteral("44"))); data.createItem(GenTodo().withId(45).withParent(42).withTitle(QStringLiteral("45"))); data.createItem(GenNote().withId(46).withParent(43).withTitle(QStringLiteral("46"))); data.createItem(GenNote().withId(47).withParent(43).withTitle(QStringLiteral("47"))); data.createItem(GenNote().withId(48).withParent(43).withTitle(QStringLiteral("48"))); data.createItem(GenNote().withId(49).withParent(43).withTitle(QStringLiteral("49"))); // The list which will be filled by the fetch function auto items = Akonadi::Item::List(); auto add = [&items] (const Akonadi::Item &item) { items.append(item); }; // WHEN QFETCH(Akonadi::Item, item); auto fetch = helpers->fetchSiblings(item); fetch(add); TestHelpers::waitForEmptyJobQueue(); auto result = QStringList(); std::transform(items.constBegin(), items.constEnd(), std::back_inserter(result), titleFromItem); result.sort(); // THEN auto expected = QStringList(); switch (item.id()) { case 43: expected << QStringLiteral("42") << QStringLiteral("43") << QStringLiteral("44") << QStringLiteral("45"); break; case 48: expected << QStringLiteral("46") << QStringLiteral("47") << QStringLiteral("48") << QStringLiteral("49"); break; } QVERIFY(!expected.isEmpty()); expected.sort(); QCOMPARE(result, expected); // WHEN (should not crash when the helpers object is deleted) helpers.clear(); items.clear(); fetch(add); TestHelpers::waitForEmptyJobQueue(); // THEN result.clear(); std::transform(items.constBegin(), items.constEnd(), std::back_inserter(result), titleFromItem); result.sort(); QCOMPARE(result, expected); } void shouldFetchTags() { // GIVEN auto data = AkonadiFakeData(); auto helpers = createHelpers(data); // Two tags (one plain, one context) data.createTag(GenTag().withId(42).withName(QStringLiteral("42")).asPlain()); data.createTag(GenTag().withId(43).withName(QStringLiteral("43")).asContext()); // The list which will be filled by the fetch function auto tags = Akonadi::Tag::List(); auto add = [&tags] (const Akonadi::Tag &tag) { tags.append(tag); }; // WHEN auto fetch = helpers->fetchTags(); fetch(add); TestHelpers::waitForEmptyJobQueue(); auto result = QStringList(); std::transform(tags.constBegin(), tags.constEnd(), std::back_inserter(result), std::bind(&Akonadi::Tag::name, _1)); result.sort(); // THEN auto expected = QStringList({"42", "43"}); expected.sort(); QCOMPARE(result, expected); // WHEN (should not crash when the helpers object is deleted) helpers.clear(); tags.clear(); fetch(add); TestHelpers::waitForEmptyJobQueue(); // THEN result.clear(); std::transform(tags.constBegin(), tags.constEnd(), std::back_inserter(result), std::bind(&Akonadi::Tag::name, _1)); result.sort(); QCOMPARE(result, expected); } }; ZANSHIN_TEST_MAIN(AkonadiLiveQueryHelpersTest) #include "akonadilivequeryhelperstest.moc" diff --git a/tests/units/akonadi/akonadinotequeriestest.cpp b/tests/units/akonadi/akonadinotequeriestest.cpp index d22f240d..77062075 100644 --- a/tests/units/akonadi/akonadinotequeriestest.cpp +++ b/tests/units/akonadi/akonadinotequeriestest.cpp @@ -1,487 +1,487 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens Copyright 2014 Remi Benoit 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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 "akonadi/akonadinotequeries.h" #include "akonadi/akonadiserializer.h" #include "testlib/akonadifakedata.h" #include "testlib/gencollection.h" #include "testlib/gennote.h" #include "testlib/gentag.h" #include "testlib/gentodo.h" #include "testlib/testhelpers.h" using namespace Testlib; class AkonadiNoteQueriesTest : public QObject { Q_OBJECT private slots: void shouldLookInAllReportedForAllNotes() { // GIVEN AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withNoteContent()); // One note in the first collection data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42"))); // Two notes in the second collection data.createItem(GenNote().withId(43).withParent(43).withTitle(QStringLiteral("43"))); data.createItem(GenNote().withId(44).withParent(43).withTitle(QStringLiteral("44"))); // WHEN QScopedPointer queries(new Akonadi::NoteQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findAll(); result->data(); result = queries->findAll(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 3); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); QCOMPARE(result->data().at(2)->title(), QStringLiteral("44")); } void shouldIgnoreItemsWhichAreNotNotes() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); // Two notes in the collection data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42"))); auto item = Akonadi::Item(43); item.setPayloadFromData("FooBar"); item.setParentCollection(Akonadi::Collection(42)); data.createItem(item); // WHEN QScopedPointer queries(new Akonadi::NoteQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findAll(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } void shouldReactToItemAddsForNotesOnly() { // GIVEN AkonadiFakeData data; // One empty collection data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); QScopedPointer queries(new Akonadi::NoteQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findAll(); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); // WHEN data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42"))); auto item = Akonadi::Item(43); item.setPayloadFromData("FooBar"); item.setParentCollection(Akonadi::Collection(42)); data.createItem(item); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().first()->title(), QStringLiteral("42")); } void shouldReactToItemRemovesForAllNotes() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); // Three notes in the collection data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42"))); data.createItem(GenNote().withId(43).withParent(42).withTitle(QStringLiteral("43"))); data.createItem(GenNote().withId(44).withParent(42).withTitle(QStringLiteral("44"))); QScopedPointer queries(new Akonadi::NoteQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findAll(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 3); // WHEN data.removeItem(Akonadi::Item(43)); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("44")); } void shouldReactToItemChangesForAllNotes() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); // Three Note in the collection data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42"))); data.createItem(GenNote().withId(43).withParent(42).withTitle(QStringLiteral("43"))); data.createItem(GenNote().withId(44).withParent(42).withTitle(QStringLiteral("44"))); QScopedPointer queries(new Akonadi::NoteQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult::Ptr result = queries->findAll(); // Even though the pointer didn't change it's convenient to user if we call // the replace handlers bool replaceHandlerCalled = false; result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::Note::Ptr &, int) { replaceHandlerCalled = true; }); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 3); // WHEN data.modifyItem(GenNote(data.item(43)).withTitle(QStringLiteral("43bis"))); // THEN QCOMPARE(result->data().size(), 3); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43bis")); QCOMPARE(result->data().at(2)->title(), QStringLiteral("44")); QVERIFY(replaceHandlerCalled); } void shouldLookInAllSelectedCollectionsForInboxNotes() { // GIVEN AkonadiFakeData data; // Three top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withNoteContent()); - data.createCollection(GenCollection().withId(44).withRootAsParent().withNoteContent().enabled(false)); + data.createCollection(GenCollection().withId(44).withRootAsParent().withNoteContent().selected(false)); // One note in the first collection data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42"))); // Two notes in the second collection data.createItem(GenNote().withId(43).withParent(43).withTitle(QStringLiteral("43"))); data.createItem(GenNote().withId(44).withParent(43).withTitle(QStringLiteral("44"))); // One note in the third collection data.createItem(GenNote().withId(45).withParent(44).withTitle(QStringLiteral("45"))); // WHEN QScopedPointer queries(new Akonadi::NoteQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findInbox(); result->data(); result = queries->findInbox(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 3); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); QCOMPARE(result->data().at(2)->title(), QStringLiteral("44")); } void shouldIgnoreItemsWhichAreNotNotesInInbox() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); // Three items in the collection data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42"))); // One of them is a task data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43"))); // One of them is not a task or a note auto item = Akonadi::Item(44); item.setPayloadFromData("FooBar"); item.setParentCollection(Akonadi::Collection(42)); data.createItem(item); // WHEN QScopedPointer queries(new Akonadi::NoteQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findInbox(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } void shouldNotHaveNotesWithTagsInInbox_data() { QTest::addColumn("hasTags"); QTest::addColumn("isExpectedInInbox"); QTest::newRow("note with no tags") << false << true; QTest::newRow("note with tags") << true << false; } void shouldNotHaveNotesWithTagsInInbox() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); // One plain tag data.createTag(GenTag().withId(42).withName(QStringLiteral("tag-42")).asPlain()); // One item in the collection QFETCH(bool, hasTags); auto tagIds = QList(); if (hasTags) tagIds << 42; data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42")).withTags(tagIds)); // WHEN QScopedPointer queries(new Akonadi::NoteQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findInbox(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QFETCH(bool, isExpectedInInbox); if (isExpectedInInbox) { QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } else { QVERIFY(result->data().isEmpty()); } } void shouldReactToItemAddsForInbox_data() { QTest::addColumn("reactionExpected"); QTest::addColumn("isTodo"); QTest::addColumn("hasTags"); QTest::newRow("task with no tag") << false << true << false; QTest::newRow("task with tag") << false << true << true; QTest::newRow("note which should be in inbox") << true << false << false; QTest::newRow("note with tag") << false << false << true; } void shouldReactToItemAddsForInbox() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent().withNoteContent()); // One plain tag data.createTag(GenTag().withId(42).withName(QStringLiteral("tag-42")).asPlain()); QScopedPointer queries(new Akonadi::NoteQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findInbox(); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); // WHEN QFETCH(bool, hasTags); auto tagIds = QList(); if (hasTags) tagIds << 42; QFETCH(bool, isTodo); if (isTodo) { data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).withTags(tagIds)); } else { data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42")).withTags(tagIds)); } // THEN QFETCH(bool, reactionExpected); if (reactionExpected) { QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } else { QVERIFY(result->data().isEmpty()); } } void shouldReactToItemRemovesForInbox() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); // One item in the collection data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42"))); // WHEN QScopedPointer queries(new Akonadi::NoteQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findInbox(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().first()->title(), QStringLiteral("42")); // WHEN data.removeItem(Akonadi::Item(42)); // THEN QVERIFY(result->data().isEmpty()); } void shouldReactToItemChangesForInbox_data() { QTest::addColumn("hasTagsBefore"); QTest::addColumn("hasTagsAfter"); QTest::addColumn("inListAfterChange"); QTest::newRow("note appears in inbox") << true << false << true; QTest::newRow("note disappears from inbox") << false << true << false; } void shouldReactToItemChangesForInbox() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); // One plain tag data.createTag(GenTag().withId(42).withName(QStringLiteral("tag-42")).asPlain()); // Note data QFETCH(bool, hasTagsBefore); auto tagIds = QList(); if (hasTagsBefore) tagIds << 42; data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42")).withTags(tagIds)); QScopedPointer queries(new Akonadi::NoteQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findInbox(); TestHelpers::waitForEmptyJobQueue(); QFETCH(bool, inListAfterChange); if (inListAfterChange) { QVERIFY(result->data().isEmpty()); } else { QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } // WHEN QFETCH(bool, hasTagsAfter); tagIds.clear(); if (hasTagsAfter) tagIds << 42; data.modifyItem(GenNote(data.item(42)).withTags(tagIds)); // THEN if (inListAfterChange) { QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } else { QVERIFY(result->data().isEmpty()); } } void shouldReactToCollectionSelectionChangesForInbox() { // GIVEN AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withNoteContent()); // One note in each collection data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42"))); data.createItem(GenNote().withId(43).withParent(43).withTitle(QStringLiteral("43"))); QScopedPointer queries(new Akonadi::NoteQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findInbox(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); // WHEN data.modifyCollection(GenCollection(data.collection(43)).selected(false)); TestHelpers::waitForEmptyJobQueue(); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } }; ZANSHIN_TEST_MAIN(AkonadiNoteQueriesTest) #include "akonadinotequeriestest.moc" diff --git a/tests/units/akonadi/akonadiserializertest.cpp b/tests/units/akonadi/akonadiserializertest.cpp index c62b77b7..6f777820 100644 --- a/tests/units/akonadi/akonadiserializertest.cpp +++ b/tests/units/akonadi/akonadiserializertest.cpp @@ -1,2493 +1,2405 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens Copyright 2014 Rémi Benoit 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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 "akonadi/akonadiserializer.h" #include "akonadi/akonadiapplicationselectedattribute.h" #include "akonadi/akonaditimestampattribute.h" #include #include #include #include #include #include #include Q_DECLARE_METATYPE(Akonadi::Item*) class AkonadiSerializerTest : public QObject { Q_OBJECT private slots: void shouldKnowWhenAnObjectRepresentsACollection() { // GIVEN Akonadi::Serializer serializer; auto object = Akonadi::Serializer::QObjectPtr::create(); Akonadi::Collection collection(42); // WHEN // Nothing yet // THEN QVERIFY(!serializer.representsCollection(object, collection)); // WHEN object->setProperty("collectionId", 42); // THEN QVERIFY(serializer.representsCollection(object, collection)); // WHEN object->setProperty("collectionId", 43); // THEN QVERIFY(!serializer.representsCollection(object, collection)); } void shouldKnowWhenAnObjectRepresentsAnItem() { // GIVEN Akonadi::Serializer serializer; auto object = Akonadi::Serializer::QObjectPtr::create(); Akonadi::Item item(42); // WHEN // Nothing yet // THEN QVERIFY(!serializer.representsItem(object, item)); // WHEN object->setProperty("itemId", 42); // THEN QVERIFY(serializer.representsItem(object, item)); // WHEN object->setProperty("itemId", 43); // THEN QVERIFY(!serializer.representsItem(object, item)); } void shouldKnowWhenAnAkonadiTagRepresentsATag() { // GIVEN Akonadi::Serializer serializer; Akonadi::Tag akondiTag(42); auto tag = Domain::Tag::Ptr::create(); // WHEN // Nothing yet // THEN QVERIFY(!serializer.representsAkonadiTag(tag, akondiTag)); // WHEN tag->setProperty("tagId", 42); // THEN QVERIFY(serializer.representsAkonadiTag(tag, akondiTag)); // WHEN tag->setProperty("tagId", 43); // THEN QVERIFY(!serializer.representsAkonadiTag(tag, akondiTag)); } void shouldKnowObjectUid() { // GIVEN Akonadi::Serializer serializer; auto object = Akonadi::Serializer::QObjectPtr::create(); // WHEN object->setProperty("todoUid", "my-uid"); // THEN QCOMPARE(serializer.objectUid(object), QStringLiteral("my-uid")); } void shouldCreateDataSourceFromCollection_data() { QTest::addColumn("name"); QTest::addColumn("iconName"); QTest::addColumn("mimeTypes"); QTest::addColumn("hasSelectedAttribute"); QTest::addColumn("isSelected"); - QTest::addColumn("isReferenced"); - QTest::addColumn("isEnabled"); const auto noteMimeTypes = QStringList() << QStringLiteral("text/x-vnd.akonadi.note"); const auto taskMimeTypes = QStringList() << QStringLiteral("application/x-vnd.akonadi.calendar.todo"); const auto bogusMimeTypes = QStringList() << QStringLiteral("foo/bar"); const auto allMimeTypes = noteMimeTypes + taskMimeTypes + bogusMimeTypes; - QTest::newRow("nominal case") << "name" << "icon" << allMimeTypes << true << false << false << true; + QTest::newRow("nominal case") << "name" << "icon" << allMimeTypes << true << false; - QTest::newRow("only notes") << "name" << "icon" << noteMimeTypes << true << false << false << true; - QTest::newRow("only tasks") << "name" << "icon" << taskMimeTypes << true << false << false << true; - QTest::newRow("only bogus") << "name" << "icon" << bogusMimeTypes << true << false << false << true; + QTest::newRow("only notes") << "name" << "icon" << noteMimeTypes << true << false; + QTest::newRow("only tasks") << "name" << "icon" << taskMimeTypes << true << false; + QTest::newRow("only bogus") << "name" << "icon" << bogusMimeTypes << true << false; - QTest::newRow("no selected attribute") << "name" << "icon" << allMimeTypes << false << false << false << true; - QTest::newRow("selected attribute (false)") << "name" << "icon" << allMimeTypes << true << false << false << true; - QTest::newRow("selected attribute (true)") << "name" << "icon" << allMimeTypes << true << true << false << true; + QTest::newRow("no selected attribute") << "name" << "icon" << allMimeTypes << false << false; + QTest::newRow("selected attribute (false)") << "name" << "icon" << allMimeTypes << true << false; + QTest::newRow("selected attribute (true)") << "name" << "icon" << allMimeTypes << true << true; - QTest::newRow("enabled and referenced") << "name" << "icon" << allMimeTypes << true << false << true << true; - QTest::newRow("enabled and !referenced") << "name" << "icon" << allMimeTypes << true << false << true << false; - QTest::newRow("!enabled and referenced") << "name" << "icon" << allMimeTypes << true << false << false << true; - QTest::newRow("!enabled and !referenced") << "name" << "icon" << allMimeTypes << true << false << false << false; - - QTest::newRow("empty case") << QString() << QString() << QStringList() << false << false << false << true; + QTest::newRow("empty case") << QString() << QString() << QStringList() << false << false; } void shouldCreateDataSourceFromCollection() { // GIVEN // Data... QFETCH(QString, name); QFETCH(QString, iconName); QFETCH(QStringList, mimeTypes); QFETCH(bool, hasSelectedAttribute); QFETCH(bool, isSelected); - QFETCH(bool, isReferenced); - QFETCH(bool, isEnabled); Domain::DataSource::ContentTypes expectedContentTypes; if (mimeTypes.contains(QStringLiteral("text/x-vnd.akonadi.note"))) { expectedContentTypes |= Domain::DataSource::Notes; } if (mimeTypes.contains(QStringLiteral("application/x-vnd.akonadi.calendar.todo"))) { expectedContentTypes |= Domain::DataSource::Tasks; } // ... stored in a collection Akonadi::Collection collection(42); collection.setContentMimeTypes(mimeTypes); collection.setName(name); - collection.setReferenced(isReferenced); - collection.setEnabled(isEnabled); auto displayAttribute = new Akonadi::EntityDisplayAttribute; displayAttribute->setIconName(iconName); collection.addAttribute(displayAttribute); if (hasSelectedAttribute) { auto selectedAttribute = new Akonadi::ApplicationSelectedAttribute; selectedAttribute->setSelected(isSelected); collection.addAttribute(selectedAttribute); } // WHEN Akonadi::Serializer serializer; auto dataSource = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::BaseName); // THEN QCOMPARE(dataSource->name(), name); QCOMPARE(dataSource->iconName(), iconName); QCOMPARE(dataSource->contentTypes(), expectedContentTypes); QCOMPARE(dataSource->isSelected(), !hasSelectedAttribute || isSelected); QCOMPARE(dataSource->property("collectionId").value(), collection.id()); - QCOMPARE((dataSource->listStatus() & Domain::DataSource::Listed) != 0, isReferenced || isEnabled); - QCOMPARE((dataSource->listStatus() == Domain::DataSource::Bookmarked), isEnabled); } void shouldCreateNullDataSourceFromInvalidCollection() { // GIVEN Akonadi::Collection collection; // WHEN Akonadi::Serializer serializer; auto dataSource = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::BaseName); // THEN QVERIFY(dataSource.isNull()); } void shouldUpdateDataSourceFromCollection_data() { QTest::addColumn("updatedName"); QTest::newRow("no change") << "name"; QTest::newRow("changed") << "new name"; } void shouldUpdateDataSourceFromCollection() { // GIVEN // A collection... Akonadi::Collection originalCollection(42); originalCollection.setName(QStringLiteral("name")); // ... deserialized as a data source Akonadi::Serializer serializer; auto dataSource = serializer.createDataSourceFromCollection(originalCollection, Akonadi::SerializerInterface::BaseName); // WHEN // Data... QFETCH(QString, updatedName); // ... in a new collection Akonadi::Collection updatedCollection(42); updatedCollection.setName(updatedName); serializer.updateDataSourceFromCollection(dataSource, updatedCollection, Akonadi::SerializerInterface::BaseName); // THEN QCOMPARE(dataSource->name(), updatedName); } void shouldNotUpdateDataSourceFromInvalidCollection() { // GIVEN // Data... const QString name = QStringLiteral("name"); // ... stored in a collection... Akonadi::Collection originalCollection(42); originalCollection.setName(name); // ... deserialized as a data source Akonadi::Serializer serializer; auto dataSource = serializer.createDataSourceFromCollection(originalCollection, Akonadi::SerializerInterface::BaseName); // WHEN Akonadi::Collection invalidCollection; invalidCollection.setName(QStringLiteral("foo")); serializer.updateDataSourceFromCollection(dataSource, invalidCollection, Akonadi::SerializerInterface::BaseName); // THEN QCOMPARE(dataSource->name(), name); } void shouldNameDataSourceFromCollectionPathIfRequested() { // GIVEN // Data... const QString name = QStringLiteral("name"); const QString parentName = QStringLiteral("parent"); // ... stored in a collection with a parent Akonadi::Collection collection(42); collection.setName(name); Akonadi::Collection parentCollection(41); parentCollection.setName(QStringLiteral("Foo")); auto attribute = new Akonadi::EntityDisplayAttribute; attribute->setDisplayName(parentName); parentCollection.addAttribute(attribute); collection.setParentCollection(parentCollection); // WHEN Akonadi::Serializer serializer; auto dataSource1 = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::FullPath); auto dataSource2 = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::BaseName); // Give it another try with the root parentCollection.setParentCollection(Akonadi::Collection::root()); collection.setParentCollection(parentCollection); auto dataSource3 = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::FullPath); auto dataSource4 = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::BaseName); // THEN QCOMPARE(dataSource1->name(), QString(parentName + '/' + name)); QCOMPARE(dataSource2->name(), name); QCOMPARE(dataSource3->name(), QString(parentName + '/' + name)); QCOMPARE(dataSource4->name(), name); } void shouldCreateCollectionFromDataSource_data() { QTest::addColumn("name"); QTest::addColumn("iconName"); QTest::addColumn("contentTypes"); QTest::addColumn("isSelected"); - QTest::addColumn("listStatus"); const auto noType = Domain::DataSource::ContentTypes(Domain::DataSource::NoContent); const auto taskType = Domain::DataSource::ContentTypes(Domain::DataSource::Tasks); const auto noteType = Domain::DataSource::ContentTypes(Domain::DataSource::Notes); const auto allTypes = taskType | noteType; - const auto unlisted = Domain::DataSource::Unlisted; - const auto listed = Domain::DataSource::Listed; - const auto bookmarked = Domain::DataSource::Bookmarked; - - QTest::newRow("nominal case") << "name" << "icon-name" << allTypes << true << unlisted; - - QTest::newRow("only notes") << "name" << "icon-name" << noteType << true << unlisted; - QTest::newRow("only tasks") << "name" << "icon-name" << taskType << true << unlisted; - QTest::newRow("only nothing ;)") << "name" << "icon-name" << noType << true << unlisted; + QTest::newRow("nominal case") << "name" << "icon-name" << allTypes << true; - QTest::newRow("not selected") << "name" << "icon-name" << allTypes << false << unlisted; - QTest::newRow("selected") << "name" << "icon-name" << allTypes << true << unlisted; + QTest::newRow("only notes") << "name" << "icon-name" << noteType << true; + QTest::newRow("only tasks") << "name" << "icon-name" << taskType << true; + QTest::newRow("only nothing ;)") << "name" << "icon-name" << noType << true; - QTest::newRow("unlisted") << "name" << "icon-name" << allTypes << true << unlisted; - QTest::newRow("listed") << "name" << "icon-name" << allTypes << true << listed; - QTest::newRow("bookmarked") << "name" << "icon-name" << allTypes << true << bookmarked; + QTest::newRow("not selected") << "name" << "icon-name" << allTypes << false; + QTest::newRow("selected") << "name" << "icon-name" << allTypes << true; - QTest::newRow("empty case") << QString() << QString() << noType << true << unlisted; + QTest::newRow("empty case") << QString() << QString() << noType << true; } void shouldCreateCollectionFromDataSource() { // GIVEN const auto timestamp = QDateTime::currentMSecsSinceEpoch(); // Data... QFETCH(QString, name); QFETCH(QString, iconName); QFETCH(Domain::DataSource::ContentTypes, contentTypes); QFETCH(bool, isSelected); - QFETCH(Domain::DataSource::ListStatus, listStatus); QStringList mimeTypes; if (contentTypes & Domain::DataSource::Tasks) mimeTypes << QStringLiteral("application/x-vnd.akonadi.calendar.todo"); if (contentTypes & Domain::DataSource::Notes) mimeTypes << QStringLiteral("text/x-vnd.akonadi.note"); // ... stored in a data source auto source = Domain::DataSource::Ptr::create(); source->setName(name); source->setIconName(iconName); source->setContentTypes(contentTypes); - source->setListStatus(listStatus); source->setSelected(isSelected); source->setProperty("collectionId", 42); // WHEN Akonadi::Serializer serializer; auto collection = serializer.createCollectionFromDataSource(source); // THEN QCOMPARE(collection.id(), source->property("collectionId").value()); QVERIFY(collection.hasAttribute()); QCOMPARE(collection.attribute()->isSelected(), isSelected); QVERIFY(collection.hasAttribute()); QVERIFY(collection.attribute()->timestamp() >= timestamp); - - switch (listStatus) { - case Domain::DataSource::Unlisted: - QVERIFY(!collection.referenced()); - QVERIFY(!collection.enabled()); - break; - case Domain::DataSource::Listed: - QVERIFY(collection.referenced()); - QVERIFY(!collection.enabled()); - break; - case Domain::DataSource::Bookmarked: - QVERIFY(collection.enabled()); - break; - default: - qFatal("Shouldn't happen"); - break; - } - } - - void shouldVerifyIfCollectionIsListed_data() - { - QTest::addColumn("isEnabled"); - QTest::addColumn("isReferenced"); - QTest::addColumn("expectedListed"); - - QTest::newRow("enabled and referenced") << true << true << true; - QTest::newRow("enabled and !referenced") << true << false << true; - QTest::newRow("!enabled and referenced") << false << true << true; - QTest::newRow("!enabled and !referenced") << false << false << false; - } - - void shouldVerifyIfCollectionIsListed() - { - // GIVEN - QFETCH(bool, isEnabled); - QFETCH(bool, isReferenced); - - // ... stored in a collection - Akonadi::Collection collection(42); - collection.setReferenced(isReferenced); - collection.setEnabled(isEnabled); - - // WHEN - Akonadi::Serializer serializer; - - // THEN - QFETCH(bool, expectedListed); - QCOMPARE(serializer.isListedCollection(collection), expectedListed); } void shouldVerifyIfCollectionIsSelected_data() { QTest::addColumn("mimeTypes"); QTest::addColumn("hasSelectedAttribute"); QTest::addColumn("isSelected"); - QTest::addColumn("isReferenced"); - QTest::addColumn("isEnabled"); QTest::addColumn("expectedSelected"); const auto noteMimeTypes = QStringList() << QStringLiteral("text/x-vnd.akonadi.note"); const auto taskMimeTypes = QStringList() << QStringLiteral("application/x-vnd.akonadi.calendar.todo"); const auto bogusMimeTypes = QStringList() << QStringLiteral("foo/bar"); const auto allMimeTypes = noteMimeTypes + taskMimeTypes + bogusMimeTypes; - QTest::newRow("nominal case") << allMimeTypes << true << false << false << true << false; - - QTest::newRow("only notes") << noteMimeTypes << true << false << false << true << false; - QTest::newRow("only tasks") << taskMimeTypes << true << false << false << true << false; - QTest::newRow("only bogus") << bogusMimeTypes << true << false << false << true << false; - - QTest::newRow("selected, only notes") << noteMimeTypes << true << true << false << true << true; - QTest::newRow("selected, only tasks") << taskMimeTypes << true << true << false << true << true; - QTest::newRow("selected, only bogus") << bogusMimeTypes << true << true << false << true << false; + QTest::newRow("nominal case") << allMimeTypes << true << false << false; - QTest::newRow("no selected attribute") << allMimeTypes << false << false << false << true << true; - QTest::newRow("selected attribute (false)") << allMimeTypes << true << false << false << true << false; - QTest::newRow("selected attribute (true)") << allMimeTypes << true << true << false << true << true; + QTest::newRow("only notes") << noteMimeTypes << true << false << false; + QTest::newRow("only tasks") << taskMimeTypes << true << false << false; + QTest::newRow("only bogus") << bogusMimeTypes << true << false << false; - QTest::newRow("enabled and referenced") << allMimeTypes << true << false << true << true << false; - QTest::newRow("enabled and !referenced") << allMimeTypes << true << false << true << false << false; - QTest::newRow("!enabled and referenced") << allMimeTypes << true << false << false << true << false; - QTest::newRow("!enabled and !referenced") << allMimeTypes << true << false << false << false << false; + QTest::newRow("selected, only notes") << noteMimeTypes << true << true << true; + QTest::newRow("selected, only tasks") << taskMimeTypes << true << true << true; + QTest::newRow("selected, only bogus") << bogusMimeTypes << true << true << false; - QTest::newRow("selected, enabled and referenced") << allMimeTypes << true << true << true << true << true; - QTest::newRow("selected, enabled and !referenced") << allMimeTypes << true << true << true << false << true; - QTest::newRow("selected, !enabled and referenced") << allMimeTypes << true << true << false << true << true; - QTest::newRow("selected, !enabled and !referenced") << allMimeTypes << true << true << false << false << false; + QTest::newRow("no selected attribute") << allMimeTypes << false << false << true; + QTest::newRow("selected attribute (false)") << allMimeTypes << true << false << false; + QTest::newRow("selected attribute (true)") << allMimeTypes << true << true << true; - QTest::newRow("empty case") << QStringList() << false << false << false << true << false; + QTest::newRow("empty case") << QStringList() << false << false << false; } void shouldVerifyIfCollectionIsSelected() { // GIVEN QFETCH(QStringList, mimeTypes); QFETCH(bool, hasSelectedAttribute); QFETCH(bool, isSelected); - QFETCH(bool, isReferenced); - QFETCH(bool, isEnabled); Domain::DataSource::ContentTypes expectedContentTypes; if (mimeTypes.contains(QStringLiteral("text/x-vnd.akonadi.note"))) { expectedContentTypes |= Domain::DataSource::Notes; } if (mimeTypes.contains(QStringLiteral("application/x-vnd.akonadi.calendar.todo"))) { expectedContentTypes |= Domain::DataSource::Tasks; } // ... stored in a collection Akonadi::Collection collection(42); collection.setContentMimeTypes(mimeTypes); - collection.setReferenced(isReferenced); - collection.setEnabled(isEnabled); if (hasSelectedAttribute) { auto selectedAttribute = new Akonadi::ApplicationSelectedAttribute; selectedAttribute->setSelected(isSelected); collection.addAttribute(selectedAttribute); } // WHEN Akonadi::Serializer serializer; // THEN QFETCH(bool, expectedSelected); QCOMPARE(serializer.isSelectedCollection(collection), expectedSelected); } void shouldVerifyCollectionContents_data() { QTest::addColumn("mimeType"); QTest::addColumn("expectedNotes"); QTest::addColumn("expectedTasks"); QTest::newRow("task collection") << "application/x-vnd.akonadi.calendar.todo" << false << true; QTest::newRow("note collection") << "text/x-vnd.akonadi.note" << true << false; } void shouldVerifyCollectionContents() { // GIVEN // Data... QFETCH(QString, mimeType); // ... stored in a collection Akonadi::Collection collection(42); collection.setContentMimeTypes(QStringList() << mimeType); // WHEN Akonadi::Serializer serializer; QFETCH(bool, expectedNotes); QFETCH(bool, expectedTasks); // THEN QCOMPARE(serializer.isNoteCollection(collection), expectedNotes); QCOMPARE(serializer.isTaskCollection(collection), expectedTasks); } void shouldCreateTaskFromItem_data() { QTest::addColumn("summary"); QTest::addColumn("content"); QTest::addColumn("isDone"); QTest::addColumn("doneDate"); QTest::addColumn("startDate"); QTest::addColumn("dueDate"); QTest::addColumn("delegateName"); QTest::addColumn("delegateEmail"); QTest::newRow("nominal case") << "summary" << "content" << false << QDateTime() << QDateTime(QDate(2013, 11, 24)) << QDateTime(QDate(2014, 03, 01)) << "John Doe" << "j@d.com"; QTest::newRow("done case") << "summary" << "content" << true << QDateTime(QDate(2013, 11, 30)) << QDateTime(QDate(2013, 11, 24)) << QDateTime(QDate(2014, 03, 01)) << "John Doe" << "j@d.com"; QTest::newRow("done without doneDate case") << "summary" << "content" << true << QDateTime() << QDateTime(QDate(2013, 11, 24)) << QDateTime(QDate(2014, 03, 01)) << "John Doe" << "j@d.com"; QTest::newRow("empty case") << QString() << QString() << false << QDateTime() << QDateTime() << QDateTime() << QString() << QString(); } void shouldCreateTaskFromItem() { // GIVEN // Data... QFETCH(QString, summary); QFETCH(QString, content); QFETCH(bool, isDone); QFETCH(QDateTime, doneDate); QFETCH(QDateTime, startDate); QFETCH(QDateTime, dueDate); QFETCH(QString, delegateName); QFETCH(QString, delegateEmail); // Switch to UTC doneDate.setTimeSpec(Qt::UTC); startDate.setTimeSpec(Qt::UTC); dueDate.setTimeSpec(Qt::UTC); // ... stored in a todo... KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(summary); todo->setDescription(content); if (isDone) todo->setCompleted(KDateTime(doneDate)); else todo->setCompleted(isDone); todo->setDtStart(KDateTime(startDate, KDateTime::UTC)); todo->setDtDue(KDateTime(dueDate, KDateTime::UTC)); todo->setRelatedTo(QStringLiteral("my-uid")); if (!delegateName.isEmpty() || !delegateEmail.isEmpty()) { KCalCore::Attendee::Ptr attendee(new KCalCore::Attendee(delegateName, delegateEmail, true, KCalCore::Attendee::Accepted)); todo->addAttendee(attendee); } // ... as payload of an item Akonadi::Item item; item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); // which has a parent collection Akonadi::Collection collection(43); item.setParentCollection(collection); // WHEN Akonadi::Serializer serializer; Domain::Task::Ptr task = serializer.createTaskFromItem(item); auto artifact = serializer.createArtifactFromItem(item).dynamicCast(); // THEN QCOMPARE(task->title(), summary); QCOMPARE(task->text(), content); QCOMPARE(task->isDone(), isDone); QCOMPARE(task->doneDate(), doneDate); QCOMPARE(task->startDate(), startDate); QCOMPARE(task->dueDate(), dueDate); QCOMPARE(task->property("todoUid").toString(), todo->uid()); QCOMPARE(task->property("relatedUid").toString(), todo->relatedTo()); QCOMPARE(task->property("itemId").toLongLong(), item.id()); QCOMPARE(task->property("parentCollectionId").toLongLong(), collection.id()); QCOMPARE(task->delegate().name(), delegateName); QCOMPARE(task->delegate().email(), delegateEmail); QVERIFY(!artifact.isNull()); QCOMPARE(artifact->title(), summary); QCOMPARE(artifact->text(), content); QCOMPARE(artifact->isDone(), isDone); QCOMPARE(artifact->doneDate(), doneDate); QCOMPARE(artifact->startDate(), startDate); QCOMPARE(artifact->dueDate(), dueDate); QCOMPARE(artifact->property("todoUid").toString(), todo->uid()); QCOMPARE(artifact->property("relatedUid").toString(), todo->relatedTo()); QCOMPARE(artifact->property("itemId").toLongLong(), item.id()); QCOMPARE(artifact->property("parentCollectionId").toLongLong(), collection.id()); QCOMPARE(artifact->delegate().name(), delegateName); QCOMPARE(artifact->delegate().email(), delegateEmail); } void shouldCreateNullTaskFromInvalidItem() { // GIVEN Akonadi::Item item; // WHEN Akonadi::Serializer serializer; Domain::Task::Ptr task = serializer.createTaskFromItem(item); auto artifact = serializer.createArtifactFromItem(item); // THEN QVERIFY(task.isNull()); QVERIFY(artifact.isNull()); } void shouldCreateNullTaskFromProjectItem() { // GIVEN // A todo with the project flag KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(QStringLiteral("foo")); todo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); // ... as payload of an item Akonadi::Item item; item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); // WHEN Akonadi::Serializer serializer; Domain::Task::Ptr task = serializer.createTaskFromItem(item); auto artifact = serializer.createArtifactFromItem(item); // THEN QVERIFY(task.isNull()); QVERIFY(artifact.isNull()); } void shouldUpdateTaskFromItem_data() { QTest::addColumn("updatedSummary"); QTest::addColumn("updatedContent"); QTest::addColumn("updatedDone"); QTest::addColumn("updatedDoneDate"); QTest::addColumn("updatedStartDate"); QTest::addColumn("updatedDueDate"); QTest::addColumn("updatedRelated"); QTest::addColumn("updatedDelegateName"); QTest::addColumn("updatedDelegateEmail"); QTest::addColumn("updatedRunning"); QTest::newRow("no change") << "summary" << "content" << false << QDateTime() << QDateTime(QDate(2013, 11, 24)) << QDateTime(QDate(2014, 03, 01)) << "my-uid" << "John Doe" << "j@d.com" << false; QTest::newRow("changed") << "new summary" << "new content" << true << QDateTime(QDate(2013, 11, 28)) << QDateTime(QDate(2013, 11, 25)) << QDateTime(QDate(2014, 03, 02)) << "my-new-uid" << "John Smith" << "j@s.com" << false; QTest::newRow("set_to_running") << "summary" << "content" << false << QDateTime() << QDateTime(QDate(2013, 11, 24)) << QDateTime(QDate(2014, 03, 01)) << "my-uid" << "John Doe" << "j@d.com" << true; } void shouldUpdateTaskFromItem() { // GIVEN // A todo... KCalCore::Todo::Ptr originalTodo(new KCalCore::Todo); originalTodo->setSummary(QStringLiteral("summary")); originalTodo->setDescription(QStringLiteral("content")); originalTodo->setCompleted(false); originalTodo->setDtStart(KDateTime(QDate(2013, 11, 24), KDateTime::UTC)); originalTodo->setDtDue(KDateTime(QDate(2014, 03, 01), KDateTime::UTC)); originalTodo->setRelatedTo(QStringLiteral("my-uid")); KCalCore::Attendee::Ptr originalAttendee(new KCalCore::Attendee(QStringLiteral("John Doe"), QStringLiteral("j@d.com"), true, KCalCore::Attendee::Accepted)); originalTodo->addAttendee(originalAttendee); // ... as payload of an item... Akonadi::Item originalItem; originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); originalItem.setPayload(originalTodo); // ... which has a parent collection... Akonadi::Collection originalCollection(43); originalItem.setParentCollection(originalCollection); // ... deserialized as a task Akonadi::Serializer serializer; auto task = serializer.createTaskFromItem(originalItem); auto artifact = serializer.createArtifactFromItem(originalItem); // WHEN // Data... QFETCH(QString, updatedSummary); QFETCH(QString, updatedContent); QFETCH(bool, updatedDone); QFETCH(QDateTime, updatedDoneDate); QFETCH(QDateTime, updatedStartDate); QFETCH(QDateTime, updatedDueDate); QFETCH(QString, updatedRelated); QFETCH(QString, updatedDelegateName); QFETCH(QString, updatedDelegateEmail); QFETCH(bool, updatedRunning); // Switch to UTC updatedDoneDate.setTimeSpec(Qt::UTC); updatedStartDate.setTimeSpec(Qt::UTC); updatedDueDate.setTimeSpec(Qt::UTC); // ... in a new todo... KCalCore::Todo::Ptr updatedTodo(new KCalCore::Todo); updatedTodo->setSummary(updatedSummary); updatedTodo->setDescription(updatedContent); if (updatedDone) updatedTodo->setCompleted(KDateTime(updatedDoneDate)); else updatedTodo->setCompleted(updatedDone); updatedTodo->setDtStart(KDateTime(updatedStartDate, KDateTime::UTC)); updatedTodo->setDtDue(KDateTime(updatedDueDate, KDateTime::UTC)); updatedTodo->setRelatedTo(updatedRelated); if (!updatedDelegateName.isEmpty() || !updatedDelegateEmail.isEmpty()) { KCalCore::Attendee::Ptr updatedAttendee(new KCalCore::Attendee(updatedDelegateName, updatedDelegateEmail, true, KCalCore::Attendee::Accepted)); updatedTodo->addAttendee(updatedAttendee); } if (updatedRunning) { updatedTodo->setCustomProperty("Zanshin", "Running", "1"); } else { updatedTodo->removeCustomProperty("Zanshin", "Running"); } // ... as payload of a new item Akonadi::Item updatedItem; updatedItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); updatedItem.setPayload(updatedTodo); // ... which has a new parent collection Akonadi::Collection updatedCollection(45); updatedItem.setParentCollection(updatedCollection); serializer.updateTaskFromItem(task, updatedItem); serializer.updateArtifactFromItem(artifact, updatedItem); // THEN QCOMPARE(task->title(), updatedSummary); QCOMPARE(task->text(), updatedContent); QCOMPARE(task->isDone(), updatedDone); QCOMPARE(task->doneDate(), updatedDoneDate.toUTC()); QCOMPARE(task->startDate(), updatedStartDate.toUTC()); QCOMPARE(task->dueDate(), updatedDueDate.toUTC()); QCOMPARE(task->property("todoUid").toString(), updatedTodo->uid()); QCOMPARE(task->property("relatedUid").toString(), updatedTodo->relatedTo()); QCOMPARE(task->property("itemId").toLongLong(), updatedItem.id()); QCOMPARE(task->property("parentCollectionId").toLongLong(), updatedCollection.id()); QCOMPARE(task->delegate().name(), updatedDelegateName); QCOMPARE(task->delegate().email(), updatedDelegateEmail); QCOMPARE(task->isRunning(), updatedRunning); task = artifact.dynamicCast(); QCOMPARE(task->title(), updatedSummary); QCOMPARE(task->text(), updatedContent); QCOMPARE(task->isDone(), updatedDone); QCOMPARE(task->doneDate(), updatedDoneDate.toUTC()); QCOMPARE(task->startDate(), updatedStartDate.toUTC()); QCOMPARE(task->dueDate(), updatedDueDate.toUTC()); QCOMPARE(task->property("todoUid").toString(), updatedTodo->uid()); QCOMPARE(task->property("relatedUid").toString(), updatedTodo->relatedTo()); QCOMPARE(task->property("itemId").toLongLong(), updatedItem.id()); QCOMPARE(task->property("parentCollectionId").toLongLong(), updatedCollection.id()); QCOMPARE(task->delegate().name(), updatedDelegateName); QCOMPARE(task->delegate().email(), updatedDelegateEmail); QCOMPARE(task->isRunning(), updatedRunning); } void shouldNotUpdateTaskFromInvalidItem() { // GIVEN // Data... const QString summary = QStringLiteral("summary"); const QString content = QStringLiteral("content"); const bool isDone = true; const QDateTime doneDate(QDate(2013, 11, 30), QTime(0, 0), Qt::UTC); const QDateTime startDate(QDate(2013, 11, 24), QTime(0, 0), Qt::UTC); const QDateTime dueDate(QDate(2014, 03, 01), QTime(0, 0), Qt::UTC); // ... stored in a todo... KCalCore::Todo::Ptr originalTodo(new KCalCore::Todo); originalTodo->setSummary(summary); originalTodo->setDescription(content); if (originalTodo) originalTodo->setCompleted(KDateTime(doneDate)); else originalTodo->setCompleted(isDone); originalTodo->setDtStart(KDateTime(startDate, KDateTime::UTC)); originalTodo->setDtDue(KDateTime(dueDate, KDateTime::UTC)); // ... as payload of an item... Akonadi::Item originalItem; originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); originalItem.setPayload(originalTodo); // ... deserialized as a task Akonadi::Serializer serializer; auto task = serializer.createTaskFromItem(originalItem); auto artifact = serializer.createArtifactFromItem(originalItem); // WHEN Akonadi::Item invalidItem; serializer.updateTaskFromItem(task, invalidItem); serializer.updateArtifactFromItem(artifact, invalidItem); // THEN QCOMPARE(task->title(), summary); QCOMPARE(task->text(), content); QCOMPARE(task->isDone(), isDone); QCOMPARE(task->doneDate(), doneDate); QCOMPARE(task->startDate(), startDate); QCOMPARE(task->dueDate(), dueDate); QCOMPARE(task->property("itemId").toLongLong(), originalItem.id()); task = artifact.dynamicCast(); QCOMPARE(task->title(), summary); QCOMPARE(task->text(), content); QCOMPARE(task->isDone(), isDone); QCOMPARE(task->doneDate(), doneDate); QCOMPARE(task->startDate(), startDate); QCOMPARE(task->dueDate(), dueDate); QCOMPARE(task->property("itemId").toLongLong(), originalItem.id()); } void shouldNotUpdateTaskFromProjectItem() { // GIVEN // Data... const QString summary = QStringLiteral("summary"); const QString content = QStringLiteral("content"); const bool isDone = true; const QDateTime doneDate(QDate(2013, 11, 30), QTime(0, 0), Qt::UTC); const QDateTime startDate(QDate(2013, 11, 24), QTime(0, 0), Qt::UTC); const QDateTime dueDate(QDate(2014, 03, 01), QTime(0, 0), Qt::UTC); // ... stored in a todo... KCalCore::Todo::Ptr originalTodo(new KCalCore::Todo); originalTodo->setSummary(summary); originalTodo->setDescription(content); if (originalTodo) originalTodo->setCompleted(KDateTime(doneDate)); else originalTodo->setCompleted(isDone); originalTodo->setDtStart(KDateTime(startDate, KDateTime::UTC)); originalTodo->setDtDue(KDateTime(dueDate, KDateTime::UTC)); // ... as payload of an item... Akonadi::Item originalItem; originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); originalItem.setPayload(originalTodo); // ... deserialized as a task Akonadi::Serializer serializer; auto task = serializer.createTaskFromItem(originalItem); auto artifact = serializer.createArtifactFromItem(originalItem); // WHEN // A todo with the project flag KCalCore::Todo::Ptr projectTodo(new KCalCore::Todo); projectTodo->setSummary(QStringLiteral("foo")); projectTodo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); // ... as payload of an item Akonadi::Item projectItem; projectItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); projectItem.setPayload(projectTodo); serializer.updateTaskFromItem(task, projectItem); serializer.updateArtifactFromItem(artifact, projectItem); // THEN QCOMPARE(task->title(), summary); QCOMPARE(task->text(), content); QCOMPARE(task->isDone(), isDone); QCOMPARE(task->doneDate(), doneDate); QCOMPARE(task->startDate(), startDate); QCOMPARE(task->dueDate(), dueDate); QCOMPARE(task->property("itemId").toLongLong(), originalItem.id()); task = artifact.dynamicCast(); QCOMPARE(task->title(), summary); QCOMPARE(task->text(), content); QCOMPARE(task->isDone(), isDone); QCOMPARE(task->doneDate(), doneDate); QCOMPARE(task->startDate(), startDate); QCOMPARE(task->dueDate(), dueDate); QCOMPARE(task->property("itemId").toLongLong(), originalItem.id()); } void shouldCreateItemFromTask_data() { QTest::addColumn("summary"); QTest::addColumn("content"); QTest::addColumn("isDone"); QTest::addColumn("doneDate"); QTest::addColumn("startDate"); QTest::addColumn("dueDate"); QTest::addColumn("itemId"); QTest::addColumn("parentCollectionId"); QTest::addColumn("todoUid"); QTest::addColumn("delegate"); QTest::addColumn("running"); QTest::newRow("nominal case (no id)") << "summary" << "content" << false << QDateTime() << QDateTime(QDate(2013, 11, 24)) << QDateTime(QDate(2014, 03, 01)) << qint64(-1) << qint64(-1) << QString() << Domain::Task::Delegate(QStringLiteral("John Doe"), QStringLiteral("j@d.com")) << false; QTest::newRow("done case (no id)") << "summary" << "content" << true << QDateTime(QDate(2013, 11, 30)) << QDateTime(QDate(2013, 11, 24)) << QDateTime(QDate(2014, 03, 01)) << qint64(-1) << qint64(-1) << QString() << Domain::Task::Delegate(QStringLiteral("John Doe"), QStringLiteral("j@d.com")) << false; QTest::newRow("empty case (no id)") << QString() << QString() << false << QDateTime() << QDateTime() << QDateTime() << qint64(-1) << qint64(-1) << QString() << Domain::Task::Delegate() << false; QTest::newRow("nominal_with_time_info_noid") << "summary" << "content" << true << QDateTime(QDate(2015, 3, 1), QTime(1, 2, 3), Qt::UTC) << QDateTime(QDate(2013, 11, 24), QTime(0, 1, 2), Qt::UTC) << QDateTime(QDate(2016, 3, 1), QTime(4, 5, 6), Qt::UTC) << qint64(-1) << qint64(-1) << QString() << Domain::Task::Delegate(QStringLiteral("John Doe"), QStringLiteral("j@d.com")) << false; QTest::newRow("nominal case (with id)") << "summary" << "content" << false << QDateTime() << QDateTime(QDate(2013, 11, 24)) << QDateTime(QDate(2014, 03, 01)) << qint64(42) << qint64(43) << "my-uid" << Domain::Task::Delegate(QStringLiteral("John Doe"), QStringLiteral("j@d.com")) << false; QTest::newRow("done case (with id)") << "summary" << "content" << true << QDateTime(QDate(2013, 11, 30)) << QDateTime(QDate(2013, 11, 24)) << QDateTime(QDate(2014, 03, 01)) << qint64(42) << qint64(43) << "my-uid" << Domain::Task::Delegate(QStringLiteral("John Doe"), QStringLiteral("j@d.com")) << false; QTest::newRow("empty case (with id)") << QString() << QString() << false << QDateTime() << QDateTime() << QDateTime() << qint64(42) << qint64(43) << "my-uid" << Domain::Task::Delegate() << false; QTest::newRow("nominal case (running)") << "running" << QString() << false << QDateTime() << QDateTime(QDate(2013, 11, 24)) << QDateTime(QDate(2014, 03, 01)) << qint64(-1) << qint64(-1) << QString() << Domain::Task::Delegate() << true; } void shouldCreateItemFromTask() { // GIVEN // Data... QFETCH(QString, summary); QFETCH(QString, content); QFETCH(bool, isDone); QFETCH(QDateTime, doneDate); QFETCH(QDateTime, startDate); QFETCH(QDateTime, dueDate); QFETCH(qint64, itemId); QFETCH(qint64, parentCollectionId); QFETCH(QString, todoUid); QFETCH(Domain::Task::Delegate, delegate); QFETCH(bool, running); // Switch to UTC doneDate.setTimeSpec(Qt::UTC); startDate.setTimeSpec(Qt::UTC); dueDate.setTimeSpec(Qt::UTC); // ... stored in a task auto task = Domain::Task::Ptr::create(); task->setTitle(summary); task->setText(content); task->setDone(isDone); task->setDoneDate(doneDate); task->setStartDate(startDate); task->setDueDate(dueDate); task->setDelegate(delegate); task->setRunning(running); if (itemId > 0) task->setProperty("itemId", itemId); if (parentCollectionId > 0) task->setProperty("parentCollectionId", parentCollectionId); if (!todoUid.isEmpty()) task->setProperty("todoUid", todoUid); task->setProperty("relatedUid", "parent-uid"); // WHEN Akonadi::Serializer serializer; auto item = serializer.createItemFromTask(task); // THEN QCOMPARE(item.mimeType(), KCalCore::Todo::todoMimeType()); QCOMPARE(item.isValid(), itemId > 0); if (itemId > 0) { QCOMPARE(item.id(), itemId); } QCOMPARE(item.parentCollection().isValid(), parentCollectionId > 0); if (parentCollectionId > 0) { QCOMPARE(item.parentCollection().id(), parentCollectionId); } auto todo = item.payload(); QCOMPARE(todo->summary(), summary); QCOMPARE(todo->description(), content); QCOMPARE(todo->isCompleted(), isDone); QCOMPARE(todo->completed().dateTime().toUTC(), doneDate); QCOMPARE(todo->dtStart().dateTime().toUTC(), startDate); QCOMPARE(todo->dtDue().dateTime().toUTC(), dueDate); if (todo->dtStart().isValid()) { QCOMPARE(int(todo->dtStart().timeType()), int(KDateTime::UTC)); } QCOMPARE(todo->dtStart().isDateOnly(), todo->allDay()); if (delegate.isValid()) { auto attendee = todo->attendeeByMail(delegate.email()); QVERIFY(attendee); QCOMPARE(attendee->name(), delegate.name()); QCOMPARE(attendee->email(), delegate.email()); } if (!todoUid.isEmpty()) { QCOMPARE(todo->uid(), todoUid); } QCOMPARE(todo->relatedTo(), QStringLiteral("parent-uid")); QCOMPARE(todo->customProperty("Zanshin", "Running"), running ? QStringLiteral("1") : QString()); } void shouldVerifyIfAnItemIsATaskChild_data() { QTest::addColumn("task"); QTest::addColumn("item"); QTest::addColumn("isParent"); // Create task const QString summary = QStringLiteral("summary"); const QString content = QStringLiteral("content"); const bool isDone = true; const QDateTime doneDate(QDate(2013, 11, 30), QTime(0, 0), Qt::UTC); const QDateTime startDate(QDate(2013, 11, 24), QTime(0, 0), Qt::UTC); const QDateTime dueDate(QDate(2014, 03, 01), QTime(0, 0), Qt::UTC); // ... create a task Domain::Task::Ptr task(new Domain::Task); task->setTitle(summary); task->setText(content); task->setDone(isDone); task->setDoneDate(doneDate); task->setStartDate(startDate); task->setDueDate(dueDate); task->setProperty("todoUid", "1"); // Create Child item KCalCore::Todo::Ptr childTodo(new KCalCore::Todo); childTodo->setSummary(summary); childTodo->setDescription(content); if (isDone) childTodo->setCompleted(KDateTime(doneDate)); else childTodo->setCompleted(isDone); childTodo->setDtStart(KDateTime(startDate, KDateTime::UTC)); childTodo->setDtDue(KDateTime(dueDate, KDateTime::UTC)); Akonadi::Item childItem; childItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); childItem.setPayload(childTodo); QTest::newRow("without parent") << task << childItem << false; // Create Child Item with parent KCalCore::Todo::Ptr childTodo2(new KCalCore::Todo); childTodo2->setSummary(summary); childTodo2->setDescription(content); if (isDone) childTodo2->setCompleted(KDateTime(doneDate)); else childTodo2->setCompleted(isDone); childTodo2->setDtStart(KDateTime(startDate, KDateTime::UTC)); childTodo2->setDtDue(KDateTime(dueDate, KDateTime::UTC)); childTodo2->setRelatedTo(QStringLiteral("1")); Akonadi::Item childItem2; childItem2.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); childItem2.setPayload(childTodo2); QTest::newRow("with parent") << task << childItem2 << true; Domain::Task::Ptr invalidTask(new Domain::Task); QTest::newRow("with invalid task") << invalidTask << childItem << false; Akonadi::Item invalidItem; QTest::newRow("with invalid item") << task << invalidItem << false; } void shouldVerifyIfAnItemIsATaskChild() { // GIVEN QFETCH(Domain::Task::Ptr, task); QFETCH(Akonadi::Item, item); QFETCH(bool, isParent); // WHEN Akonadi::Serializer serializer; bool value = serializer.isTaskChild(task, item); // THEN QCOMPARE(value, isParent); } void shouldRetrieveRelatedUidFromItem_data() { QTest::addColumn("item"); QTest::addColumn("expectedUid"); Akonadi::Item item1; KCalCore::Todo::Ptr todo1(new KCalCore::Todo); item1.setPayload(todo1); Akonadi::Item item2; KCalCore::Todo::Ptr todo2(new KCalCore::Todo); todo2->setRelatedTo(QStringLiteral("1")); item2.setPayload(todo2); Akonadi::Item item3; KMime::Message::Ptr message1(new KMime::Message); message1->subject(true)->fromUnicodeString(QStringLiteral("foo"), "utf-8"); message1->mainBodyPart()->fromUnicodeString(QStringLiteral("bar")); item3.setMimeType(Akonadi::NoteUtils::noteMimeType()); item3.setPayload(message1); Akonadi::Item item4; KMime::Message::Ptr message2(new KMime::Message); message2->subject(true)->fromUnicodeString(QStringLiteral("foo"), "utf-8"); message2->mainBodyPart()->fromUnicodeString(QStringLiteral("bar")); auto relatedHeader1 = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); relatedHeader1->from7BitString("1"); message2->appendHeader(relatedHeader1); item4.setMimeType(Akonadi::NoteUtils::noteMimeType()); item4.setPayload(message2); Akonadi::Item item5; KMime::Message::Ptr message3(new KMime::Message); message3->subject(true)->fromUnicodeString(QStringLiteral("foo"), "utf-8"); message3->mainBodyPart()->fromUnicodeString(QStringLiteral("bar")); auto relatedHeader2 = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); message3->appendHeader(relatedHeader2); item5.setMimeType(Akonadi::NoteUtils::noteMimeType()); item5.setPayload(message3); QTest::newRow("task without related") << item1 << QString(); QTest::newRow("task with related") << item2 << "1"; QTest::newRow("note without related") << item3 << QString(); QTest::newRow("note with related") << item4 << "1"; QTest::newRow("note with empty related") << item5 << QString(); } void shouldRetrieveRelatedUidFromItem() { // GIVEN QFETCH(Akonadi::Item, item); QFETCH(QString, expectedUid); // WHEN Akonadi::Serializer serializer; QString uid = serializer.relatedUidFromItem(item); // THEN QCOMPARE(uid, expectedUid); } void shouldCreateNoteFromItem_data() { QTest::addColumn("title"); QTest::addColumn("text"); QTest::addColumn("relatedUid"); QTest::newRow("nominal case (no related)") << "A note title" << "A note content.\nWith two lines." << QString(); QTest::newRow("nominal case (with related)") << "A note title" << "A note content.\nWith two lines." << "parent-uid"; QTest::newRow("trailing new lines") << "A note title" << "Empty lines at the end.\n\n\n" << QString(); QTest::newRow("empty case") << QString() << QString() << QString(); } void shouldCreateNoteFromItem() { // GIVEN // Data... QFETCH(QString, title); QFETCH(QString, text); QFETCH(QString, relatedUid); // ... stored in a message... KMime::Message::Ptr message(new KMime::Message); message->subject(true)->fromUnicodeString(title, "utf-8"); message->mainBodyPart()->fromUnicodeString(text); if (!relatedUid.isEmpty()) { auto relatedHeader = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); relatedHeader->from7BitString(relatedUid.toUtf8()); message->appendHeader(relatedHeader); } // ... as payload of an item. Akonadi::Item item; item.setMimeType(Akonadi::NoteUtils::noteMimeType()); item.setPayload(message); // WHEN Akonadi::Serializer serializer; Domain::Note::Ptr note = serializer.createNoteFromItem(item); auto artifact = serializer.createArtifactFromItem(item).dynamicCast(); // THEN const auto expectedText = text.endsWith('\n') ? (text.chop(1), text) : text; QCOMPARE(note->title(), title); QCOMPARE(note->text(), expectedText); QCOMPARE(note->property("itemId").toLongLong(), item.id()); QCOMPARE(note->property("relatedUid").toString(), relatedUid); QVERIFY(!artifact.isNull()); QCOMPARE(artifact->title(), title); QCOMPARE(artifact->text(), expectedText); QCOMPARE(artifact->property("itemId").toLongLong(), item.id()); QCOMPARE(artifact->property("relatedUid").toString(), relatedUid); } void shouldCreateNullNoteFromInvalidItem() { // GIVEN Akonadi::Item item; // WHEN Akonadi::Serializer serializer; Domain::Note::Ptr note = serializer.createNoteFromItem(item); auto artifact = serializer.createArtifactFromItem(item); // THEN QVERIFY(note.isNull()); QVERIFY(artifact.isNull()); } void shouldUpdateNoteFromItem_data() { QTest::addColumn("updatedTitle"); QTest::addColumn("updatedText"); QTest::addColumn("updatedRelatedUid"); QTest::newRow("no change") << "title" << "content" << "parent-uid"; QTest::newRow("data changed (with related)") << "A new title" << "A new content" << "new-parent-uid"; QTest::newRow("data changed (with no related)") << "A new title" << "A new content" << QString(); } void shouldUpdateNoteFromItem() { // GIVEN // A message... KMime::Message::Ptr message(new KMime::Message); message->subject(true)->fromUnicodeString(QStringLiteral("title"), "utf-8"); message->mainBodyPart()->fromUnicodeString(QStringLiteral("text")); auto relatedHeader = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); relatedHeader->from7BitString("parent-uid"); message->appendHeader(relatedHeader); //... as the payload of an item... Akonadi::Item item; item.setMimeType(Akonadi::NoteUtils::noteMimeType()); item.setPayload(message); //... deserialized as a note Akonadi::Serializer serializer; auto note = serializer.createNoteFromItem(item); auto artifact = serializer.createNoteFromItem(item); // WHEN // Data... QFETCH(QString, updatedTitle); QFETCH(QString, updatedText); QFETCH(QString, updatedRelatedUid); //... stored in a new message... KMime::Message::Ptr updatedMessage(new KMime::Message); updatedMessage->subject(true)->fromUnicodeString(updatedTitle, "utf-8"); updatedMessage->mainBodyPart()->fromUnicodeString(updatedText); if (!updatedRelatedUid.isEmpty()) { relatedHeader = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); relatedHeader->from7BitString(updatedRelatedUid.toUtf8()); updatedMessage->appendHeader(relatedHeader); } //... as the payload of a new item... Akonadi::Item updatedItem; updatedItem.setMimeType(Akonadi::NoteUtils::noteMimeType()); updatedItem.setPayload(updatedMessage); serializer.updateNoteFromItem(note, updatedItem); serializer.updateArtifactFromItem(artifact, updatedItem); // THEN QCOMPARE(note->title(), updatedTitle); QCOMPARE(note->text(), updatedText); QCOMPARE(note->property("itemId").toLongLong(), updatedItem.id()); QCOMPARE(note->property("relatedUid").toString(), updatedRelatedUid); note = artifact.dynamicCast(); QCOMPARE(note->title(), updatedTitle); QCOMPARE(note->text(), updatedText); QCOMPARE(note->property("itemId").toLongLong(), updatedItem.id()); QCOMPARE(note->property("relatedUid").toString(), updatedRelatedUid); } void shouldNotUpdateNoteFromInvalidItem() { // GIVEN // Data... QString title = QStringLiteral("A title"); QString text = QStringLiteral("A note content"); // ... stored in a message... KMime::Message::Ptr message(new KMime::Message); message->subject(true)->fromUnicodeString(title, "utf-8"); message->mainBodyPart()->fromUnicodeString(text); //... as the payload of an item... Akonadi::Item item; item.setMimeType(Akonadi::NoteUtils::noteMimeType()); item.setPayload(message); //... deserialized as a note Akonadi::Serializer serializer; auto note = serializer.createNoteFromItem(item); auto artifact = serializer.createArtifactFromItem(item); // WHEN Akonadi::Item invalidItem; serializer.updateNoteFromItem(note, invalidItem); serializer.updateArtifactFromItem(artifact, invalidItem); //THEN QCOMPARE(note->title(), title); QCOMPARE(note->text(), text); QCOMPARE(note->property("itemId").toLongLong(), item.id()); note = artifact.dynamicCast(); QCOMPARE(note->title(), title); QCOMPARE(note->text(), text); QCOMPARE(note->property("itemId").toLongLong(), item.id()); } void shouldCreateItemFromNote_data() { QTest::addColumn("title"); QTest::addColumn("content"); QTest::addColumn("expectedTitle"); QTest::addColumn("expectedContent"); QTest::addColumn("itemId"); QTest::addColumn("relatedUid"); QTest::newRow("nominal case (no id)") << "title" << "content" << "title" << "content" << qint64(-1) << QString(); QTest::newRow("empty case (no id)") << QString() << QString() << "New Note" << QString() << qint64(-1) << QString(); QTest::newRow("nominal case (with id)") << "title" << "content" << "title" << "content" << qint64(42) << "parent-uid"; QTest::newRow("empty case (with id)") << QString() << QString() << "New Note" << QString() << qint64(42) << "parent-uid"; QTest::newRow("empty line at the end") << "title" << "content\n\n\n" << "title" << "content\n\n\n" << qint64(-1) << QString(); } void shouldCreateItemFromNote() { // GIVEN // Data... QFETCH(QString, title); QFETCH(QString, content); QFETCH(qint64, itemId); QFETCH(QString, relatedUid); // ... stored in a note auto note = Domain::Note::Ptr::create(); note->setTitle(title); note->setText(content); if (itemId > 0) note->setProperty("itemId", itemId); if (!relatedUid.isEmpty()) note->setProperty("relatedUid", relatedUid); // WHEN Akonadi::Serializer serializer; auto item = serializer.createItemFromNote(note); // THEN QCOMPARE(item.mimeType(), Akonadi::NoteUtils::noteMimeType()); QCOMPARE(item.isValid(), itemId > 0); if (itemId > 0) { QCOMPARE(item.id(), itemId); } QFETCH(QString, expectedTitle); QFETCH(QString, expectedContent); auto message = item.payload(); QCOMPARE(message->subject(false)->asUnicodeString(), expectedTitle); QCOMPARE(message->mainBodyPart()->decodedText(), expectedContent); if (relatedUid.isEmpty()) { QVERIFY(!message->headerByType("X-Zanshin-RelatedProjectUid")); } else { QVERIFY(message->headerByType("X-Zanshin-RelatedProjectUid")); QCOMPARE(message->headerByType("X-Zanshin-RelatedProjectUid")->asUnicodeString(), relatedUid); } } void shouldCreateProjectFromItem_data() { QTest::addColumn("summary"); QTest::newRow("nominal case") << "summary"; QTest::newRow("empty case") << QString(); } void shouldCreateProjectFromItem() { // GIVEN // Data... QFETCH(QString, summary); // ... stored in a todo... KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(summary); todo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); QVERIFY(!todo->uid().isEmpty()); // ... as payload of an item Akonadi::Item item(42); item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); // which has a prent collection Akonadi::Collection collection(43); item.setParentCollection(collection); // WHEN Akonadi::Serializer serializer; Domain::Project::Ptr project = serializer.createProjectFromItem(item); // THEN QCOMPARE(project->name(), summary); QCOMPARE(project->property("itemId").toLongLong(), item.id()); QCOMPARE(project->property("parentCollectionId").toLongLong(), collection.id()); QCOMPARE(project->property("todoUid").toString(), todo->uid()); } void shouldCreateNullProjectFromInvalidItem() { // GIVEN Akonadi::Item item; // WHEN Akonadi::Serializer serializer; Domain::Project::Ptr project = serializer.createProjectFromItem(item); // THEN QVERIFY(project.isNull()); } void shouldCreateNullProjectFromTaskItem() { // GIVEN // A todo without the project flag KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(QStringLiteral("foo")); // ... as payload of an item Akonadi::Item item; item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); // WHEN Akonadi::Serializer serializer; Domain::Project::Ptr project = serializer.createProjectFromItem(item); // THEN QVERIFY(project.isNull()); } void shouldUpdateProjectFromItem_data() { QTest::addColumn("updatedSummary"); QTest::newRow("no change") << "summary"; QTest::newRow("changed") << "new summary"; } void shouldUpdateProjectFromItem() { // GIVEN // A todo... KCalCore::Todo::Ptr originalTodo(new KCalCore::Todo); originalTodo->setSummary(QStringLiteral("summary")); originalTodo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); // ... as payload of an item... Akonadi::Item originalItem(42); originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); originalItem.setPayload(originalTodo); // ... which has a parent collection... Akonadi::Collection originalCollection(43); originalItem.setParentCollection(originalCollection); // ... deserialized as a project Akonadi::Serializer serializer; auto project = serializer.createProjectFromItem(originalItem); // WHEN // Data... QFETCH(QString, updatedSummary); // ... in a new todo... KCalCore::Todo::Ptr updatedTodo(new KCalCore::Todo); updatedTodo->setSummary(updatedSummary); updatedTodo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); QVERIFY(!updatedTodo->uid().isEmpty()); // ... as payload of a new item Akonadi::Item updatedItem(44); updatedItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); updatedItem.setPayload(updatedTodo); // ... which has a new parent collection Akonadi::Collection updatedCollection(45); updatedItem.setParentCollection(updatedCollection); serializer.updateProjectFromItem(project, updatedItem); // THEN QCOMPARE(project->name(), updatedSummary); QCOMPARE(project->property("itemId").toLongLong(), updatedItem.id()); QCOMPARE(project->property("parentCollectionId").toLongLong(), updatedCollection.id()); QCOMPARE(project->property("todoUid").toString(), updatedTodo->uid()); } void shouldNotUpdateProjectFromInvalidItem() { // GIVEN // Data... const QString summary = QStringLiteral("summary"); // ... stored in a todo... KCalCore::Todo::Ptr originalTodo(new KCalCore::Todo); originalTodo->setSummary(summary); originalTodo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); // ... as payload of an item... Akonadi::Item originalItem; originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); originalItem.setPayload(originalTodo); // ... deserialized as a project Akonadi::Serializer serializer; auto project = serializer.createProjectFromItem(originalItem); // WHEN Akonadi::Item invalidItem; serializer.updateProjectFromItem(project, invalidItem); // THEN QCOMPARE(project->name(), summary); } void shouldNotUpdateProjectFromTaskItem() { // GIVEN // Data... const QString summary = QStringLiteral("summary"); // ... stored in a todo... KCalCore::Todo::Ptr originalTodo(new KCalCore::Todo); originalTodo->setSummary(summary); originalTodo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); // ... as payload of an item... Akonadi::Item originalItem; originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); originalItem.setPayload(originalTodo); // ... deserialized as a project Akonadi::Serializer serializer; auto project = serializer.createProjectFromItem(originalItem); // WHEN // A todo without the project flag KCalCore::Todo::Ptr projectTodo(new KCalCore::Todo); projectTodo->setSummary(QStringLiteral("foo")); // ... as payload of an item Akonadi::Item projectItem; projectItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); projectItem.setPayload(projectTodo); serializer.updateProjectFromItem(project, projectItem); // THEN QCOMPARE(project->name(), summary); } void shouldCreateItemFromProject_data() { QTest::addColumn("summary"); QTest::addColumn("itemId"); QTest::addColumn("parentCollectionId"); QTest::newRow("nominal case (no id)") << "summary" << qint64(-1) << qint64(-1); QTest::newRow("empty case (no id)") << QString() << qint64(-1) << qint64(-1); QTest::newRow("nominal case (with id)") << "summary" << qint64(42) << qint64(43); QTest::newRow("empty case (with id)") << QString() << qint64(42) << qint64(43); } void shouldCreateItemFromProject() { // GIVEN // Data... QFETCH(QString, summary); QFETCH(qint64, itemId); QFETCH(qint64, parentCollectionId); const QString todoUid = QStringLiteral("test-uid"); // ... stored in a project auto project = Domain::Project::Ptr::create(); project->setName(summary); project->setProperty("todoUid", todoUid); if (itemId > 0) project->setProperty("itemId", itemId); if (parentCollectionId > 0) project->setProperty("parentCollectionId", parentCollectionId); // WHEN Akonadi::Serializer serializer; auto item = serializer.createItemFromProject(project); // THEN QCOMPARE(item.mimeType(), KCalCore::Todo::todoMimeType()); QCOMPARE(item.isValid(), itemId > 0); if (itemId > 0) { QCOMPARE(item.id(), itemId); } QCOMPARE(item.parentCollection().isValid(), parentCollectionId > 0); if (parentCollectionId > 0) { QCOMPARE(item.parentCollection().id(), parentCollectionId); } auto todo = item.payload(); QCOMPARE(todo->summary(), summary); QCOMPARE(todo->uid(), todoUid); QVERIFY(!todo->customProperty("Zanshin", "Project").isEmpty()); } void shouldVerifyIfAnItemIsAProjectChild_data() { QTest::addColumn("project"); QTest::addColumn("item"); QTest::addColumn("isParent"); // Create project auto project = Domain::Project::Ptr::create(); project->setName(QStringLiteral("project")); project->setProperty("todoUid", "1"); // Create unrelated todo auto unrelatedTodo = KCalCore::Todo::Ptr::create(); unrelatedTodo->setSummary(QStringLiteral("summary")); Akonadi::Item unrelatedTodoItem; unrelatedTodoItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); unrelatedTodoItem.setPayload(unrelatedTodo); QTest::newRow("unrelated todo") << project << unrelatedTodoItem << false; // Create child todo auto childTodo = KCalCore::Todo::Ptr::create(); childTodo->setSummary(QStringLiteral("summary")); childTodo->setRelatedTo(QStringLiteral("1")); Akonadi::Item childTodoItem; childTodoItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); childTodoItem.setPayload(childTodo); QTest::newRow("child todo") << project << childTodoItem << true; // Create unrelated note KMime::Message::Ptr unrelatedNote(new KMime::Message); unrelatedNote->subject(true)->fromUnicodeString(QStringLiteral("subject"), "utf-8"); Akonadi::Item unrelatedNoteItem; unrelatedNoteItem.setMimeType(Akonadi::NoteUtils::noteMimeType()); unrelatedNoteItem.setPayload(unrelatedNote); QTest::newRow("unrelated note") << project << unrelatedNoteItem << false; // Create child note KMime::Message::Ptr childNote(new KMime::Message); childNote->subject(true)->fromUnicodeString(QStringLiteral("subject"), "utf-8"); auto relatedHeader = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); relatedHeader->from7BitString("1"); childNote->appendHeader(relatedHeader); Akonadi::Item childNoteItem; childNoteItem.setMimeType(Akonadi::NoteUtils::noteMimeType()); childNoteItem.setPayload(childNote); QTest::newRow("child todo") << project << childNoteItem << true; auto invalidProject = Domain::Project::Ptr::create(); QTest::newRow("invalid project") << invalidProject << unrelatedNoteItem << false; Akonadi::Item invalidItem; QTest::newRow("invalid item") << project << invalidItem << false; } void shouldVerifyIfAnItemIsAProjectChild() { // GIVEN QFETCH(Domain::Project::Ptr, project); QFETCH(Akonadi::Item, item); QFETCH(bool, isParent); // WHEN Akonadi::Serializer serializer; bool value = serializer.isProjectChild(project, item); // THEN QCOMPARE(value, isParent); } void shouldUpdateItemParent_data() { QTest::addColumn("item"); QTest::addColumn("parent"); QTest::addColumn("expectedRelatedToUid"); Akonadi::Item item1; KCalCore::Todo::Ptr todo1(new KCalCore::Todo); item1.setPayload(todo1); Domain::Task::Ptr parent(new Domain::Task); parent->setProperty("todoUid", "1"); QTest::newRow("nominal case") << item1 << parent << "1"; Akonadi::Item item2; QTest::newRow("update item without payload") << item2 << parent << QString(); Domain::Task::Ptr parent2(new Domain::Task); QTest::newRow("update item with a empty parent uid") << item1 << parent2 << QString(); } void shouldUpdateItemParent() { // GIVEN QFETCH(Akonadi::Item, item); QFETCH(Domain::Task::Ptr, parent); QFETCH(QString, expectedRelatedToUid); // WHEN Akonadi::Serializer serializer; serializer.updateItemParent(item, parent); // THEN if (item.hasPayload()) { auto todo = item.payload(); QString relatedUid = todo->relatedTo(); QCOMPARE(relatedUid, expectedRelatedToUid); } } void shouldUpdateItemProject_data() { QTest::addColumn("item"); QTest::addColumn("parent"); QTest::addColumn("expectedRelatedToUid"); Akonadi::Item todoItem; KCalCore::Todo::Ptr todo(new KCalCore::Todo); todoItem.setPayload(todo); auto parent = Domain::Project::Ptr::create(); parent->setProperty("todoUid", "1"); QTest::newRow("nominal todo case") << todoItem << parent << "1"; auto invalidParent = Domain::Project::Ptr::create(); QTest::newRow("update todo item with a empty parent uid") << todoItem << invalidParent << QString(); Akonadi::Item noteItem; KMime::Message::Ptr note(new KMime::Message); noteItem.setPayload(note); QTest::newRow("nominal note case") << noteItem << parent << "1"; QTest::newRow("update note item with a empty parent uid") << noteItem << invalidParent << QString(); Akonadi::Item invalidItem; QTest::newRow("update item without payload") << invalidItem << parent << QString(); } void shouldUpdateItemProject() { // GIVEN QFETCH(Akonadi::Item, item); QFETCH(Domain::Project::Ptr, parent); QFETCH(QString, expectedRelatedToUid); // WHEN Akonadi::Serializer serializer; serializer.updateItemProject(item, parent); // THEN if (item.hasPayload()) { auto todo = item.payload(); const QString relatedUid = todo->relatedTo(); QCOMPARE(relatedUid, expectedRelatedToUid); } else if (item.hasPayload()) { auto note = item.payload(); const auto relatedHeader = note->headerByType("X-Zanshin-RelatedProjectUid"); const QString relatedUid = relatedHeader ? relatedHeader->asUnicodeString() : QString(); QCOMPARE(relatedUid, expectedRelatedToUid); if (!expectedRelatedToUid.isEmpty()) QVERIFY(note->encodedContent().contains(QStringLiteral("X-Zanshin-RelatedProjectUid: %1").arg(expectedRelatedToUid).toUtf8())); else QVERIFY(!note->encodedContent().contains("X-Zanshin-RelatedProjectUid:")); } } void shouldFilterChildrenItem_data() { QTest::addColumn("item"); QTest::addColumn("items"); QTest::addColumn("size"); Akonadi::Item item(12); KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setUid(QStringLiteral("1")); item.setPayload(todo); Akonadi::Item::List items; QTest::newRow("empty list") << item << items << 0; Akonadi::Item item2(13); KCalCore::Todo::Ptr todo2(new KCalCore::Todo); item2.setPayload(todo2); Akonadi::Item::List items2; items2 << item2; QTest::newRow("list without child") << item << items2 << 0; Akonadi::Item item3(14); KCalCore::Todo::Ptr todo3(new KCalCore::Todo); todo3->setUid(QStringLiteral("3")); todo3->setRelatedTo(QStringLiteral("1")); item3.setPayload(todo3); Akonadi::Item::List items3; items3 << item2 << item3; QTest::newRow("list with child") << item << items3 << 1; Akonadi::Item item4(15); KCalCore::Todo::Ptr todo4(new KCalCore::Todo); todo4->setRelatedTo(QStringLiteral("3")); item4.setPayload(todo4); Akonadi::Item::List items4; items4 << item2 << item3 << item4; QTest::newRow("list with child with a child") << item << items4 << 2; Akonadi::Item::List items5; items5 << item << item2 << item3 << item4; QTest::newRow("list with filter in list") << item << items5 << 2; } void shouldFilterChildrenItem() { // GIVEN QFETCH(Akonadi::Item, item); QFETCH(Akonadi::Item::List, items); QFETCH(int, size); // WHEN Akonadi::Serializer serializer; Akonadi::Item::List list = serializer.filterDescendantItems(items, item); // THEN QCOMPARE(list.size(), size); } void shouldRemoveItemParent_data() { QTest::addColumn("item"); Akonadi::Item item(15); KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setRelatedTo(QStringLiteral("3")); item.setPayload(todo); QTest::newRow("nominal case") << item; Akonadi::Item item2(16); QTest::newRow("parent invalid") << item2; } void shouldRemoveItemParent() { // GIVEN QFETCH(Akonadi::Item, item); // WHEN Akonadi::Serializer serializer; serializer.removeItemParent(item); // THEN if (item.hasPayload()) QCOMPARE(item.payload()->relatedTo(), QString()); } void shouldPromoteItemToProject_data() { QTest::addColumn("item"); auto item = Akonadi::Item(15); auto todo = KCalCore::Todo::Ptr::create(); todo->setRelatedTo(QStringLiteral("3")); item.setPayload(todo); QTest::newRow("nominal case") << item; QTest::newRow("invalid item") << Akonadi::Item(16); } void shouldPromoteItemToProject() { // GIVEN QFETCH(Akonadi::Item, item); // WHEN Akonadi::Serializer serializer; serializer.promoteItemToProject(item); // THEN if (item.hasPayload()) { auto todo = item.payload(); QCOMPARE(todo->relatedTo(), QString()); QVERIFY(!todo->customProperty("Zanshin", "Project").isEmpty()); } } void shouldClearItem_data() { QTest::addColumn("item"); Akonadi::Item *itemWithContent = new Akonadi::Item(15); KCalCore::Todo::Ptr todo(new KCalCore::Todo); // context Akonadi::Tag context(QStringLiteral("42")); context.setType( Akonadi::SerializerInterface::contextTagType() ); // tag Akonadi::Tag tag(QStringLiteral("43")); tag.setType( Akonadi::Tag::PLAIN ); Akonadi::Tag::List tagsList = Akonadi::Tag::List() << tag << context; itemWithContent->setTags(tagsList); itemWithContent->setPayload(todo); QTest::newRow("nominal case") << itemWithContent; Akonadi::Item *item2 = new Akonadi::Item(16); QTest::newRow("parent invalid") << item2; } void shouldClearItem() { // GIVEN QFETCH(Akonadi::Item*, item); // WHEN Akonadi::Serializer serializer; serializer.clearItem(item); // THEN QCOMPARE(item->tags().size(), 0); delete item; } void shouldCreateContextFromTag_data() { QTest::addColumn("type"); QTest::addColumn("name"); QTest::addColumn("tagId"); const QByteArray rightTagType = Akonadi::Serializer::contextTagType() ; QTest::newRow("nominal case") << rightTagType << "Context42" << Akonadi::Tag::Id(42); QTest::newRow("empty name case") << rightTagType << "" << Akonadi::Tag::Id(43); } void shouldCreateContextFromTag() { // GIVEN // Data... QFETCH(QByteArray, type); QFETCH(QString, name); QFETCH(Akonadi::Tag::Id, tagId); // ... stored as an Akonadi Tag Akonadi::Tag tag(name); tag.setType(type); tag.setId(tagId); // WHEN Akonadi::Serializer serializer; Domain::Context::Ptr context = serializer.createContextFromTag(tag); // THEN QCOMPARE(context->name(), tag.name()); QCOMPARE(context->property("tagId").toLongLong(), tag.id()); } void shouldNotCreateContextFromWrongTagType() { // GIVEN // Data stored as an Akonadi Tag Akonadi::Tag tag(QStringLiteral("context42")); tag.setType(QByteArray("wrongTagType")); // WHEN Akonadi::Serializer serializer; Domain::Context::Ptr context = serializer.createContextFromTag(tag); // THEN QVERIFY(!context); } void shouldUpdateContextFromTag_data() { shouldCreateContextFromTag_data(); } void shouldUpdateContextFromTag() { // GIVEN // Data... QFETCH(QByteArray, type); QFETCH(QString, name); QFETCH(Akonadi::Tag::Id, tagId); // ... stored as an Akonadi Tag Akonadi::Tag tag(name); tag.setType(type); tag.setId(tagId); // WHEN Akonadi::Serializer serializer; Domain::Context::Ptr context(new Domain::Context); serializer.updateContextFromTag(context, tag); // THEN QCOMPARE(context->name(), tag.name()); QCOMPARE(context->property("tagId").toLongLong(), tag.id()); } void shouldNotUpdateContextFromWrongTagType() { // GIVEN Akonadi::Tag originalTag(QStringLiteral("Context42")); originalTag.setType(Akonadi::Serializer::contextTagType()); originalTag.setId(42); Akonadi::Serializer serializer; Domain::Context::Ptr context = serializer.createContextFromTag(originalTag); // WHEN Akonadi::Tag wrongTag(QStringLiteral("WrongContext42")); wrongTag.setType(QByteArray("wrongTypeTag")); serializer.updateContextFromTag(context, wrongTag); // THEN QCOMPARE(context->name(), originalTag.name()); QCOMPARE(context->property("tagId").toLongLong(), originalTag.id()); } void shouldVerifyIfAnItemIsAContextChild_data() { QTest::addColumn("context"); QTest::addColumn("item"); QTest::addColumn("isChild"); // Create a context auto context = Domain::Context::Ptr::create(); context->setProperty("tagId", qint64(43)); Akonadi::Tag tag(Akonadi::Tag::Id(43)); Akonadi::Item unrelatedItem; QTest::newRow("Unrelated item") << context << unrelatedItem << false; Akonadi::Item relatedItem; relatedItem.setTag(tag); QTest::newRow("Related item") << context << relatedItem << true; auto invalidContext = Domain::Context::Ptr::create(); QTest::newRow("Invalid context") << invalidContext << relatedItem << false; Akonadi::Item invalidItem; QTest::newRow("Invalid Item") << context << invalidItem << false; } void shouldVerifyIfAnItemIsAContextChild() { // GIVEN QFETCH(Domain::Context::Ptr, context); QFETCH(Akonadi::Item, item); QFETCH(bool, isChild); // WHEN Akonadi::Serializer serializer; bool value = serializer.isContextChild(context, item); // THEN QCOMPARE(value, isChild); } void shouldCheckIfAnItemHasContextsOrTags_data() { QTest::addColumn("item"); QTest::addColumn("contextsExpected"); QTest::addColumn("tagsExpected"); Akonadi::Tag unrelatedTag(QStringLiteral("Foo")); unrelatedTag.setType("unrelated"); Akonadi::Tag contextTag(QStringLiteral("Bar")); contextTag.setType(Akonadi::Serializer::contextTagType()); Akonadi::Tag akonadiTag(QStringLiteral("Baz")); akonadiTag.setType(Akonadi::Tag::PLAIN); Akonadi::Item item; QTest::newRow("no tags") << item << false << false; item.setTags({ unrelatedTag }); QTest::newRow("unrelated tags") << item << false << false; item.setTags({ unrelatedTag, contextTag }); QTest::newRow("has contexts") << item << true << false; item.setTags({ unrelatedTag, akonadiTag }); QTest::newRow("has tags") << item << false << true; item.setTags({ unrelatedTag, contextTag, akonadiTag }); QTest::newRow("has both") << item << true << true; } void shouldCheckIfAnItemHasContextsOrTags() { // GIVEN QFETCH(Akonadi::Item, item); QFETCH(bool, contextsExpected); QFETCH(bool, tagsExpected); Akonadi::Serializer serializer; // WHEN const bool hasContexts = serializer.hasContextTags(item); const bool hasTags = serializer.hasAkonadiTags(item); // THEN QCOMPARE(hasContexts, contextsExpected); QCOMPARE(hasTags, tagsExpected); } void shouldCreateTagFromContext_data() { QTest::addColumn("name"); QTest::addColumn("tagId"); QTest::addColumn("tagGid"); QString nameInternet = QStringLiteral("Internet"); QTest::newRow("nominal case") << QString(nameInternet) << qint64(42) << nameInternet.toLatin1(); QTest::newRow("null name case") << QString() << qint64(42) << QByteArray(); QTest::newRow("null tagId case") << QString(nameInternet)<< qint64(-1) << nameInternet.toLatin1(); QTest::newRow("totally null context case") << QString() << qint64(-1) << QByteArray(); } void shouldCreateTagFromContext() { // GIVEN QFETCH(QString, name); QFETCH(qint64, tagId); QFETCH(QByteArray, tagGid); // WHEN auto context = Domain::Context::Ptr::create(); context->setProperty("tagId", tagId); context->setName(name); Akonadi::Serializer serializer; Akonadi::Tag tag = serializer.createTagFromContext(context); // THEN QCOMPARE(tag.name(), name); QCOMPARE(tag.isValid(), tagId > 0); if (tagId > 0) { QCOMPARE(tag.id(), tagId); QCOMPARE(tag.gid(), tagGid); QCOMPARE(tag.type(), Akonadi::SerializerInterface::contextTagType()); } } void shouldCreateTagFromAkonadiTag_data() { QTest::addColumn("name"); QTest::addColumn("tagId"); QTest::addColumn("type"); QString tagName = QStringLiteral("Optional"); QByteArray plainType = Akonadi::Tag::PLAIN; QTest::newRow("nominal case") << tagName << qint64(42) << plainType; QTest::newRow("null name case") << QString() << qint64(42) << plainType; QTest::newRow("null tagId case") << tagName << qint64(-1) << plainType; QTest::newRow("totally null tag case") << QString() << qint64(-1) << plainType; } void shouldCreateTagFromAkonadiTag() { // GIVEN QFETCH(QString, name); QFETCH(qint64, tagId); QFETCH(QByteArray, type); auto akonadiTag = Akonadi::Tag(); akonadiTag.setName(name); akonadiTag.setId(tagId); akonadiTag.setType(type); // WHEN Akonadi::Serializer serializer; Domain::Tag::Ptr resultTag = serializer.createTagFromAkonadiTag(akonadiTag); // THEN QCOMPARE(resultTag->name(), akonadiTag.name()); QCOMPARE(resultTag->property("tagId").toLongLong(), akonadiTag.id()); } void shouldUpdateTagFromAkonadiTag_data() { shouldCreateTagFromAkonadiTag_data(); } void shouldUpdateTagFromAkonadiTag() { // GIVEN QFETCH(QString, name); QFETCH(qint64, tagId); QFETCH(QByteArray, type); // ... stored as an Akonadi Tag Akonadi::Tag akonadiTag(name); akonadiTag.setId(tagId); akonadiTag.setType(type); // WHEN Akonadi::Serializer serializer; auto tag = Domain::Tag::Ptr::create(); tag->setName(QStringLiteral("tag42")); serializer.updateTagFromAkonadiTag(tag, akonadiTag); // THEN QCOMPARE(tag->name(), akonadiTag.name()); QCOMPARE(tag->property("tagId").toLongLong(), akonadiTag.id()); } void shouldCreateAkonadiTagFromTag_data() { // GIVEN QTest::addColumn("name"); QTest::addColumn("tagId"); QTest::addColumn("tagGid"); const QByteArray namePhilo = "Philosophy"; QTest::newRow("nominal case") << QString(namePhilo) << qint64(42) << namePhilo; QTest::newRow("null name case") << QString() << qint64(42) << QByteArray(); QTest::newRow("null tagId case") << QString(namePhilo) << qint64(-1) << namePhilo; QTest::newRow("totally null tag case") << QString() << qint64(-1) << QByteArray(); } void shouldCreateAkonadiTagFromTag() { // GIVEN QFETCH(QString, name); QFETCH(qint64, tagId); QFETCH(QByteArray, tagGid); // WHEN auto tag = Domain::Tag::Ptr::create(); tag->setProperty("tagId", tagId); tag->setName(name); Akonadi::Serializer serializer; Akonadi::Tag akonadiTag = serializer.createAkonadiTagFromTag(tag); // THEN QCOMPARE(akonadiTag.name(), name); QCOMPARE(akonadiTag.isValid(), tagId > 0); if (tagId > 0) { QCOMPARE(akonadiTag.id(), tagId); QCOMPARE(akonadiTag.gid(), tagGid); QCOMPARE(akonadiTag.type(), QByteArray(Akonadi::Tag::PLAIN)); } } void shouldVerifyIfAnItemIsATagChild_data() { QTest::addColumn("tag"); QTest::addColumn("item"); QTest::addColumn("isChild"); // Create a Tag auto tag = Domain::Tag::Ptr::create(); tag->setProperty("tagId", qint64(43)); Akonadi::Tag akonadiTag(Akonadi::Tag::Id(43)); Akonadi::Item unrelatedItem; QTest::newRow("Unrelated item") << tag << unrelatedItem << false; Akonadi::Item relatedItem; relatedItem.setTag(akonadiTag); QTest::newRow("Related item") << tag << relatedItem << true; auto invalidTag = Domain::Tag::Ptr::create(); QTest::newRow("Invalid Tag") << invalidTag << relatedItem << false; Akonadi::Item invalidItem; QTest::newRow("Invalid Item") << tag << invalidItem << false; QTest::newRow("both invalid") << invalidTag << invalidItem << false; } void shouldVerifyIfAnItemIsATagChild() { // GIVEN QFETCH(Domain::Tag::Ptr, tag); QFETCH(Akonadi::Item, item); QFETCH(bool, isChild); // WHEN Akonadi::Serializer serializer; bool value = serializer.isTagChild(tag, item); // THEN QCOMPARE(value, isChild); } // Investigation into how to differentiate all-day events from events with time, // using QDateTime only. Doesn't seem to be possible. void KDateTimeShouldStillBeNeeded() // although I wish it wasn't... { // GIVEN a QDateTime without time information QDateTime dateOnly(QDate(2016, 6, 12), QTime(-1, -1, -1)); // THEN we can't detect that there was no time information, i.e. all day event QVERIFY(dateOnly.time().isValid()); // I wish this was "!" QVERIFY(!dateOnly.time().isNull()); // got converted to midnight localtime by QDateTime // This doesn't help, QDateTime converts "null time" to midnight. dateOnly.setTime(QTime()); QVERIFY(dateOnly.time().isValid()); // same as above QVERIFY(!dateOnly.time().isNull()); // same as above // GIVEN a QDateTime at midnight QDateTime atMidnight(QDate(2016, 6, 12), QTime(0, 0, 0)); // THEN we can detect that a time information was present QVERIFY(atMidnight.time().isValid()); QVERIFY(!atMidnight.time().isNull()); // GIVEN a KDateTime without time information KDateTime kdOnly(QDate(2016, 6, 12)); // THEN we can detect that there was no time information, i.e. all day event QVERIFY(kdOnly.isDateOnly()); } }; ZANSHIN_TEST_MAIN(AkonadiSerializerTest) #include "akonadiserializertest.moc" diff --git a/tests/units/akonadi/akonaditaskqueriestest.cpp b/tests/units/akonadi/akonaditaskqueriestest.cpp index 9b3b0824..6e4c5ea3 100644 --- a/tests/units/akonadi/akonaditaskqueriestest.cpp +++ b/tests/units/akonadi/akonaditaskqueriestest.cpp @@ -1,1529 +1,1529 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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 "akonadi/akonaditaskqueries.h" #include "akonadi/akonadiserializer.h" #include "testlib/akonadifakedata.h" #include "testlib/gencollection.h" #include "testlib/gennote.h" #include "testlib/gentag.h" #include "testlib/gentodo.h" #include "testlib/testhelpers.h" #include "utils/datetime.h" using namespace Testlib; class AkonadiTaskQueriesTest : public QObject { Q_OBJECT private slots: void shouldLookInAllReportedForAllTasks() { // GIVEN AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); // One task in the first collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); // Two tasks in the second collection data.createItem(GenTodo().withId(43).withParent(43).withTitle(QStringLiteral("43"))); data.createItem(GenTodo().withId(44).withParent(43).withTitle(QStringLiteral("44"))); // WHEN QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findAll(); result->data(); result = queries->findAll(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 3); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); QCOMPARE(result->data().at(2)->title(), QStringLiteral("44")); } void shouldIgnoreItemsWhichAreNotTasks() { // GIVEN AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Two items in the collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); // One of them is not a task auto item = Akonadi::Item(43); item.setPayloadFromData("FooBar"); item.setParentCollection(Akonadi::Collection(42)); data.createItem(item); // WHEN QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findAll(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } void shouldReactToItemAddsForTasksOnly() { // GIVEN AkonadiFakeData data; // One empty collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findAll(); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); // WHEN data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); auto item = Akonadi::Item(43); item.setPayloadFromData("FooBar"); item.setParentCollection(Akonadi::Collection(42)); data.createItem(item); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().first()->title(), QStringLiteral("42")); } void shouldReactToItemRemovesForAllTasks() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three task in the collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43"))); data.createItem(GenTodo().withId(44).withParent(42).withTitle(QStringLiteral("44"))); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findAll(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 3); // WHEN data.removeItem(Akonadi::Item(43)); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("44")); } void shouldReactToItemChangesForAllTasks() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three task in the collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43"))); data.createItem(GenTodo().withId(44).withParent(42).withTitle(QStringLiteral("44"))); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findAll(); // Even though the pointer didn't change it's convenient to user if we call // the replace handlers bool replaceHandlerCalled = false; result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::Task::Ptr &, int) { replaceHandlerCalled = true; }); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 3); // WHEN data.modifyItem(GenTodo(data.item(43)).withTitle(QStringLiteral("43bis"))); // THEN QCOMPARE(result->data().size(), 3); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43bis")); QCOMPARE(result->data().at(2)->title(), QStringLiteral("44")); QVERIFY(replaceHandlerCalled); } void shouldLookInAllChildrenReportedForAllChildrenTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (two being children of the first one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")) .withParentUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); // WHEN auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto task = serializer->createTaskFromItem(data.item(42)); auto result = queries->findChildren(task); result->data(); result = queries->findChildren(task); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("43")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("44")); // Should not change nothing result = queries->findChildren(task); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("43")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("44")); } void shouldNotCrashWhenWeAskAgainTheSameChildrenList() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // One task in the collection data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto task = serializer->createTaskFromItem(data.item(42)); // The bug we're trying to hit here is the following: // - when findChildren is called the first time a provider is created internally // - result is deleted at the end of the loop, no one holds the provider with // a strong reference anymore so it is deleted as well // - when findChildren is called the second time, there's a risk of a dangling // pointer if the recycling of providers is wrongly implemented which can lead // to a crash, if it is properly done no crash will occur for (int i = 0; i < 2; i++) { // WHEN * 2 auto result = queries->findChildren(task); // THEN * 2 QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); } } void shouldReactToItemAddsForChildrenTask() { // GIVEN AkonadiFakeData data; // One top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // One task in the collection data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto task = serializer->createTaskFromItem(data.item(42)); auto result = queries->findChildren(task); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); // WHEN data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")) .withParentUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("43")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("44")); } void shouldReactToItemChangesForChildrenTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (two being children of the first one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")) .withParentUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto task = serializer->createTaskFromItem(data.item(42)); auto result = queries->findChildren(task); bool replaceHandlerCalled = false; result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::Task::Ptr &, int) { replaceHandlerCalled = true; }); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.modifyItem(GenTodo(data.item(43)).withTitle(QStringLiteral("43bis"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("43bis")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("44")); QVERIFY(replaceHandlerCalled); } void shouldRemoveItemFromCorrespondingResultWhenRelatedItemChangeForChildrenTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (two being children of the first one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")) .withParentUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto task = serializer->createTaskFromItem(data.item(42)); auto result = queries->findChildren(task); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.modifyItem(GenTodo(data.item(43)).withParentUid(QLatin1String(""))); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("44")); } void shouldAddItemToCorrespondingResultWhenRelatedItemChangeForChildrenTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (two being top level) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto task = serializer->createTaskFromItem(data.item(42)); auto result = queries->findChildren(task); bool replaceHandlerCalled = false; result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::Task::Ptr &, int) { replaceHandlerCalled = true; }); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); // WHEN data.modifyItem(GenTodo(data.item(43)).withParentUid(QStringLiteral("uid-42"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("44")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); QVERIFY(!replaceHandlerCalled); } void shouldMoveItemToCorrespondingResultWhenRelatedItemChangeForChildTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (two being top level) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto task1 = serializer->createTaskFromItem(data.item(42)); auto task2 = serializer->createTaskFromItem(data.item(43)); auto result1 = queries->findChildren(task1); auto result2 = queries->findChildren(task2); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result1->data().size(), 1); QCOMPARE(result1->data().at(0)->title(), QStringLiteral("44")); QCOMPARE(result2->data().size(), 0); // WHEN data.modifyItem(GenTodo(data.item(44)).withParentUid(QStringLiteral("uid-43"))); // THEN QCOMPARE(result1->data().size(), 0); QCOMPARE(result2->data().size(), 1); QCOMPARE(result2->data().at(0)->title(), QStringLiteral("44")); } void shouldReactToItemRemovesForChildrenTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (two being children of the first one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")) .withParentUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto task = serializer->createTaskFromItem(data.item(42)); auto result = queries->findChildren(task); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.removeItem(Akonadi::Item(43)); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("44")); } void shouldLookInAllReportedForTopLevelTasks() { // GIVEN AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); // One task in the first collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); // Two tasks in the second collection data.createItem(GenTodo().withId(43).withParent(43).withTitle(QStringLiteral("43"))); data.createItem(GenTodo().withId(44).withParent(43).withTitle(QStringLiteral("44")).withParentUid(QStringLiteral("2"))); // WHEN QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findTopLevel(); result->data(); result = queries->findTopLevel(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); } void shouldReactToItemAddsForTopLevelTasks() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findTopLevel(); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); // WHEN data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43")).withParentUid(QStringLiteral("2"))); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().first()->title(), QStringLiteral("42")); } void shouldReactToItemRemovesForTopLevelTasks() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (one being child of the second one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-43"))); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.removeItem(Akonadi::Item(43)); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } void shouldReactToItemChangesForTopLevelTasks() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (one being child of the second one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-43"))); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findTopLevel(); // Even though the pointer didn't change it's convenient to user if we call // the replace handlers bool replaceHandlerCalled = false; result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::Task::Ptr &, int) { replaceHandlerCalled = true; }); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.modifyItem(GenTodo(data.item(43)).withTitle(QStringLiteral("43bis"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43bis")); QVERIFY(replaceHandlerCalled); } void shouldRemoveItemFromTopLevelResultWhenRelatedItemChangeForTopLevelTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (one being child of the second one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-43"))); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.modifyItem(GenTodo(data.item(43)).withParentUid(QStringLiteral("uid-42"))); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } void shouldAddItemToTopLevelResultWhenRelatedItemChangeForChildrenTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (one being child of the second one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")) .withParentUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-43"))); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); // WHEN data.modifyItem(GenTodo(data.item(43)).withParentUid(QString())); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); } void shouldRemoveParentNodeAndMoveChildrenInTopLevelResult() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (one being child of the second one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-43"))); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); auto resultChild = queries->findChildren(result->data().at(1)); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(resultChild->data().size(), 1); // WHEN data.removeItem(Akonadi::Item(43)); // THEN QCOMPARE(resultChild->data().size(), 0); QCOMPARE(result->data().size(), 1); // FIXME: Should become 2 once we got a proper cache in place } void shouldNotCrashDuringFindChildrenWhenJobIsKilled() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (two being children of the first one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); data.storageBehavior().setFetchItemErrorCode(42, KJob::KilledJobError); // WHEN auto task1 = serializer->createTaskFromItem(data.item(42)); auto result = queries->findChildren(task1); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); } void shouldNotCrashDuringFindChildrenWhenItemsJobReceiveResult_data() { QTest::addColumn("errorCode"); QTest::addColumn("fetchBehavior"); QTest::addColumn("deleteQuery"); QTest::newRow("No error with empty list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("Error with empty list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << true; QTest::newRow("Error with list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << true; } void shouldNotCrashDuringFindChildrenWhenItemsJobReceiveResult() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (two being children of the first one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); auto storage = Akonadi::StorageInterface::Ptr(data.createStorage()); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto monitor = Akonadi::MonitorInterface::Ptr(data.createMonitor()); QScopedPointer queries(new Akonadi::TaskQueries(storage, serializer, monitor)); QFETCH(int, errorCode); QFETCH(int, fetchBehavior); QFETCH(bool, deleteQuery); data.storageBehavior().setFetchItemsErrorCode(42, errorCode); data.storageBehavior().setFetchItemsBehavior(42, AkonadiFakeStorageBehavior::FetchBehavior(fetchBehavior)); // WHEN auto task1 = serializer->createTaskFromItem(data.item(42)); auto result = queries->findChildren(task1); if (deleteQuery) delete queries.take(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); } void shouldNotCrashDuringFindAllWhenFetchJobFailedOrEmpty_data() { QTest::addColumn("colErrorCode"); QTest::addColumn("colFetchBehavior"); QTest::addColumn("itemsErrorCode"); QTest::addColumn("itemsFetchBehavior"); QTest::addColumn("deleteQuery"); QTest::newRow("No error with empty collection list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << false; QTest::newRow("Error with empty collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << true; QTest::newRow("Error with collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << true; QTest::newRow("No error with empty item list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("Error with empty item list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("Error with item list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << false; } void shouldNotCrashDuringFindAllWhenFetchJobFailedOrEmpty() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44"))); auto storage = Akonadi::StorageInterface::Ptr(data.createStorage()); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto monitor = Akonadi::MonitorInterface::Ptr(data.createMonitor()); QScopedPointer queries(new Akonadi::TaskQueries(storage, serializer, monitor)); QFETCH(int, colErrorCode); QFETCH(int, colFetchBehavior); data.storageBehavior().setFetchCollectionsErrorCode(Akonadi::Collection::root().id(), colErrorCode); data.storageBehavior().setFetchCollectionsBehavior(Akonadi::Collection::root().id(), AkonadiFakeStorageBehavior::FetchBehavior(colFetchBehavior)); QFETCH(int, itemsErrorCode); QFETCH(int, itemsFetchBehavior); data.storageBehavior().setFetchItemsErrorCode(42, itemsErrorCode); data.storageBehavior().setFetchItemsBehavior(42, AkonadiFakeStorageBehavior::FetchBehavior(itemsFetchBehavior)); QFETCH(bool, deleteQuery); // WHEN auto result = queries->findAll(); if (deleteQuery) delete queries.take(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); } void shouldNotCrashDuringFindTopLevelWhenFetchJobFailedOrEmpty_data() { QTest::addColumn("colErrorCode"); QTest::addColumn("colFetchBehavior"); QTest::addColumn("itemsErrorCode"); QTest::addColumn("itemsFetchBehavior"); QTest::addColumn("deleteQuery"); QTest::newRow("No error with empty collection list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << false; QTest::newRow("Error with empty collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << true; QTest::newRow("Error with collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << true; QTest::newRow("No error with empty item list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("Error with empty item list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("Error with item list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << false; } void shouldNotCrashDuringFindTopLevelWhenFetchJobFailedOrEmpty() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (third one being child of the first one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); auto storage = Akonadi::StorageInterface::Ptr(data.createStorage()); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto monitor = Akonadi::MonitorInterface::Ptr(data.createMonitor()); QScopedPointer queries(new Akonadi::TaskQueries(storage, serializer, monitor)); QFETCH(int, colErrorCode); QFETCH(int, colFetchBehavior); data.storageBehavior().setFetchCollectionsErrorCode(Akonadi::Collection::root().id(), colErrorCode); data.storageBehavior().setFetchCollectionsBehavior(Akonadi::Collection::root().id(), AkonadiFakeStorageBehavior::FetchBehavior(colFetchBehavior)); QFETCH(int, itemsErrorCode); QFETCH(int, itemsFetchBehavior); data.storageBehavior().setFetchItemsErrorCode(42, itemsErrorCode); data.storageBehavior().setFetchItemsBehavior(42, AkonadiFakeStorageBehavior::FetchBehavior(itemsFetchBehavior)); QFETCH(bool, deleteQuery); // WHEN auto result = queries->findTopLevel(); if (deleteQuery) delete queries.take(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); } void shouldIgnoreProjectsWhenReportingTopLevelTasks() { // GIVEN AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); // One task in the first collection data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); // Two tasks and one project in the second collection data.createItem(GenTodo().withId(43).withParent(43) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(43) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(45).withParent(43) .withTitle(QStringLiteral("45")).withUid(QStringLiteral("uid-45")) .asProject()); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findTopLevel(); result->data(); result = queries->findTopLevel(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); } void shouldLookInAllSelectedCollectionsForInboxTopLevel() { // GIVEN AkonadiFakeData data; // Three top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); - data.createCollection(GenCollection().withId(44).withRootAsParent().withTaskContent().enabled(false)); + data.createCollection(GenCollection().withId(44).withRootAsParent().withTaskContent().selected(false)); // One note in the first collection data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42"))); // Two tasks in the second collection data.createItem(GenTodo().withId(43).withParent(43).withTitle(QStringLiteral("43"))); data.createItem(GenTodo().withId(44).withParent(43).withTitle(QStringLiteral("44"))); // One task in the third collection data.createItem(GenTodo().withId(45).withParent(44).withTitle(QStringLiteral("45"))); // WHEN QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findInboxTopLevel(); result->data(); result = queries->findInboxTopLevel(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("43")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("44")); } void shouldIgnoreItemsWhichAreNotTasksInInboxTopLevel() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Two items in the collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); // One of them is not a task auto item = Akonadi::Item(43); item.setPayloadFromData("FooBar"); item.setParentCollection(Akonadi::Collection(42)); data.createItem(item); // WHEN QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findInboxTopLevel(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } void shouldNotHaveTasksWithParentsInInboxTopLevel() { // TODO: Note that this specification is kind of an over simplification which // assumes that all the underlying data is correct. Ideally it should be checked // that the uid referred to actually points to a todo which exists in a proper // collection. We will need a cache to be able to implement that properly though. // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three items in the collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")).withParentUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(44).withParent(42).withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")).withParentUid(QStringLiteral("foo"))); // WHEN QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findInboxTopLevel(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } void shouldHaveTasksWithContextsInInboxTopLevel_data() { QTest::addColumn("hasContexts"); QTest::addColumn("isExpectedInInbox"); QTest::newRow("task with no context") << false << true; QTest::newRow("task with contexts") << true << true; } void shouldHaveTasksWithContextsInInboxTopLevel() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // One context tag data.createTag(GenTag().withId(42).withName(QStringLiteral("tag-42")).asContext()); // One item in the collection QFETCH(bool, hasContexts); auto tagIds = QList(); if (hasContexts) tagIds << 42; data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).withTags(tagIds)); // WHEN QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findInboxTopLevel(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QFETCH(bool, isExpectedInInbox); if (isExpectedInInbox) { QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } else { QVERIFY(result->data().isEmpty()); } } void shouldReactToItemAddsForInboxTopLevel_data() { QTest::addColumn("reactionExpected"); QTest::addColumn("relatedUid"); QTest::addColumn("hasContexts"); QTest::newRow("task which should be in inbox") << true << QString() << false; QTest::newRow("task with related uid") << false << "foo" << false; QTest::newRow("task with context") << true << QString() << true; } void shouldReactToItemAddsForInboxTopLevel() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // One context tag data.createTag(GenTag().withId(42).withName(QStringLiteral("tag-42")).asContext()); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findInboxTopLevel(); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); // WHEN QFETCH(QString, relatedUid); QFETCH(bool, hasContexts); auto tagIds = QList(); if (hasContexts) tagIds << 42; data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).withTags(tagIds).withParentUid(relatedUid)); // THEN QFETCH(bool, reactionExpected); if (reactionExpected) { QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } else { QVERIFY(result->data().isEmpty()); } } void shouldReactToItemRemovesForInboxTopLevel() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // One item in the collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); // WHEN QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findInboxTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().first()->title(), QStringLiteral("42")); // WHEN data.removeItem(Akonadi::Item(42)); // THEN QVERIFY(result->data().isEmpty()); } void shouldReactToItemChangesForInboxTopLevel_data() { QTest::addColumn("inListAfterChange"); QTest::addColumn("relatedUidBefore"); QTest::addColumn("relatedUidAfter"); QTest::newRow("task appears in inbox (related uid)") << true << "foo" << QString(); QTest::newRow("task disappears from inbox (related uid)") << false << QString() << "foo"; } void shouldReactToItemChangesForInboxTopLevel() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // One context tag data.createTag(GenTag().withId(42).withName(QStringLiteral("tag-42")).asContext()); // Artifact data QFETCH(QString, relatedUidBefore); data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).withParentUid(relatedUidBefore)); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findInboxTopLevel(); TestHelpers::waitForEmptyJobQueue(); QFETCH(bool, inListAfterChange); if (inListAfterChange) { QVERIFY(result->data().isEmpty()); } else { QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } // WHEN QFETCH(QString, relatedUidAfter); data.modifyItem(GenTodo(data.item(42)).withParentUid(relatedUidAfter)); // THEN if (inListAfterChange) { QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } else { QVERIFY(result->data().isEmpty()); } } void shouldReactToCollectionSelectionChangesForInboxTopLevel() { // GIVEN AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); // One task in each collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); data.createItem(GenTodo().withId(43).withParent(43).withTitle(QStringLiteral("43"))); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findInboxTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); // WHEN data.modifyCollection(GenCollection(data.collection(43)).selected(false)); TestHelpers::waitForEmptyJobQueue(); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } void shouldLookInAllWorkdayReportedForAllTasks_data() { QTest::addColumn("isExpectedInWorkday"); QTest::addColumn("item2"); const auto today = Utils::DateTime::currentDateTime(); QTest::newRow("todayTask") << true << Akonadi::Item(GenTodo() .withStartDate(today) .withDueDate(today)); QTest::newRow("pastTask") << true << Akonadi::Item(GenTodo() .withStartDate(today.addDays(-42)) .withDueDate(today.addDays(-41))); QTest::newRow("startTodayTask") << true << Akonadi::Item(GenTodo() .withStartDate(today)); QTest::newRow("endTodayTask") << true << Akonadi::Item(GenTodo() .withStartDate(today)); QTest::newRow("futureTask") << false << Akonadi::Item(GenTodo() .withStartDate(today.addDays(41)) .withDueDate(today.addDays(42))); QTest::newRow("pastDoneTask") << false << Akonadi::Item(GenTodo() .withStartDate(today.addDays(-42)) .withDueDate(today.addDays(-41)) .done() .withDoneDate(today.addDays(-30))); QTest::newRow("todayDoneTask") << true << Akonadi::Item(GenTodo() .withStartDate(today) .withDueDate(today) .done() .withDoneDate(QDateTime(today.date(), QTime(12, 00)))); QTest::newRow("startTodayDoneTask") << true << Akonadi::Item(GenTodo() .withStartDate(today) .done() .withDoneDate(QDateTime(today.date(), QTime(12, 00)))); QTest::newRow("endTodayDoneTask") << true << Akonadi::Item(GenTodo() .withDueDate(today) .done() .withDoneDate(QDateTime(today.date(), QTime(12, 00)))); } void shouldLookInAllWorkdayReportedForAllTasks() { // GIVEN const auto today = Utils::DateTime::currentDateTime(); AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); // One task in the first collection data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42")) .withStartDate(today.addSecs(300))); // One task in the second collection (from data driven set) QFETCH(Akonadi::Item, item2); data.createItem(GenTodo(item2).withId(43).withParent(43) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); // WHEN QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findWorkdayTopLevel(); result->data(); result = queries->findWorkdayTopLevel(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QFETCH(bool, isExpectedInWorkday); const int sizeExpected = (isExpectedInWorkday) ? 2 : 1; QCOMPARE(result->data().size(), sizeExpected); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); if (isExpectedInWorkday) QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); } void shouldLookInAllWorkdayReportedForAllTasksWhenOverrideDate() { // GIVEN qputenv("ZANSHIN_OVERRIDE_DATETIME", "2015-03-10"); const auto today = Utils::DateTime::currentDateTime(); AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); // One task in the first collection data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42")) .withStartDate(today)); // One task in the second collection data.createItem(GenTodo().withId(43).withParent(43) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")) .withStartDate(today.addSecs(3600))); // WHEN QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); auto result = queries->findWorkdayTopLevel(); result->data(); result = queries->findWorkdayTopLevel(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); } void shouldPollForCurrentDayToListWorkday() { // GIVEN qputenv("ZANSHIN_OVERRIDE_DATETIME", "2015-03-10T23:59:59UTC"); const auto today = Utils::DateTime::currentDateTime(); AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); // One task in the first collection data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42")) .withStartDate(today)); // One task in the second collection data.createItem(GenTodo().withId(43).withParent(43) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")) .withStartDate(today.addDays(1))); QScopedPointer queries; { auto akqueries = new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor())); QCOMPARE(akqueries->workdayPollInterval(), 30000); akqueries->setWorkdayPollInterval(500); queries.reset(akqueries); } auto result = queries->findWorkdayTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); // WHEN qputenv("ZANSHIN_OVERRIDE_DATETIME", "2015-03-11T00:01:00UTC"); QTest::qWait(1000); TestHelpers::waitForEmptyJobQueue(); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); } }; ZANSHIN_TEST_MAIN(AkonadiTaskQueriesTest) #include "akonaditaskqueriestest.moc" diff --git a/tests/units/domain/datasourcetest.cpp b/tests/units/domain/datasourcetest.cpp index a635fe7a..e26f69a8 100644 --- a/tests/units/domain/datasourcetest.cpp +++ b/tests/units/domain/datasourcetest.cpp @@ -1,146 +1,126 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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 "domain/datasource.h" using namespace Domain; class DataSourceTest : public QObject { Q_OBJECT public: explicit DataSourceTest(QObject *parent = Q_NULLPTR) : QObject(parent) { qRegisterMetaType(); - qRegisterMetaType(); } private slots: void shouldHaveEmptyPropertiesByDefault() { DataSource ds; QCOMPARE(ds.name(), QString()); QCOMPARE(ds.iconName(), QString()); QCOMPARE(ds.contentTypes(), DataSource::NoContent); - QCOMPARE(ds.listStatus(), DataSource::Unlisted); QVERIFY(!ds.isSelected()); } void shouldNotifyNameChanges() { DataSource ds; QSignalSpy spy(&ds, &DataSource::nameChanged); ds.setName(QStringLiteral("Foo")); QCOMPARE(spy.count(), 1); QCOMPARE(spy.first().first().toString(), QStringLiteral("Foo")); } void shouldNotNotifyIdenticalNameChanges() { DataSource ds; ds.setName(QStringLiteral("Foo")); QSignalSpy spy(&ds, &DataSource::nameChanged); ds.setName(QStringLiteral("Foo")); QCOMPARE(spy.count(), 0); } void shouldNotifyIconNameChanges() { DataSource ds; QSignalSpy spy(&ds, &DataSource::iconNameChanged); ds.setIconName(QStringLiteral("Foo")); QCOMPARE(spy.count(), 1); QCOMPARE(spy.first().first().toString(), QStringLiteral("Foo")); } void shouldNotNotifyIdenticalIconNameChanges() { DataSource ds; ds.setIconName(QStringLiteral("Foo")); QSignalSpy spy(&ds, &DataSource::iconNameChanged); ds.setIconName(QStringLiteral("Foo")); QCOMPARE(spy.count(), 0); } void shouldNotifyContentTypesChanges() { DataSource ds; QSignalSpy spy(&ds, &DataSource::contentTypesChanged); ds.setContentTypes(Domain::DataSource::Notes); QCOMPARE(spy.count(), 1); QCOMPARE(spy.first().first().value(), Domain::DataSource::Notes); } void shouldNotNotifyIdenticalContentTypesChanges() { DataSource ds; ds.setContentTypes(Domain::DataSource::Notes); QSignalSpy spy(&ds, &DataSource::contentTypesChanged); ds.setContentTypes(Domain::DataSource::Notes); QCOMPARE(spy.count(), 0); } void shouldNotifySelectedChanges() { DataSource ds; QSignalSpy spy(&ds, &DataSource::selectedChanged); ds.setSelected(true); QCOMPARE(spy.count(), 1); QVERIFY(spy.first().first().toBool()); } void shouldNotNotifyIdenticalSelectedChanges() { DataSource ds; ds.setSelected(true); QSignalSpy spy(&ds, &DataSource::selectedChanged); ds.setSelected(true); QCOMPARE(spy.count(), 0); } - - void shouldNotifyListStatusChanges() - { - DataSource ds; - QSignalSpy spy(&ds, &DataSource::listStatusChanged); - ds.setListStatus(DataSource::Bookmarked); - QCOMPARE(spy.count(), 1); - QCOMPARE(spy.first().first().value(), DataSource::Bookmarked); - } - - void shouldNotNotifyIdenticalListStatusChanges() - { - DataSource ds; - ds.setListStatus(DataSource::Bookmarked); - QSignalSpy spy(&ds, &DataSource::listStatusChanged); - ds.setListStatus(DataSource::Bookmarked); - QCOMPARE(spy.count(), 0); - } }; ZANSHIN_TEST_MAIN(DataSourceTest) #include "datasourcetest.moc" diff --git a/tests/units/presentation/availablesourcesmodeltest.cpp b/tests/units/presentation/availablesourcesmodeltest.cpp index 87f83e3a..7d48ca6e 100644 --- a/tests/units/presentation/availablesourcesmodeltest.cpp +++ b/tests/units/presentation/availablesourcesmodeltest.cpp @@ -1,586 +1,282 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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 "utils/mockobject.h" #define ZANSHIN_I_SWEAR_I_AM_IN_A_PRESENTATION_TEST #include "domain/datasourcequeries.h" #include "domain/datasourcerepository.h" #include "presentation/availablesourcesmodel.h" #include "presentation/querytreemodelbase.h" #include "presentation/errorhandler.h" #include "testlib/fakejob.h" Q_DECLARE_METATYPE(QModelIndex); using namespace mockitopp; using namespace mockitopp::matcher; class FakeErrorHandler : public Presentation::ErrorHandler { public: void doDisplayMessage(const QString &message) { m_message = message; } QString m_message; }; class AvailableSourcesModelTest : public QObject { Q_OBJECT public: explicit AvailableSourcesModelTest(QObject *parent = Q_NULLPTR) : QObject(parent) { qRegisterMetaType(); } private slots: void shouldListAvailableSources() { // GIVEN // Two top level sources auto source1 = Domain::DataSource::Ptr::create(); source1->setName(QStringLiteral("Source 1")); source1->setIconName(QStringLiteral("foo-icon")); source1->setSelected(true); auto source2 = Domain::DataSource::Ptr::create(); source2->setName(QStringLiteral("Source 2")); source2->setSelected(false); source2->setContentTypes(Domain::DataSource::Tasks); auto topLevelProvider = Domain::QueryResultProvider::Ptr::create(); auto topLevelResult = Domain::QueryResult::create(topLevelProvider); topLevelProvider->append(source1); topLevelProvider->append(source2); // Two other sources under source1 auto source3 = Domain::DataSource::Ptr::create(); source3->setName(QStringLiteral("Source 3")); source3->setSelected(false); source3->setContentTypes(Domain::DataSource::Notes); auto source4 = Domain::DataSource::Ptr::create(); source4->setSelected(true); source4->setName(QStringLiteral("Source 4")); source4->setContentTypes(Domain::DataSource::Notes | Domain::DataSource::Tasks); auto source1Provider = Domain::QueryResultProvider::Ptr::create(); auto source1Result = Domain::QueryResult::create(source1Provider); source1Provider->append(source3); source1Provider->append(source4); // Nothing under source2, source3 or source4 auto source2Provider = Domain::QueryResultProvider::Ptr::create(); auto source2Result = Domain::QueryResult::create(source2Provider); auto source3Provider = Domain::QueryResultProvider::Ptr::create(); auto source3Result = Domain::QueryResult::create(source3Provider); auto source4Provider = Domain::QueryResultProvider::Ptr::create(); auto source4Result = Domain::QueryResult::create(source4Provider); Utils::MockObject sourceQueriesMock; sourceQueriesMock(&Domain::DataSourceQueries::findTopLevel).when().thenReturn(topLevelResult); sourceQueriesMock(&Domain::DataSourceQueries::findChildren).when(source1).thenReturn(source1Result); sourceQueriesMock(&Domain::DataSourceQueries::findChildren).when(source2).thenReturn(source2Result); sourceQueriesMock(&Domain::DataSourceQueries::findChildren).when(source3).thenReturn(source3Result); sourceQueriesMock(&Domain::DataSourceQueries::findChildren).when(source4).thenReturn(source4Result); // We'll simulate a default source change later on sourceQueriesMock(&Domain::DataSourceQueries::isDefaultSource).when(source1).thenReturn(false); sourceQueriesMock(&Domain::DataSourceQueries::isDefaultSource).when(source2).thenReturn(true) .thenReturn(false); sourceQueriesMock(&Domain::DataSourceQueries::isDefaultSource).when(source3).thenReturn(false); sourceQueriesMock(&Domain::DataSourceQueries::isDefaultSource).when(source4).thenReturn(false) .thenReturn(false) .thenReturn(true); Utils::MockObject sourceRepositoryMock; Presentation::AvailableSourcesModel sources(sourceQueriesMock.getInstance(), sourceRepositoryMock.getInstance(), Q_NULLPTR); // WHEN QAbstractItemModel *model = sources.sourceListModel(); // THEN const QModelIndex source1Index = model->index(0, 0); const QModelIndex source2Index = model->index(1, 0); const QModelIndex source3Index = model->index(0, 0, source1Index); const QModelIndex source4Index = model->index(1, 0, source1Index); QCOMPARE(model->rowCount(), 2); QCOMPARE(model->rowCount(source1Index), 2); QCOMPARE(model->rowCount(source2Index), 0); QCOMPARE(model->rowCount(source3Index), 0); QCOMPARE(model->rowCount(source4Index), 0); const Qt::ItemFlags defaultFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; QCOMPARE(model->flags(source1Index), defaultFlags); QCOMPARE(model->flags(source2Index), defaultFlags | Qt::ItemIsUserCheckable); QCOMPARE(model->flags(source3Index), defaultFlags | Qt::ItemIsUserCheckable); QCOMPARE(model->flags(source4Index), defaultFlags | Qt::ItemIsUserCheckable); QCOMPARE(model->data(source1Index).toString(), source1->name()); QCOMPARE(model->data(source2Index).toString(), source2->name()); QCOMPARE(model->data(source3Index).toString(), source3->name()); QCOMPARE(model->data(source4Index).toString(), source4->name()); QCOMPARE(model->data(source1Index, Qt::EditRole).toString(), source1->name()); QCOMPARE(model->data(source2Index, Qt::EditRole).toString(), source2->name()); QCOMPARE(model->data(source3Index, Qt::EditRole).toString(), source3->name()); QCOMPARE(model->data(source4Index, Qt::EditRole).toString(), source4->name()); QVERIFY(!model->data(source1Index, Qt::CheckStateRole).isValid()); QCOMPARE(model->data(source2Index, Qt::CheckStateRole).toBool(), source2->isSelected()); QCOMPARE(model->data(source3Index, Qt::CheckStateRole).toBool(), source3->isSelected()); QCOMPARE(model->data(source4Index, Qt::CheckStateRole).toBool(), source4->isSelected()); QCOMPARE(model->data(source1Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), source1->iconName()); QCOMPARE(model->data(source2Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("folder")); QCOMPARE(model->data(source3Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("folder")); QCOMPARE(model->data(source4Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("folder")); QCOMPARE(model->data(source1Index, Presentation::QueryTreeModelBase::IsDefaultRole).toBool(), false); QCOMPARE(model->data(source2Index, Presentation::QueryTreeModelBase::IsDefaultRole).toBool(), true); QCOMPARE(model->data(source3Index, Presentation::QueryTreeModelBase::IsDefaultRole).toBool(), false); QCOMPARE(model->data(source4Index, Presentation::QueryTreeModelBase::IsDefaultRole).toBool(), false); // WHEN sourceRepositoryMock(&Domain::DataSourceRepository::update).when(source2).thenReturn(new FakeJob(this)); sourceRepositoryMock(&Domain::DataSourceRepository::update).when(source4).thenReturn(new FakeJob(this)); QVERIFY(!model->setData(source1Index, Qt::Unchecked, Qt::CheckStateRole)); QVERIFY(model->setData(source2Index, Qt::Checked, Qt::CheckStateRole)); QVERIFY(model->setData(source4Index, Qt::Unchecked, Qt::CheckStateRole)); // THEN QVERIFY(sourceRepositoryMock(&Domain::DataSourceRepository::update).when(source2).exactly(1)); QVERIFY(sourceRepositoryMock(&Domain::DataSourceRepository::update).when(source4).exactly(1)); QVERIFY(source2->isSelected()); QVERIFY(!source4->isSelected()); // WHEN QSignalSpy spy(model, &QAbstractItemModel::dataChanged); sourceQueriesMock(&Domain::DataSourceQueries::changeDefaultSource).when(source4).thenReturn(); sources.setDefaultItem(source4Index); // THEN QCOMPARE(model->data(source1Index, Presentation::QueryTreeModelBase::IsDefaultRole).toBool(), false); QCOMPARE(model->data(source2Index, Presentation::QueryTreeModelBase::IsDefaultRole).toBool(), false); QCOMPARE(model->data(source3Index, Presentation::QueryTreeModelBase::IsDefaultRole).toBool(), false); QCOMPARE(model->data(source4Index, Presentation::QueryTreeModelBase::IsDefaultRole).toBool(), true); // Not overly efficient way of signaling the change, but doesn't happen often QCOMPARE(spy.count(), 4); QCOMPARE(spy.at(0).at(0).toModelIndex(), source1Index); QCOMPARE(spy.at(0).at(1).toModelIndex(), source1Index); QCOMPARE(spy.at(1).at(0).toModelIndex(), source3Index); QCOMPARE(spy.at(1).at(1).toModelIndex(), source3Index); QCOMPARE(spy.at(2).at(0).toModelIndex(), source4Index); QCOMPARE(spy.at(2).at(1).toModelIndex(), source4Index); QCOMPARE(spy.at(3).at(0).toModelIndex(), source2Index); QCOMPARE(spy.at(3).at(1).toModelIndex(), source2Index); QVERIFY(sourceQueriesMock(&Domain::DataSourceQueries::changeDefaultSource).when(source4).exactly(1)); } - void shouldListAvailableSearchSources() - { - // GIVEN - - // Two top level sources - auto source1 = Domain::DataSource::Ptr::create(); - source1->setName(QStringLiteral("Source 1")); - source1->setIconName(QStringLiteral("foo-icon")); - source1->setSelected(true); - auto source2 = Domain::DataSource::Ptr::create(); - source2->setName(QStringLiteral("Source 2")); - source2->setSelected(false); - source2->setContentTypes(Domain::DataSource::Tasks); - auto topLevelProvider = Domain::QueryResultProvider::Ptr::create(); - auto topLevelResult = Domain::QueryResult::create(topLevelProvider); - topLevelProvider->append(source1); - topLevelProvider->append(source2); - - // Two other sources under source1 - auto source3 = Domain::DataSource::Ptr::create(); - source3->setName(QStringLiteral("Source 3")); - source3->setSelected(false); - source3->setContentTypes(Domain::DataSource::Notes); - auto source4 = Domain::DataSource::Ptr::create(); - source4->setSelected(true); - source4->setName(QStringLiteral("Source 4")); - source4->setContentTypes(Domain::DataSource::Notes | Domain::DataSource::Tasks); - auto source1Provider = Domain::QueryResultProvider::Ptr::create(); - auto source1Result = Domain::QueryResult::create(source1Provider); - source1Provider->append(source3); - source1Provider->append(source4); - - // Nothing under source2, source3 or source4 - auto source2Provider = Domain::QueryResultProvider::Ptr::create(); - auto source2Result = Domain::QueryResult::create(source2Provider); - auto source3Provider = Domain::QueryResultProvider::Ptr::create(); - auto source3Result = Domain::QueryResult::create(source3Provider); - auto source4Provider = Domain::QueryResultProvider::Ptr::create(); - auto source4Result = Domain::QueryResult::create(source4Provider); - - Utils::MockObject sourceQueriesMock; - sourceQueriesMock(&Domain::DataSourceQueries::findSearchTopLevel).when().thenReturn(topLevelResult); - sourceQueriesMock(&Domain::DataSourceQueries::findSearchChildren).when(source1).thenReturn(source1Result); - sourceQueriesMock(&Domain::DataSourceQueries::findSearchChildren).when(source2).thenReturn(source2Result); - sourceQueriesMock(&Domain::DataSourceQueries::findSearchChildren).when(source3).thenReturn(source3Result); - sourceQueriesMock(&Domain::DataSourceQueries::findSearchChildren).when(source4).thenReturn(source4Result); - - Utils::MockObject sourceRepositoryMock; - - Presentation::AvailableSourcesModel sources(sourceQueriesMock.getInstance(), - sourceRepositoryMock.getInstance(), - Q_NULLPTR); - - // WHEN - QAbstractItemModel *model = sources.searchListModel(); - - // THEN - const QModelIndex source1Index = model->index(0, 0); - const QModelIndex source2Index = model->index(1, 0); - const QModelIndex source3Index = model->index(0, 0, source1Index); - const QModelIndex source4Index = model->index(1, 0, source1Index); - - QCOMPARE(model->rowCount(), 2); - QCOMPARE(model->rowCount(source1Index), 2); - QCOMPARE(model->rowCount(source2Index), 0); - QCOMPARE(model->rowCount(source3Index), 0); - QCOMPARE(model->rowCount(source4Index), 0); - - const Qt::ItemFlags defaultFlags = Qt::ItemIsSelectable - | Qt::ItemIsEnabled; - QCOMPARE(model->flags(source1Index), defaultFlags); - QCOMPARE(model->flags(source2Index), defaultFlags); - QCOMPARE(model->flags(source3Index), defaultFlags); - QCOMPARE(model->flags(source4Index), defaultFlags); - - QCOMPARE(model->data(source1Index).toString(), source1->name()); - QCOMPARE(model->data(source2Index).toString(), source2->name()); - QCOMPARE(model->data(source3Index).toString(), source3->name()); - QCOMPARE(model->data(source4Index).toString(), source4->name()); - - QCOMPARE(model->data(source1Index, Qt::EditRole).toString(), source1->name()); - QCOMPARE(model->data(source2Index, Qt::EditRole).toString(), source2->name()); - QCOMPARE(model->data(source3Index, Qt::EditRole).toString(), source3->name()); - QCOMPARE(model->data(source4Index, Qt::EditRole).toString(), source4->name()); - - QVERIFY(!model->data(source1Index, Qt::CheckStateRole).isValid()); - QVERIFY(!model->data(source2Index, Qt::CheckStateRole).isValid()); - QVERIFY(!model->data(source3Index, Qt::CheckStateRole).isValid()); - QVERIFY(!model->data(source4Index, Qt::CheckStateRole).isValid()); - - QCOMPARE(model->data(source1Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), source1->iconName()); - QCOMPARE(model->data(source2Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("folder")); - QCOMPARE(model->data(source3Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("folder")); - QCOMPARE(model->data(source4Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("folder")); - - QVERIFY(!model->setData(source1Index, Qt::Unchecked, Qt::CheckStateRole)); - QVERIFY(!model->setData(source2Index, Qt::Checked, Qt::CheckStateRole)); - QVERIFY(!model->setData(source4Index, Qt::Unchecked, Qt::CheckStateRole)); - } - - void shouldChangeSourceToListed() - { - // GIVEN - - auto source = Domain::DataSource::Ptr::create(); - source->setName(QStringLiteral("Source")); - source->setIconName(QStringLiteral("folder")); - source->setContentTypes(Domain::DataSource::Tasks); - source->setSelected(false); - source->setListStatus(Domain::DataSource::Unlisted); - - - Utils::MockObject sourceQueriesMock; - - Utils::MockObject sourceRepositoryMock; - sourceRepositoryMock(&Domain::DataSourceRepository::update).when(source).thenReturn(new FakeJob(this)); - - Presentation::AvailableSourcesModel sources(sourceQueriesMock.getInstance(), - sourceRepositoryMock.getInstance()); - - // WHEN - sources.listSource(source); - - // THEN - QVERIFY(sourceRepositoryMock(&Domain::DataSourceRepository::update).when(source).exactly(1)); - QVERIFY(source->isSelected()); - QCOMPARE(source->listStatus(), Domain::DataSource::Listed); - } - - void shouldChangeSourceToUnlisted() - { - // GIVEN - - auto source = Domain::DataSource::Ptr::create(); - source->setName(QStringLiteral("Source")); - source->setIconName(QStringLiteral("folder")); - source->setContentTypes(Domain::DataSource::Tasks); - source->setSelected(true); - source->setListStatus(Domain::DataSource::Bookmarked); - - - Utils::MockObject sourceQueriesMock; - - Utils::MockObject sourceRepositoryMock; - sourceRepositoryMock(&Domain::DataSourceRepository::update).when(source).thenReturn(new FakeJob(this)); - - Presentation::AvailableSourcesModel sources(sourceQueriesMock.getInstance(), - sourceRepositoryMock.getInstance()); - - // WHEN - sources.unlistSource(source); - - // THEN - QVERIFY(sourceRepositoryMock(&Domain::DataSourceRepository::update).when(source).exactly(1)); - QVERIFY(!source->isSelected()); - QCOMPARE(source->listStatus(), Domain::DataSource::Unlisted); - } - - void shouldToggleSourceToBookmarkStatus_data() - { - QTest::addColumn("wasSelected"); - QTest::addColumn("wasBookmarked"); - QTest::newRow("unselected, not bookmarked") << false << false; - QTest::newRow("selected, not bookmarked") << true << false; - QTest::newRow("unselected, bookmarked") << false << true; - QTest::newRow("selected, bookmarked") << true << true; - } - - void shouldToggleSourceToBookmarkStatus() - { - // GIVEN - QFETCH(bool, wasSelected); - QFETCH(bool, wasBookmarked); - - auto source = Domain::DataSource::Ptr::create(); - source->setName(QStringLiteral("Source")); - source->setIconName(QStringLiteral("folder")); - source->setContentTypes(Domain::DataSource::Tasks); - source->setSelected(wasSelected); - if (wasBookmarked) - source->setListStatus(Domain::DataSource::Bookmarked); - else - source->setListStatus(Domain::DataSource::Listed); - - - Utils::MockObject sourceQueriesMock; - - Utils::MockObject sourceRepositoryMock; - sourceRepositoryMock(&Domain::DataSourceRepository::update).when(source).thenReturn(new FakeJob(this)); - - Presentation::AvailableSourcesModel sources(sourceQueriesMock.getInstance(), - sourceRepositoryMock.getInstance()); - - // WHEN - sources.bookmarkSource(source); - - // THEN - QVERIFY(sourceRepositoryMock(&Domain::DataSourceRepository::update).when(source).exactly(1)); - QCOMPARE(source->isSelected(), wasSelected); - if (wasBookmarked) - QCOMPARE(source->listStatus(), Domain::DataSource::Listed); - else - QCOMPARE(source->listStatus(), Domain::DataSource::Bookmarked); - } - - void shouldGetAnErrorMessageWhenListSourceFailed() - { - // GIVEN - - auto source = Domain::DataSource::Ptr::create(); - source->setName(QStringLiteral("Source")); - source->setIconName(QStringLiteral("folder")); - source->setContentTypes(Domain::DataSource::Tasks); - source->setSelected(false); - source->setListStatus(Domain::DataSource::Unlisted); - - - Utils::MockObject sourceQueriesMock; - - Utils::MockObject sourceRepositoryMock; - - auto job = new FakeJob(this); - job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); - sourceRepositoryMock(&Domain::DataSourceRepository::update).when(source).thenReturn(job); - - Presentation::AvailableSourcesModel sources(sourceQueriesMock.getInstance(), - sourceRepositoryMock.getInstance()); - FakeErrorHandler errorHandler; - sources.setErrorHandler(&errorHandler); - - // WHEN - sources.listSource(source); - - // THEN - QTest::qWait(150); - QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot modify source Source: Foo")); - } - - void shouldGetAnErrorMessageWhenUnlistSourceFailed() - { - // GIVEN - - auto source = Domain::DataSource::Ptr::create(); - source->setName(QStringLiteral("Source")); - source->setIconName(QStringLiteral("folder")); - source->setContentTypes(Domain::DataSource::Tasks); - source->setSelected(false); - source->setListStatus(Domain::DataSource::Unlisted); - - - Utils::MockObject sourceQueriesMock; - - Utils::MockObject sourceRepositoryMock; - - auto job = new FakeJob(this); - job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); - sourceRepositoryMock(&Domain::DataSourceRepository::update).when(source).thenReturn(job); - - Presentation::AvailableSourcesModel sources(sourceQueriesMock.getInstance(), - sourceRepositoryMock.getInstance()); - FakeErrorHandler errorHandler; - sources.setErrorHandler(&errorHandler); - - // WHEN - sources.unlistSource(source); - - // THEN - QTest::qWait(150); - QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot modify source Source: Foo")); - } - - void shouldGetAnErrorMessageWhenBookmarkSourceFailed() - { - // GIVEN - - auto source = Domain::DataSource::Ptr::create(); - source->setName(QStringLiteral("Source")); - source->setIconName(QStringLiteral("folder")); - source->setContentTypes(Domain::DataSource::Tasks); - source->setSelected(false); - source->setListStatus(Domain::DataSource::Unlisted); - - - Utils::MockObject sourceQueriesMock; - - Utils::MockObject sourceRepositoryMock; - - auto job = new FakeJob(this); - job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); - sourceRepositoryMock(&Domain::DataSourceRepository::update).when(source).thenReturn(job); - - Presentation::AvailableSourcesModel sources(sourceQueriesMock.getInstance(), - sourceRepositoryMock.getInstance()); - FakeErrorHandler errorHandler; - sources.setErrorHandler(&errorHandler); - - // WHEN - sources.bookmarkSource(source); - - // THEN - QTest::qWait(150); - QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot modify source Source: Foo")); - } - void shouldGetAnErrorMessageWhenSetDataSourceFailed() { // GIVEN // Two top level sources auto source1 = Domain::DataSource::Ptr::create(); source1->setName(QStringLiteral("Source 1")); source1->setIconName(QStringLiteral("foo-icon")); source1->setSelected(false); source1->setContentTypes(Domain::DataSource::Tasks); auto topLevelProvider = Domain::QueryResultProvider::Ptr::create(); auto topLevelResult = Domain::QueryResult::create(topLevelProvider); topLevelProvider->append(source1); // Nothing under source1 auto source1Provider = Domain::QueryResultProvider::Ptr::create(); auto source1Result = Domain::QueryResult::create(source1Provider); Utils::MockObject sourceQueriesMock; sourceQueriesMock(&Domain::DataSourceQueries::findTopLevel).when().thenReturn(topLevelResult); sourceQueriesMock(&Domain::DataSourceQueries::findChildren).when(source1).thenReturn(source1Result); Utils::MockObject sourceRepositoryMock; Presentation::AvailableSourcesModel sources(sourceQueriesMock.getInstance(), sourceRepositoryMock.getInstance(), Q_NULLPTR); FakeErrorHandler errorHandler; sources.setErrorHandler(&errorHandler); // WHEN QAbstractItemModel *model = sources.sourceListModel(); // THEN const QModelIndex source1Index = model->index(0, 0); // WHEN auto job = new FakeJob(this); job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); sourceRepositoryMock(&Domain::DataSourceRepository::update).when(source1).thenReturn(job); QVERIFY(model->setData(source1Index, Qt::Unchecked, Qt::CheckStateRole)); // THEN QTest::qWait(150); QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot modify source Source 1: Foo")); } void shouldExecBackendSettingsDialog() { // GIVEN Utils::MockObject sourceRepositoryMock; sourceRepositoryMock(&Domain::DataSourceRepository::showConfigDialog).when().thenReturn(); Presentation::AvailableSourcesModel sources(Domain::DataSourceQueries::Ptr(), sourceRepositoryMock.getInstance()); // WHEN sources.showConfigDialog(); // THEN QVERIFY(sourceRepositoryMock(&Domain::DataSourceRepository::showConfigDialog).when().exactly(1)); } }; ZANSHIN_TEST_MAIN(AvailableSourcesModelTest) #include "availablesourcesmodeltest.moc" diff --git a/tests/units/testlib/gencollectiontest.cpp b/tests/units/testlib/gencollectiontest.cpp index 6e302e60..a060286f 100644 --- a/tests/units/testlib/gencollectiontest.cpp +++ b/tests/units/testlib/gencollectiontest.cpp @@ -1,190 +1,160 @@ /* This file is part of Zanshin Copyright 2015 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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 "testlib/gencollection.h" #include "akonadi/akonadiapplicationselectedattribute.h" #include #include using namespace Testlib; class GenCollectionTest : public QObject { Q_OBJECT private slots: void shouldImplicitlyConvertBackToCollection() { // GIVEN auto col = Akonadi::Collection(42); auto gen = GenCollection(col); // WHEN Akonadi::Collection newCol = gen; // THEN QCOMPARE(newCol, col); } void shouldAllowToSetId() { // GIVEN Akonadi::Collection col = GenCollection().withId(42); // THEN QCOMPARE(col.id(), 42LL); } void shouldAllowToSetParent() { // GIVEN Akonadi::Collection col = GenCollection().withParent(42); // THEN QCOMPARE(col.parentCollection().id(), 42LL); } void shouldAllowToSetRootAsParent() { // GIVEN Akonadi::Collection col = GenCollection().withRootAsParent(); // THEN QCOMPARE(col.parentCollection(), Akonadi::Collection::root()); } void shouldAllowToSetName() { // GIVEN Akonadi::Collection col = GenCollection().withName(QStringLiteral("42")); // THEN QCOMPARE(col.name(), QStringLiteral("42")); } void shouldAllowToSetIcon() { // GIVEN Akonadi::Collection col = GenCollection().withIcon(QStringLiteral("42")); // THEN QCOMPARE(col.attribute()->iconName(), QStringLiteral("42")); } - void shouldAllowToSetReferenced() - { - // GIVEN - Akonadi::Collection col = GenCollection().referenced(); - - // THEN - QCOMPARE(col.referenced(), true); - - // WHEN - col = GenCollection(col).referenced(false); - - // THEN - QCOMPARE(col.referenced(), false); - } - - void shouldAllowToSetEnabled() - { - // GIVEN - Akonadi::Collection col = GenCollection().enabled(); - - // THEN - QCOMPARE(col.enabled(), true); - - // WHEN - col = GenCollection(col).enabled(false); - - // THEN - QCOMPARE(col.enabled(), false); - } - void shouldAllowToSetSelected() { // GIVEN Akonadi::Collection col = GenCollection().selected(); // THEN QVERIFY(!col.hasAttribute()); // WHEN col = GenCollection(col).selected(false); // THEN QCOMPARE(col.attribute()->isSelected(), false); } void shouldAllowToSetTaskContent() { // GIVEN Akonadi::Collection col = GenCollection().withTaskContent(); // THEN QVERIFY(col.contentMimeTypes().contains(QStringLiteral("application/x-vnd.akonadi.calendar.todo"))); // WHEN col = GenCollection(col).withTaskContent(false); // THEN QVERIFY(!col.contentMimeTypes().contains(QStringLiteral("application/x-vnd.akonadi.calendar.todo"))); } void shouldAllowToSetNoteContent() { // GIVEN Akonadi::Collection col = GenCollection().withNoteContent(); // THEN QVERIFY(col.contentMimeTypes().contains(QStringLiteral("text/x-vnd.akonadi.note"))); // WHEN col = GenCollection(col).withNoteContent(false); // THEN QVERIFY(!col.contentMimeTypes().contains(QStringLiteral("text/x-vnd.akonadi.note"))); } void shouldAllowToSetNoteAndTaskContent() { // GIVEN Akonadi::Collection col = GenCollection().withTaskContent().withNoteContent(); // THEN QVERIFY(col.contentMimeTypes().contains(QStringLiteral("application/x-vnd.akonadi.calendar.todo"))); QVERIFY(col.contentMimeTypes().contains(QStringLiteral("text/x-vnd.akonadi.note"))); // WHEN col = GenCollection(col).withTaskContent(false).withNoteContent(false); // THEN QVERIFY(!col.contentMimeTypes().contains(QStringLiteral("application/x-vnd.akonadi.calendar.todo"))); QVERIFY(!col.contentMimeTypes().contains(QStringLiteral("text/x-vnd.akonadi.note"))); } }; ZANSHIN_TEST_MAIN(GenCollectionTest) #include "gencollectiontest.moc" diff --git a/tests/units/widgets/CMakeLists.txt b/tests/units/widgets/CMakeLists.txt index 291bd3de..368a9aba 100644 --- a/tests/units/widgets/CMakeLists.txt +++ b/tests/units/widgets/CMakeLists.txt @@ -1,19 +1,18 @@ zanshin_auto_tests( applicationcomponentstest availablepagesviewtest availablesourcesviewtest - datasourcedelegatetest editorviewtest filterwidgettest newprojectdialogtest pageviewerrorhandlertest pageviewtest quickselectdialogtest runningtaskwidgettest scripteditortest taskapplicationcomponentstest ) # These tests need a window that takes focus set_tests_properties(tests-units-widgets-editorviewtest PROPERTIES RUN_SERIAL TRUE) set_tests_properties(tests-units-widgets-pageviewtest PROPERTIES RUN_SERIAL TRUE) diff --git a/tests/units/widgets/availablesourcesviewtest.cpp b/tests/units/widgets/availablesourcesviewtest.cpp index 7a4b4ef8..48572c3b 100644 --- a/tests/units/widgets/availablesourcesviewtest.cpp +++ b/tests/units/widgets/availablesourcesviewtest.cpp @@ -1,419 +1,241 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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 "presentation/metatypes.h" #include "presentation/querytreemodelbase.h" #include "widgets/availablesourcesview.h" #include "widgets/datasourcedelegate.h" class AvailableSourcesModelStub : public QObject { Q_OBJECT - Q_PROPERTY(QString searchTerm READ searchTerm WRITE setSearchTerm) public: explicit AvailableSourcesModelStub(QObject *parent = Q_NULLPTR) : QObject(parent), settingsCalled(false) { } - QString searchTerm() const - { - return m_searchTerm; - } - public slots: void showConfigDialog() { settingsCalled = true; } void setDefaultItem(const QModelIndex &index) { defaultIndex = index; } - void setSearchTerm(const QString &term) - { - m_searchTerm = term; - } - - void listSource(const Domain::DataSource::Ptr &source) - { - listedSources << source; - } - - void unlistSource(const Domain::DataSource::Ptr &source) - { - unlistedSources << source; - } - - void bookmarkSource(const Domain::DataSource::Ptr &source) - { - bookmarkedSources << source; - } - public: bool settingsCalled; - QList listedSources; - QList unlistedSources; - QList bookmarkedSources; QPersistentModelIndex defaultIndex; - -private: - QString m_searchTerm; }; class AvailableSourcesViewTest : public QObject { Q_OBJECT private slots: void shouldHaveDefaultState() { Widgets::AvailableSourcesView available; QVERIFY(!available.model()); auto sourcesView = available.findChild(QStringLiteral("sourcesView")); QVERIFY(sourcesView); QVERIFY(sourcesView->isVisibleTo(&available)); QVERIFY(!sourcesView->header()->isVisibleTo(&available)); auto delegate = qobject_cast(sourcesView->itemDelegate()); -#ifdef ZANSHIN_HIDING_SOURCES_ENABLED - QVERIFY(delegate->actionsEnabled()); -#else - QVERIFY(!delegate->isActionsEnabled()); -#endif - - auto searchEdit = available.findChild(QStringLiteral("searchEdit")); - QVERIFY(searchEdit); -#ifdef ZANSHIN_HIDING_SOURCES_ENABLED - QVERIFY(searchEdit->isVisibleTo(&available)); -#else - QVERIFY(!searchEdit->isVisibleTo(&available)); -#endif - QVERIFY(searchEdit->isClearButtonShown()); - QCOMPARE(searchEdit->placeholderText(), tr("Search...")); + QVERIFY(delegate); auto proxy = qobject_cast(sourcesView->model()); QVERIFY(proxy); QVERIFY(proxy->dynamicSortFilter()); QCOMPARE(proxy->sortColumn(), 0); QCOMPARE(proxy->sortOrder(), Qt::AscendingOrder); auto actionBar = available.findChild(QStringLiteral("actionBar")); QVERIFY(actionBar); QVERIFY(actionBar->isVisibleTo(&available)); auto defaultAction = available.findChild(QStringLiteral("defaultAction")); QVERIFY(defaultAction); auto settingsAction = available.findChild(QStringLiteral("settingsAction")); QVERIFY(settingsAction); auto actions = available.globalActions(); QCOMPARE(actions.value(QStringLiteral("options_configure")), settingsAction); } void shouldDisplayListFromPageModel() { // GIVEN QStringListModel model(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C") ); QObject stubPagesModel; stubPagesModel.setProperty("sourceListModel", QVariant::fromValue(static_cast(&model))); Widgets::AvailableSourcesView available; auto sourcesView = available.findChild(QStringLiteral("sourcesView")); QVERIFY(sourcesView); auto proxy = qobject_cast(sourcesView->model()); QVERIFY(proxy); QVERIFY(!proxy->sourceModel()); // WHEN available.setModel(&stubPagesModel); QTest::qWait(10); // THEN QCOMPARE(proxy->sourceModel(), &model); } void shouldNotCrashWithNullModel() { // GIVEN QStringListModel model(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C") ); QObject stubPagesModel; stubPagesModel.setProperty("sourceListModel", QVariant::fromValue(static_cast(&model))); Widgets::AvailableSourcesView available; auto sourcesView = available.findChild(QStringLiteral("sourcesView")); QVERIFY(sourcesView); auto proxy = qobject_cast(sourcesView->model()); QVERIFY(proxy); QVERIFY(!proxy->sourceModel()); available.setModel(&stubPagesModel); QTest::qWait(10); // WHEN available.setModel(Q_NULLPTR); QTest::qWait(10); // THEN QVERIFY(!available.isEnabled()); QVERIFY(!proxy->sourceModel()); } void shouldSetSelectedAsDefault() { // GIVEN QStandardItemModel model; auto itemA = new QStandardItem(QStringLiteral("A")); auto sourceA = Domain::DataSource::Ptr::create(); sourceA->setContentTypes(Domain::DataSource::Tasks); itemA->setData(QVariant::fromValue(sourceA), Presentation::QueryTreeModelBase::ObjectRole); model.appendRow(itemA); auto itemB = new QStandardItem(QStringLiteral("B")); auto sourceB = Domain::DataSource::Ptr::create(); sourceB->setContentTypes(Domain::DataSource::Tasks); itemB->setData(QVariant::fromValue(sourceB), Presentation::QueryTreeModelBase::ObjectRole); model.appendRow(itemB); auto itemC = new QStandardItem(QStringLiteral("C")); auto sourceC = Domain::DataSource::Ptr::create(); sourceC->setContentTypes(Domain::DataSource::NoContent); itemC->setData(QVariant::fromValue(sourceC), Presentation::QueryTreeModelBase::ObjectRole); model.appendRow(itemC); AvailableSourcesModelStub stubSourcesModel; stubSourcesModel.setProperty("sourceListModel", QVariant::fromValue(static_cast(&model))); Widgets::AvailableSourcesView available; available.setModel(&stubSourcesModel); auto sourcesView = available.findChild(QStringLiteral("sourcesView")); QVERIFY(sourcesView); auto proxy = qobject_cast(sourcesView->model()); QVERIFY(proxy); auto defaultAction = available.findChild(QStringLiteral("defaultAction")); QVERIFY(defaultAction); // WHEN auto selectedIndex = QPersistentModelIndex(model.index(1, 0)); sourcesView->setCurrentIndex(proxy->mapFromSource(selectedIndex)); // THEN QVERIFY(defaultAction->isEnabled()); // WHEN defaultAction->trigger(); // THEN QCOMPARE(stubSourcesModel.defaultIndex, selectedIndex); // WHEN sourcesView->selectionModel()->clear(); // THEN QVERIFY(!defaultAction->isEnabled()); // WHEN selectedIndex = QPersistentModelIndex(model.index(2, 0)); sourcesView->setCurrentIndex(proxy->mapFromSource(selectedIndex)); // THEN QVERIFY(!defaultAction->isEnabled()); } void shouldInvokeModelSettingsDialog() { // GIVEN AvailableSourcesModelStub stubSourcesModel; QVERIFY(!stubSourcesModel.settingsCalled); Widgets::AvailableSourcesView available; available.setModel(&stubSourcesModel); auto settingsAction = available.findChild(QStringLiteral("settingsAction")); QVERIFY(settingsAction); // WHEN settingsAction->trigger(); // THEN QVERIFY(stubSourcesModel.settingsCalled); } - - void shouldListASourceWhenTheDelegateButtonIsClicked() - { - // GIVEN - auto source = Domain::DataSource::Ptr::create(); - - QStringListModel model(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C") ); - AvailableSourcesModelStub stubPagesModel; - stubPagesModel.setProperty("sourceListModel", QVariant::fromValue(static_cast(&model))); - - Widgets::AvailableSourcesView available; - auto sourcesDelegate = available.findChild(); - QVERIFY(sourcesDelegate); - available.setModel(&stubPagesModel); - QTest::qWait(10); - - // WHEN - QVERIFY(QMetaObject::invokeMethod(sourcesDelegate, "actionTriggered", - Q_ARG(Domain::DataSource::Ptr, source), - Q_ARG(int, Widgets::DataSourceDelegate::AddToList))); - - // THEN - QCOMPARE(stubPagesModel.listedSources.size(), 1); - QCOMPARE(stubPagesModel.listedSources.first(), source); - } - - void shouldUnlistASourceWhenTheDelegateButtonIsClicked() - { - // GIVEN - auto source = Domain::DataSource::Ptr::create(); - - QStringListModel model(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C") ); - AvailableSourcesModelStub stubPagesModel; - stubPagesModel.setProperty("sourceListModel", QVariant::fromValue(static_cast(&model))); - - Widgets::AvailableSourcesView available; - auto sourcesDelegate = available.findChild(); - QVERIFY(sourcesDelegate); - available.setModel(&stubPagesModel); - QTest::qWait(10); - - // WHEN - QVERIFY(QMetaObject::invokeMethod(sourcesDelegate, "actionTriggered", - Q_ARG(Domain::DataSource::Ptr, source), - Q_ARG(int, Widgets::DataSourceDelegate::RemoveFromList))); - - // THEN - QCOMPARE(stubPagesModel.unlistedSources.size(), 1); - QCOMPARE(stubPagesModel.unlistedSources.first(), source); - } - - void shouldBookmarkASourceWhenTheDelegateButtonIsClicked() - { - // GIVEN - auto source = Domain::DataSource::Ptr::create(); - - QStringListModel model(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C") ); - AvailableSourcesModelStub stubPagesModel; - stubPagesModel.setProperty("sourceListModel", QVariant::fromValue(static_cast(&model))); - - Widgets::AvailableSourcesView available; - auto sourcesDelegate = available.findChild(); - QVERIFY(sourcesDelegate); - available.setModel(&stubPagesModel); - QTest::qWait(10); - - // WHEN - QVERIFY(QMetaObject::invokeMethod(sourcesDelegate, "actionTriggered", - Q_ARG(Domain::DataSource::Ptr, source), - Q_ARG(int, Widgets::DataSourceDelegate::Bookmark))); - - // THEN - QCOMPARE(stubPagesModel.bookmarkedSources.size(), 1); - QCOMPARE(stubPagesModel.bookmarkedSources.first(), source); - } - - void shouldSwitchToSearchListWhenASearchTermIsGiven() - { - // GIVEN - QStringListModel sourceModel(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C") ); - QStringListModel searchModel(QStringList() << QStringLiteral("D") << QStringLiteral("E") << QStringLiteral("F") ); - - AvailableSourcesModelStub stubPagesModel; - stubPagesModel.setProperty("sourceListModel", QVariant::fromValue(static_cast(&sourceModel))); - stubPagesModel.setProperty("searchListModel", QVariant::fromValue(static_cast(&searchModel))); - - Widgets::AvailableSourcesView available; - auto sourcesView = available.findChild(QStringLiteral("sourcesView")); - QVERIFY(sourcesView); - auto proxy = qobject_cast(sourcesView->model()); - QVERIFY(proxy); - available.setModel(&stubPagesModel); - QCOMPARE(proxy->sourceModel(), &sourceModel); - - auto searchEdit = available.findChild(QStringLiteral("searchEdit")); - QVERIFY(searchEdit); - - // WHEN - QTest::keyClick(searchEdit, 'm'); - - // THEN - QCOMPARE(proxy->sourceModel(), &sourceModel); - QVERIFY(stubPagesModel.searchTerm().isEmpty()); - - // WHEN - QTest::keyClick(searchEdit, 'y'); - - // THEN - QCOMPARE(proxy->sourceModel(), &sourceModel); - QVERIFY(stubPagesModel.searchTerm().isEmpty()); - - // WHEN - QTest::keyClick(searchEdit, ' '); - - // THEN - QCOMPARE(proxy->sourceModel(), &searchModel); - QCOMPARE(stubPagesModel.searchTerm(), QStringLiteral("my ")); - - // WHEN - QTest::keyClicks(searchEdit, QStringLiteral("term")); - - // THEN - QCOMPARE(proxy->sourceModel(), &searchModel); - QCOMPARE(stubPagesModel.searchTerm(), QStringLiteral("my term")); - - // WHEN - searchEdit->clear(); - - // THEN - QCOMPARE(proxy->sourceModel(), &sourceModel); - QVERIFY(stubPagesModel.searchTerm().isEmpty()); - } }; ZANSHIN_TEST_MAIN(AvailableSourcesViewTest) #include "availablesourcesviewtest.moc" diff --git a/tests/units/widgets/datasourcedelegatetest.cpp b/tests/units/widgets/datasourcedelegatetest.cpp deleted file mode 100644 index 14f60982..00000000 --- a/tests/units/widgets/datasourcedelegatetest.cpp +++ /dev/null @@ -1,150 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens - - 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) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - 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 "domain/datasource.h" - -#include "presentation/querytreemodelbase.h" - -#include "widgets/datasourcedelegate.h" - -Q_DECLARE_METATYPE(QList) - -class DataSourceDelegateTest : public QObject -{ - Q_OBJECT -public: - explicit DataSourceDelegateTest(QObject *parent = Q_NULLPTR) - : QObject(parent) - { - qRegisterMetaType(); - } - - QStandardItem *itemFromSource(const Domain::DataSource::Ptr &source) - { - auto item = new QStandardItem; - item->setText(source->name()); - item->setIcon(QIcon::fromTheme(source->iconName())); - item->setData(QVariant::fromValue(source), - Presentation::QueryTreeModelBase::ObjectRole); - return item; - } - -private slots: - void shouldHandleClickOnButtons_data() - { - QTest::addColumn("actionsEnabled"); - QTest::addColumn("source"); - QTest::addColumn>("expectedActions"); - - QList actions; - auto source = Domain::DataSource::Ptr::create(); - source->setName(QStringLiteral("No Content")); - source->setIconName(QStringLiteral("folder")); - source->setListStatus(Domain::DataSource::Bookmarked); - QTest::newRow("no content") << true << source << actions; - - actions.clear(); - actions << Widgets::DataSourceDelegate::AddToList; - source = Domain::DataSource::Ptr::create(); - source->setName(QStringLiteral("Not listed")); - source->setIconName(QStringLiteral("folder")); - source->setContentTypes(Domain::DataSource::Tasks); - source->setListStatus(Domain::DataSource::Unlisted); - QTest::newRow("not listed") << true << source << actions; - - actions.clear(); - actions << Widgets::DataSourceDelegate::Bookmark << Widgets::DataSourceDelegate::RemoveFromList; - source = Domain::DataSource::Ptr::create(); - source->setName(QStringLiteral("Listed")); - source->setIconName(QStringLiteral("folder")); - source->setContentTypes(Domain::DataSource::Tasks); - source->setListStatus(Domain::DataSource::Listed); - QTest::newRow("listed") << true << source << actions; - - actions.clear(); - actions << Widgets::DataSourceDelegate::Bookmark << Widgets::DataSourceDelegate::RemoveFromList; - source = Domain::DataSource::Ptr::create(); - source->setName(QStringLiteral("Bookmarked")); - source->setIconName(QStringLiteral("folder")); - source->setContentTypes(Domain::DataSource::Tasks); - source->setListStatus(Domain::DataSource::Bookmarked); - QTest::newRow("bookmarked") << true << source << actions; - - actions.clear(); - source = Domain::DataSource::Ptr::create(); - source->setName(QStringLiteral("Listed")); - source->setIconName(QStringLiteral("folder")); - source->setContentTypes(Domain::DataSource::Tasks); - source->setListStatus(Domain::DataSource::Listed); - QTest::newRow("actions disabled") << false << source << actions; - } - - void shouldHandleClickOnButtons() - { - // GIVEN - QFETCH(bool, actionsEnabled); - QFETCH(Domain::DataSource::Ptr, source); - QFETCH(QList, expectedActions); - - Widgets::DataSourceDelegate delegate; - delegate.setActionsEnabled(actionsEnabled); - QSignalSpy spy(&delegate, &Widgets::DataSourceDelegate::actionTriggered); - - QStandardItemModel model; - model.appendRow(itemFromSource(source)); - - QTreeView view; - view.header()->hide(); - view.setItemDelegate(&delegate); - view.setModel(&model); - view.show(); - QTest::qWaitForWindowShown(&view); - - // WHEN - QPoint clickPoint = view.rect().topRight() + QPoint(-30, 10); - for (int i = 0; i < 5; i++) { - QTest::mouseClick(view.viewport(), Qt::LeftButton, Qt::NoModifier, clickPoint); - clickPoint += QPoint(-20, 0); - } - - // THEN - QList actions; - while (!spy.isEmpty()) { - auto parameters = spy.takeFirst(); - QCOMPARE(parameters.first().value(), source); - actions << parameters.last().toInt(); - } - QCOMPARE(actions, expectedActions); - } -}; - -ZANSHIN_TEST_MAIN(DataSourceDelegateTest) - -#include "datasourcedelegatetest.moc"