diff --git a/src/akonadi/akonadicontextqueries.cpp b/src/akonadi/akonadicontextqueries.cpp index 845ebae0..a9c684a1 100644 --- a/src/akonadi/akonadicontextqueries.cpp +++ b/src/akonadi/akonadicontextqueries.cpp @@ -1,84 +1,87 @@ /* This file is part of Zanshin Copyright 2014 Franck Arrecot 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 "akonadicontextqueries.h" using namespace Akonadi; ContextQueries::ContextQueries(const StorageInterface::Ptr &storage, const SerializerInterface::Ptr &serializer, const MonitorInterface::Ptr &monitor, const Cache::Ptr &cache) : m_serializer(serializer), m_cache(cache), m_helpers(new LiveQueryHelpers(serializer, storage)), m_integrator(new LiveQueryIntegrator(serializer, monitor)) { - m_integrator->addRemoveHandler([this] (const Tag &tag) { - m_findToplevel.remove(tag.id()); + m_integrator->addRemoveHandler([this] (const Item &contextItem) { + const auto uid = m_serializer->contextUid(contextItem); + if (!uid.isEmpty()) + m_findToplevel.remove(uid); }); } ContextQueries::ContextResult::Ptr ContextQueries::findAll() const { - auto fetch = m_helpers->fetchTags(); - auto predicate = [this] (const Akonadi::Tag &tag) { - return tag.type() == Akonadi::SerializerInterface::contextTagType(); + auto fetch = m_helpers->fetchItems(); + auto predicate = [this] (const Akonadi::Item &item) { + return m_serializer->isContext(item); }; m_integrator->bind("ContextQueries::findAll", m_findAll, fetch, predicate); return m_findAll->result(); } ContextQueries::TaskResult::Ptr ContextQueries::findTopLevelTasks(Domain::Context::Ptr context) const { - Akonadi::Tag tag = m_serializer->createTagFromContext(context); - auto fetch = m_helpers->fetchItems(tag); + Q_ASSERT(context); + auto fetch = m_helpers->fetchItemsForContext(context); auto predicate = [this, context] (const Akonadi::Item &item) { if (!m_serializer->isContextChild(context, item)) return false; const auto items = m_cache->items(item.parentCollection()); auto currentItem = item; auto parentUid = m_serializer->relatedUidFromItem(currentItem); while (!parentUid.isEmpty()) { const auto parent = std::find_if(items.cbegin(), items.cend(), [this, parentUid] (const Akonadi::Item &item) { return m_serializer->itemUid(item) == parentUid; }); if (parent == items.cend()) break; if (m_serializer->isContextChild(context, *parent)) return false; currentItem = *parent; parentUid = m_serializer->relatedUidFromItem(currentItem); } return true; }; - auto &query = m_findToplevel[tag.id()]; + auto contextUid = context->property("todoUid").toString(); + auto &query = m_findToplevel[contextUid]; m_integrator->bind("ContextQueries::findTopLevelTasks", query, fetch, predicate); return query->result(); } diff --git a/src/akonadi/akonadicontextqueries.h b/src/akonadi/akonadicontextqueries.h index 8b85237e..15e40213 100644 --- a/src/akonadi/akonadicontextqueries.h +++ b/src/akonadi/akonadicontextqueries.h @@ -1,72 +1,73 @@ /* This file is part of Zanshin Copyright 2014 Franck Arrecot 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_CONTEXTQUERIES_H #define AKONADI_CONTEXTQUERIES_H #include "domain/contextqueries.h" #include "akonadi/akonadicache.h" #include "akonadi/akonadilivequeryhelpers.h" #include "akonadi/akonadilivequeryintegrator.h" namespace Akonadi { class ContextQueries : public Domain::ContextQueries { public: typedef QSharedPointer Ptr; typedef Domain::LiveQueryInput ItemInputQuery; typedef Domain::LiveQueryOutput TaskQueryOutput; typedef Domain::QueryResult TaskResult; typedef Domain::QueryResultProvider TaskProvider; typedef Domain::LiveQueryInput TagInputQuery; typedef Domain::LiveQueryOutput ContextQueryOutput; typedef Domain::QueryResult ContextResult; typedef Domain::QueryResultProvider ContextProvider; ContextQueries(const StorageInterface::Ptr &storage, const SerializerInterface::Ptr &serializer, const MonitorInterface::Ptr &monitor, const Cache::Ptr &cache); ContextResult::Ptr findAll() const override; TaskResult::Ptr findTopLevelTasks(Domain::Context::Ptr context) const override; private: SerializerInterface::Ptr m_serializer; Cache::Ptr m_cache; LiveQueryHelpers::Ptr m_helpers; LiveQueryIntegrator::Ptr m_integrator; mutable ContextQueryOutput::Ptr m_findAll; - mutable QHash m_findToplevel; + using ContextUid = QString; + mutable QHash m_findToplevel; }; } // akonadi namespace #endif // AKONADI_CONTEXTQUERIES_H diff --git a/src/akonadi/akonadicontextrepository.cpp b/src/akonadi/akonadicontextrepository.cpp index 87c47c3e..00a35520 100644 --- a/src/akonadi/akonadicontextrepository.cpp +++ b/src/akonadi/akonadicontextrepository.cpp @@ -1,136 +1,132 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens Copyright 2014 Franck Arrecot 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 "akonadicontextrepository.h" #include "akonadiitemfetchjobinterface.h" #include "utils/compositejob.h" using namespace Akonadi; ContextRepository::ContextRepository(const StorageInterface::Ptr &storage, const SerializerInterface::Ptr &serializer): m_storage(storage), m_serializer(serializer) { } KJob *ContextRepository::create(Domain::Context::Ptr context, Domain::DataSource::Ptr source) { auto item = m_serializer->createItemFromContext(context); Q_ASSERT(!item.isValid()); auto collection = m_serializer->createCollectionFromDataSource(source); Q_ASSERT(collection.isValid()); return m_storage->createItem(item, collection); } KJob *ContextRepository::update(Domain::Context::Ptr context) { auto item = m_serializer->createItemFromContext(context); Q_ASSERT(item.isValid()); return m_storage->updateItem(item); } KJob *ContextRepository::remove(Domain::Context::Ptr context) { auto item = m_serializer->createItemFromContext(context); Q_ASSERT(item.isValid()); return m_storage->removeItem(item); } KJob *ContextRepository::associate(Domain::Context::Ptr context, Domain::Task::Ptr child) { Item childItem = m_serializer->createItemFromTask(child); Q_ASSERT(childItem.isValid()); auto job = new Utils::CompositeJob(); ItemFetchJobInterface *fetchItemJob = m_storage->fetchItem(childItem); job->install(fetchItemJob->kjob(), [fetchItemJob, context, job, this] { if (fetchItemJob->kjob()->error() != KJob::NoError) return; Q_ASSERT(fetchItemJob->items().size() == 1); auto childItem = fetchItemJob->items().at(0); m_serializer->addContextToTask(context, childItem); auto updateJob = m_storage->updateItem(childItem); job->addSubjob(updateJob); updateJob->start(); }); return job; } -KJob *ContextRepository::dissociate(Domain::Context::Ptr parent, Domain::Task::Ptr child) +KJob *ContextRepository::dissociate(Domain::Context::Ptr context, Domain::Task::Ptr child) { - Item childItem; - - childItem = m_serializer->createItemFromTask(child); + Item childItem = m_serializer->createItemFromTask(child); Q_ASSERT(childItem.isValid()); auto job = new Utils::CompositeJob(); ItemFetchJobInterface *fetchItemJob = m_storage->fetchItem(childItem); - job->install(fetchItemJob->kjob(), [fetchItemJob, parent, job, this] { + job->install(fetchItemJob->kjob(), [fetchItemJob, context, job, this] { if (fetchItemJob->kjob()->error() != KJob::NoError) return; Q_ASSERT(fetchItemJob->items().size() == 1); auto childItem = fetchItemJob->items().at(0); - auto tag = m_serializer->createTagFromContext(parent); - Q_ASSERT(tag.isValid()); - childItem.clearTag(tag); + m_serializer->removeContextFromTask(context, childItem); auto updateJob = m_storage->updateItem(childItem); job->addSubjob(updateJob); updateJob->start(); }); return job; } KJob *ContextRepository::dissociateAll(Domain::Task::Ptr child) { Item childItem; childItem = m_serializer->createItemFromTask(child); Q_ASSERT(childItem.isValid()); auto job = new Utils::CompositeJob(); ItemFetchJobInterface *fetchItemJob = m_storage->fetchItem(childItem); job->install(fetchItemJob->kjob(), [fetchItemJob, job, this] { if (fetchItemJob->kjob()->error() != KJob::NoError) return; Q_ASSERT(fetchItemJob->items().size() == 1); auto childItem = fetchItemJob->items().at(0); childItem.clearTags(); auto updateJob = m_storage->updateItem(childItem); job->addSubjob(updateJob); updateJob->start(); }); return job; } diff --git a/src/akonadi/akonadicontextrepository.h b/src/akonadi/akonadicontextrepository.h index d01f3c8d..5e8bed08 100644 --- a/src/akonadi/akonadicontextrepository.h +++ b/src/akonadi/akonadicontextrepository.h @@ -1,59 +1,59 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens Copyright 2014 Franck Arrecot 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 AKONADICONTEXTREPOSITORY_H #define AKONADICONTEXTREPOSITORY_H #include "domain/contextrepository.h" #include "akonadi/akonadiserializerinterface.h" #include "akonadi/akonadistorageinterface.h" namespace Akonadi { class ContextRepository : public QObject, public Domain::ContextRepository { Q_OBJECT public: typedef QSharedPointer Ptr; ContextRepository(const StorageInterface::Ptr &storage, const SerializerInterface::Ptr &serializer); KJob *create(Domain::Context::Ptr context, Domain::DataSource::Ptr source) override; KJob *update(Domain::Context::Ptr context) override; KJob *remove(Domain::Context::Ptr context) override; KJob *associate(Domain::Context::Ptr context, Domain::Task::Ptr child) override; - KJob *dissociate(Domain::Context::Ptr parent, Domain::Task::Ptr child) override; + KJob *dissociate(Domain::Context::Ptr context, Domain::Task::Ptr child) override; KJob *dissociateAll(Domain::Task::Ptr child) override; private: StorageInterface::Ptr m_storage; SerializerInterface::Ptr m_serializer; }; } #endif // AKONADICONTEXTREPOSITORY_H diff --git a/src/akonadi/akonadilivequeryhelpers.cpp b/src/akonadi/akonadilivequeryhelpers.cpp index 1952c659..5c7c29e2 100644 --- a/src/akonadi/akonadilivequeryhelpers.cpp +++ b/src/akonadi/akonadilivequeryhelpers.cpp @@ -1,245 +1,231 @@ /* 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/akonadiitemfetchjobinterface.h" #include "akonadi/akonaditagfetchjobinterface.h" #include "akonadi/akonadistorageinterface.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() const { auto storage = m_storage; return [storage] (const Domain::LiveQueryInput::AddFunction &add) { auto job = storage->fetchCollections(Collection::root(), StorageInterface::Recursive); 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) const { auto storage = m_storage; return [storage, root] (const Domain::LiveQueryInput::AddFunction &add) { auto job = storage->fetchCollections(root, StorageInterface::Recursive); 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::ItemFetchFunction LiveQueryHelpers::fetchItems() const { auto serializer = m_serializer; auto storage = m_storage; return [serializer, storage] (const Domain::LiveQueryInput::AddFunction &add) { auto job = storage->fetchCollections(Akonadi::Collection::root(), StorageInterface::Recursive); 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 Collection &collection) const { auto storage = m_storage; return [storage, collection] (const Domain::LiveQueryInput::AddFunction &add) { 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 +LiveQueryHelpers::ItemFetchFunction LiveQueryHelpers::fetchItemsForContext(const Domain::Context::Ptr &context) 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(); + auto serializer = m_serializer; - return [tag, fetchFunction] (const Domain::LiveQueryInput::AddFunction &add) { - auto filterAdd = [tag, add] (const Item &item) { - if (item.tags().contains(tag)) + return [context, fetchFunction, serializer] (const Domain::LiveQueryInput::AddFunction &add) { + auto filterAdd = [context, add, serializer] (const Item &item) { + if (serializer->isContextChild(context, item)) add(item); }; fetchFunction(filterAdd); }; -#endif } LiveQueryHelpers::ItemFetchFunction LiveQueryHelpers::fetchTaskAndAncestors(Domain::Task::Ptr task) const { Akonadi::Item childItem = m_serializer->createItemFromTask(task); Q_ASSERT(childItem.parentCollection().isValid()); // do I really need a fetchItem first, like fetchSiblings does? // Note: if the task moves to another collection, this live query will then be invalid... const Akonadi::Item::Id childId = childItem.id(); auto storage = m_storage; auto serializer = m_serializer; return [storage, serializer, childItem, childId] (const Domain::LiveQueryInput::AddFunction &add) { auto job = storage->fetchItems(childItem.parentCollection()); Utils::JobHandler::install(job->kjob(), [job, add, serializer, childId] { if (job->kjob()->error() != KJob::NoError) return; const auto items = job->items(); // The item itself is part of the result, we need that in findProject, to react on changes of the item itself // To return a correct child item in case it got updated, we can't use childItem, we need to find it in the list. const auto myself = std::find_if(items.cbegin(), items.cend(), [childId] (const Akonadi::Item &item) { return childId == item.id(); }); if (myself == items.cend()) { qWarning() << "Did not find item in the listing for its parent collection. Item ID:" << childId; return; } add(*myself); auto parentUid = serializer->relatedUidFromItem(*myself); while (!parentUid.isEmpty()) { const auto parent = std::find_if(items.cbegin(), items.cend(), [serializer, parentUid] (const Akonadi::Item &item) { return serializer->itemUid(item) == parentUid; }); if (parent == items.cend()) { break; } add(*parent); parentUid = serializer->relatedUidFromItem(*parent); } }); }; } LiveQueryHelpers::CollectionFetchFunction LiveQueryHelpers::fetchItemCollection(const Item& item) const { auto storage = m_storage; return [storage, item] (const Domain::LiveQueryInput::AddFunction &add) { auto job = storage->fetchCollections(item.parentCollection(), StorageInterface::Base); Utils::JobHandler::install(job->kjob(), [storage, job, add] { if (job->kjob()->error() != KJob::NoError) return; auto collection = job->collections().at(0); add(collection); }); }; } 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 8be8cb53..83295cee 100644 --- a/src/akonadi/akonadilivequeryhelpers.h +++ b/src/akonadi/akonadilivequeryhelpers.h @@ -1,70 +1,70 @@ /* 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" #include "domain/task.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() const; CollectionFetchFunction fetchCollections(const Collection &root) const; CollectionFetchFunction fetchItemCollection(const Item &item) const; ItemFetchFunction fetchItems() const; ItemFetchFunction fetchItems(const Collection &collection) const; - ItemFetchFunction fetchItems(const Tag &tag) const; + ItemFetchFunction fetchItemsForContext(const Domain::Context::Ptr &context) const; /// Returns a fetch function which calls a LiveQueryInput::AddFunction (provided as argument to the fetch function) /// with the given task, then its parent, its grandparent etc. up until the project. ItemFetchFunction fetchTaskAndAncestors(Domain::Task::Ptr task) 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/akonadilivequeryintegrator.h b/src/akonadi/akonadilivequeryintegrator.h index fb8ac611..b7f8c094 100644 --- a/src/akonadi/akonadilivequeryintegrator.h +++ b/src/akonadi/akonadilivequeryintegrator.h @@ -1,351 +1,369 @@ /* 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_LIVEQUERYINTEGRATOR_H #define AKONADI_LIVEQUERYINTEGRATOR_H #include #include #include #include #include #include #include "akonadi/akonadimonitorinterface.h" #include "akonadi/akonadiserializerinterface.h" #include "domain/livequery.h" namespace Akonadi { class LiveQueryIntegrator : public QObject { Q_OBJECT // Helper type trait to extract parameter and return types from // a function object // Lambda or functors (via method of const method) template struct UnaryFunctionTraits : public UnaryFunctionTraits {}; // Traits definition template struct UnaryFunctionTraits { typedef Return ReturnType; typedef Arg ArgType; }; // Function pointers template struct UnaryFunctionTraits : public UnaryFunctionTraits {}; // Method template struct UnaryFunctionTraits : public UnaryFunctionTraits {}; // Const method template struct UnaryFunctionTraits : public UnaryFunctionTraits {}; // std::function object template struct UnaryFunctionTraits> : public UnaryFunctionTraits {}; // const reference to std::function object template struct UnaryFunctionTraits &> : public UnaryFunctionTraits {}; public: typedef QSharedPointer Ptr; typedef std::function CollectionRemoveHandler; typedef std::function ItemRemoveHandler; typedef std::function TagRemoveHandler; LiveQueryIntegrator(const SerializerInterface::Ptr &serializer, const MonitorInterface::Ptr &monitor, QObject *parent = nullptr); template void bind(const QByteArray &debugName, QSharedPointer> &output, FetchFunction fetch, PredicateFunction predicate, ExtraArgs... extra) { typedef UnaryFunctionTraits FetchTraits; typedef UnaryFunctionTraits AddTraits; typedef UnaryFunctionTraits PredicateTraits; typedef typename std::decay::type InputType; // typically Akonadi::Item static_assert(std::is_same::value, "Fetch function must return void"); static_assert(std::is_same::value, "Fetch add function must return void"); static_assert(std::is_same::value, "Predicate function must return bool"); typedef typename std::decay::type AddInputType; static_assert(std::is_same::value, "Fetch add and predicate functions must have the same input type"); if (output) return; using namespace std::placeholders; auto query = Domain::LiveQuery::Ptr::create(); query->setDebugName(debugName); query->setFetchFunction(fetch); query->setPredicateFunction(predicate); query->setConvertFunction(std::bind(&LiveQueryIntegrator::create, this, _1, extra...)); query->setUpdateFunction(std::bind(&LiveQueryIntegrator::update, this, _1, _2, extra...)); query->setRepresentsFunction(std::bind(&LiveQueryIntegrator::represents, this, _1, _2)); inputQueries() << query; output = query; } template void bindRelationship(const QByteArray &debugName, QSharedPointer> &output, FetchFunction fetch, CompareFunction compare, PredicateFunction predicate, ExtraArgs... extra) { typedef UnaryFunctionTraits FetchTraits; typedef UnaryFunctionTraits AddTraits; typedef UnaryFunctionTraits PredicateTraits; typedef typename std::decay::type InputType; // typically Akonadi::Item static_assert(std::is_same::value, "Fetch function must return void"); static_assert(std::is_same::value, "Fetch add function must return void"); static_assert(std::is_same::value, "Predicate function must return bool"); typedef typename std::decay::type AddInputType; static_assert(std::is_same::value, "Fetch add and predicate functions must have the same input type"); if (output) return; using namespace std::placeholders; auto query = Domain::LiveRelationshipQuery::Ptr::create(); query->setDebugName(debugName); query->setFetchFunction(fetch); query->setCompareFunction(compare); query->setPredicateFunction(predicate); query->setConvertFunction(std::bind(&LiveQueryIntegrator::create, this, _1, extra...)); query->setRepresentsFunction(std::bind(&LiveQueryIntegrator::represents, this, _1, _2)); inputQueries() << query; output = query; } void addRemoveHandler(const CollectionRemoveHandler &handler); void addRemoveHandler(const ItemRemoveHandler &handler); void addRemoveHandler(const TagRemoveHandler &handler); private slots: void onCollectionSelectionChanged(); void onCollectionAdded(const Akonadi::Collection &collection); void onCollectionRemoved(const Akonadi::Collection &collection); void onCollectionChanged(const Akonadi::Collection &collection); void onItemAdded(const Akonadi::Item &item); void onItemRemoved(const Akonadi::Item &item); void onItemChanged(const Akonadi::Item &item); void onTagAdded(const Akonadi::Tag &tag); void onTagRemoved(const Akonadi::Tag &tag); void onTagChanged(const Akonadi::Tag &tag); private: void cleanupQueries(); template OutputType create(const InputType &input, ExtraArgs... extra); template void update(const InputType &input, OutputType &output, ExtraArgs... extra); template bool represents(const InputType &input, const OutputType &output); template typename Domain::LiveQueryInput::WeakList &inputQueries(); Domain::LiveQueryInput::WeakList m_collectionInputQueries; Domain::LiveQueryInput::WeakList m_itemInputQueries; Domain::LiveQueryInput::WeakList m_tagInputQueries; QList m_collectionRemoveHandlers; QList m_itemRemoveHandlers; QList m_tagRemoveHandlers; SerializerInterface::Ptr m_serializer; MonitorInterface::Ptr m_monitor; }; +template<> +inline Domain::Context::Ptr LiveQueryIntegrator::create(const Item &input) +{ + return m_serializer->createContextFromItem(input); +} + +template<> +inline void LiveQueryIntegrator::update(const Item &input, Domain::Context::Ptr &output) +{ + m_serializer->updateContextFromItem(output, input); +} + +template<> +inline bool LiveQueryIntegrator::represents(const Item &input, const Domain::Context::Ptr &output) +{ + return m_serializer->itemRepresentsContext(output, input); +} + template<> inline Domain::Context::Ptr LiveQueryIntegrator::create(const Tag &input) { return m_serializer->createContextFromTag(input); } template<> inline void LiveQueryIntegrator::update(const Tag &input, Domain::Context::Ptr &output) { m_serializer->updateContextFromTag(output, input); } template<> inline bool LiveQueryIntegrator::represents(const Tag &input, const Domain::Context::Ptr &output) { return m_serializer->isContextTag(output, input); } template<> inline Domain::DataSource::Ptr LiveQueryIntegrator::create(const Collection &input) { return m_serializer->createDataSourceFromCollection(input, SerializerInterface::BaseName); } template<> inline void LiveQueryIntegrator::update(const Collection &input, Domain::DataSource::Ptr &output) { m_serializer->updateDataSourceFromCollection(output, input, SerializerInterface::BaseName); } template<> inline Domain::DataSource::Ptr LiveQueryIntegrator::create(const Collection &input, SerializerInterface::DataSourceNameScheme nameScheme) { return m_serializer->createDataSourceFromCollection(input, nameScheme); } template<> inline void LiveQueryIntegrator::update(const Collection &input, Domain::DataSource::Ptr &output, SerializerInterface::DataSourceNameScheme nameScheme) { m_serializer->updateDataSourceFromCollection(output, input, nameScheme); } template<> inline bool LiveQueryIntegrator::represents(const Collection &input, const Domain::DataSource::Ptr &output) { return m_serializer->representsCollection(output, input); } template<> inline Domain::DataSource::Ptr LiveQueryIntegrator::create(const Item &input) { return m_serializer->createDataSourceFromCollection(input.parentCollection(), SerializerInterface::BaseName); } template<> inline void LiveQueryIntegrator::update(const Item &input, Domain::DataSource::Ptr &output) { m_serializer->updateDataSourceFromCollection(output, input.parentCollection(), SerializerInterface::BaseName); } template<> inline bool LiveQueryIntegrator::represents(const Item &input, const Domain::DataSource::Ptr &output) { return m_serializer->representsCollection(output, input.parentCollection()); } template<> inline Domain::Project::Ptr LiveQueryIntegrator::create(const Item &input) { return m_serializer->createProjectFromItem(input); } template<> inline void LiveQueryIntegrator::update(const Item &input, Domain::Project::Ptr &output) { m_serializer->updateProjectFromItem(output, input); } template<> inline bool LiveQueryIntegrator::represents(const Item &input, const Domain::Project::Ptr &output) { return m_serializer->representsItem(output, input); } template<> inline Domain::Task::Ptr LiveQueryIntegrator::create(const Item &input) { return m_serializer->createTaskFromItem(input); } template<> inline void LiveQueryIntegrator::update(const Item &input, Domain::Task::Ptr &output) { m_serializer->updateTaskFromItem(output, input); } template<> inline bool LiveQueryIntegrator::represents(const Item &input, const Domain::Task::Ptr &output) { return m_serializer->representsItem(output, input); } template<> inline typename Domain::LiveQueryInput::WeakList &LiveQueryIntegrator::inputQueries() { return m_collectionInputQueries; } template<> inline typename Domain::LiveQueryInput::WeakList &LiveQueryIntegrator::inputQueries() { return m_itemInputQueries; } template<> inline typename Domain::LiveQueryInput::WeakList &LiveQueryIntegrator::inputQueries() { return m_tagInputQueries; } } #endif // AKONADI_LIVEQUERYINTEGRATOR_H diff --git a/tests/testlib/gentodo.cpp b/tests/testlib/gentodo.cpp index 805672b7..089c6f92 100644 --- a/tests/testlib/gentodo.cpp +++ b/tests/testlib/gentodo.cpp @@ -1,152 +1,154 @@ /* 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 "gentodo.h" #include #include using namespace Testlib; +static const char s_contextListProperty[] = "ContextList"; +static const char s_appName[] = "Zanshin"; + GenTodo::GenTodo(const Akonadi::Item &item) : m_item(item) { m_item.setMimeType(KCalCore::Todo::todoMimeType()); if (!m_item.hasPayload()) m_item.setPayload(KCalCore::Todo::Ptr::create()); } Testlib::GenTodo::operator Akonadi::Item() { return m_item; } GenTodo &GenTodo::withId(Akonadi::Item::Id id) { m_item.setId(id); return *this; } GenTodo &GenTodo::withParent(Akonadi::Collection::Id id) { m_item.setParentCollection(Akonadi::Collection(id)); return *this; } -GenTodo &GenTodo::withTags(const QList &ids) +GenTodo &GenTodo::withContexts(const QStringList &contextUids) { - auto tags = Akonadi::Tag::List(); - std::transform(ids.constBegin(), ids.constEnd(), - std::back_inserter(tags), - [] (Akonadi::Tag::Id id) { - return Akonadi::Tag(id); - }); - m_item.setTags(tags); + auto todo = m_item.payload(); + if (contextUids.isEmpty()) + todo->removeCustomProperty(s_appName, s_contextListProperty); + else + todo->setCustomProperty(s_appName, s_contextListProperty, contextUids.join(',')); + m_item.setPayload(todo); return *this; } GenTodo &GenTodo::asProject(bool value) { auto todo = m_item.payload(); if (value) todo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); else todo->removeCustomProperty("Zanshin", "Project"); return *this; } GenTodo &GenTodo::asContext(bool value) { auto todo = m_item.payload(); if (value) todo->setCustomProperty("Zanshin", "Context", QStringLiteral("1")); else todo->removeCustomProperty("Zanshin", "Context"); return *this; } GenTodo &GenTodo::withUid(const QString &uid) { m_item.payload()->setUid(uid); return *this; } GenTodo &GenTodo::withParentUid(const QString &uid) { m_item.payload()->setRelatedTo(uid); return *this; } GenTodo &GenTodo::withTitle(const QString &title) { m_item.payload()->setSummary(title); return *this; } GenTodo &GenTodo::withText(const QString &text) { m_item.payload()->setDescription(text); return *this; } GenTodo &GenTodo::done(bool value) { m_item.payload()->setCompleted(value); return *this; } GenTodo &GenTodo::withDoneDate(const QString &date) { m_item.payload()->setCompleted(QDateTime(QDate::fromString(date, Qt::ISODate))); return *this; } GenTodo &GenTodo::withDoneDate(const QDate &date) { m_item.payload()->setCompleted(QDateTime(date)); return *this; } GenTodo &GenTodo::withStartDate(const QString &date) { m_item.payload()->setDtStart(QDateTime(QDate::fromString(date, Qt::ISODate))); return *this; } GenTodo &GenTodo::withStartDate(const QDate &date) { m_item.payload()->setDtStart(QDateTime(date)); return *this; } GenTodo &GenTodo::withDueDate(const QString &date) { m_item.payload()->setDtDue(QDateTime(QDate::fromString(date, Qt::ISODate))); return *this; } GenTodo &GenTodo::withDueDate(const QDate &date) { m_item.payload()->setDtDue(QDateTime(date)); return *this; } diff --git a/tests/testlib/gentodo.h b/tests/testlib/gentodo.h index 0c34bd5d..164a2cac 100644 --- a/tests/testlib/gentodo.h +++ b/tests/testlib/gentodo.h @@ -1,63 +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 TESTLIB_GENTODO_H #define TESTLIB_GENTODO_H #include #include namespace Testlib { class GenTodo { public: explicit GenTodo(const Akonadi::Item &item = Akonadi::Item()); operator Akonadi::Item(); GenTodo &withId(Akonadi::Item::Id id); GenTodo &withParent(Akonadi::Collection::Id id); - GenTodo &withTags(const QList &ids); + GenTodo &withContexts(const QStringList &contextUids); GenTodo &asProject(bool value = true); GenTodo &asContext(bool value = true); GenTodo &withUid(const QString &uid); GenTodo &withParentUid(const QString &uid); GenTodo &withTitle(const QString &title); GenTodo &withText(const QString &text); GenTodo &done(bool value = true); GenTodo &withDoneDate(const QString &date); GenTodo &withDoneDate(const QDate &date); GenTodo &withStartDate(const QString &date); GenTodo &withStartDate(const QDate &date); GenTodo &withDueDate(const QString &date); GenTodo &withDueDate(const QDate &date); private: Akonadi::Item m_item; }; } #endif // TESTLIB_GENTODO_H diff --git a/tests/units/akonadi/akonadicachetest.cpp b/tests/units/akonadi/akonadicachetest.cpp index 13472a82..171922af 100644 --- a/tests/units/akonadi/akonadicachetest.cpp +++ b/tests/units/akonadi/akonadicachetest.cpp @@ -1,649 +1,649 @@ /* This file is part of Zanshin Copyright 2017 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/akonadicache.h" #include "akonadi/akonadiserializer.h" #include "testlib/akonadifakemonitor.h" #include "testlib/gencollection.h" #include "testlib/gentodo.h" #include "testlib/gentag.h" using namespace Testlib; class AkonadiCacheTest : public QObject { Q_OBJECT private slots: void shouldHaveDefaultState() { // GIVEN auto monitor = AkonadiFakeMonitor::Ptr::create(); auto cache = Akonadi::Cache::Ptr::create(Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), monitor); // THEN QVERIFY(!cache->isCollectionListPopulated()); QVERIFY(cache->collections().isEmpty()); QVERIFY(!cache->isTagListPopulated()); QVERIFY(cache->tags().isEmpty()); } void shouldStoreCollectionsAndUpdate() { // GIVEN const auto noteCollection = Akonadi::Collection(GenCollection().withRootAsParent() .withId(1) .withName("notes") .withNoteContent()); const auto taskCollection = Akonadi::Collection(GenCollection().withRootAsParent() .withId(2) .withName("tasks") .withTaskContent()); const auto noteTaskCollection = Akonadi::Collection(GenCollection().withRootAsParent() .withId(3) .withName("tasks+notes") .withTaskContent() .withNoteContent()); const auto stuffCollection = Akonadi::Collection(GenCollection().withRootAsParent() .withId(4) .withName("stuff")); auto monitor = AkonadiFakeMonitor::Ptr::create(); auto cache = Akonadi::Cache::Ptr::create(Akonadi::Serializer::Ptr(new Akonadi::Serializer), monitor); // WHEN cache->setCollections(Akonadi::Collection::List() << stuffCollection << noteTaskCollection << taskCollection << noteCollection); // THEN QVERIFY(cache->isCollectionListPopulated()); QVERIFY(cache->isCollectionKnown(stuffCollection.id())); QVERIFY(!cache->isCollectionPopulated(stuffCollection.id())); QVERIFY(cache->items(stuffCollection).isEmpty()); QCOMPARE(cache->collections(), Akonadi::Collection::List() << noteTaskCollection << taskCollection); QCOMPARE(cache->allCollections(), Akonadi::Collection::List() << stuffCollection << noteTaskCollection << taskCollection << noteCollection); QCOMPARE(cache->collection(stuffCollection.id()), stuffCollection); QCOMPARE(cache->collection(stuffCollection.id()).name(), stuffCollection.name()); // WHEN monitor->changeCollection(GenCollection(stuffCollection).withName("stuff2")); // THEN QCOMPARE(cache->collection(stuffCollection.id()).name(), QStringLiteral("stuff2")); // WHEN monitor->changeCollection(GenCollection(noteTaskCollection).withName("note+task2")); // THEN QCOMPARE(cache->collection(noteTaskCollection.id()).name(), QStringLiteral("note+task2")); // WHEN monitor->changeCollection(GenCollection(taskCollection).withName("task2")); // THEN QCOMPARE(cache->collection(taskCollection.id()).name(), QStringLiteral("task2")); } void shouldHandleCollectionAdds_data() { QTest::addColumn("collection"); QTest::addColumn("seen"); const auto none = Akonadi::Collection(GenCollection().withRootAsParent() .withId(2) .withName("collection")); const auto task = Akonadi::Collection(GenCollection(none).withTaskContent()); const auto note = Akonadi::Collection(GenCollection(none).withNoteContent()); const auto taskNote = Akonadi::Collection(GenCollection(none).withNoteContent().withTaskContent()); QTest::newRow("tasks vs none") << none << false; QTest::newRow("tasks vs task") << task << true; QTest::newRow("tasks vs note") << note << false; QTest::newRow("tasks vs taskNote") << taskNote << true; } void shouldHandleCollectionAdds() { // GIVEN QFETCH(Akonadi::Collection, collection); auto monitor = AkonadiFakeMonitor::Ptr::create(); auto cache = Akonadi::Cache::Ptr::create(Akonadi::Serializer::Ptr(new Akonadi::Serializer), monitor); // WHEN monitor->addCollection(collection); // THEN QVERIFY(!cache->isCollectionListPopulated()); QVERIFY(cache->collections().isEmpty()); QCOMPARE(cache->collection(collection.id()), Akonadi::Collection()); // WHEN cache->setCollections(Akonadi::Collection::List()); monitor->addCollection(collection); // THEN QVERIFY(cache->isCollectionListPopulated()); QFETCH(bool, seen); if (seen) { QVERIFY(!cache->collections().isEmpty()); QCOMPARE(cache->collection(collection.id()), collection); QCOMPARE(cache->collection(collection.id()).name(), collection.name()); } else { QVERIFY(cache->collections().isEmpty()); QCOMPARE(cache->collection(collection.id()), Akonadi::Collection()); } } void shouldHandleCollectionChanges() { // GIVEN const auto collection = Akonadi::Collection(GenCollection().withRootAsParent() .withId(2) .withName("tasks") .withTaskContent()); auto monitor = AkonadiFakeMonitor::Ptr::create(); auto cache = Akonadi::Cache::Ptr::create(Akonadi::Serializer::Ptr(new Akonadi::Serializer), monitor); cache->setCollections(Akonadi::Collection::List() << collection); // WHEN const auto collection2 = Akonadi::Collection(GenCollection().withRootAsParent() .withId(2) .withName("tasks2") .withTaskContent()); monitor->changeCollection(collection2); // THEN QCOMPARE(cache->collection(collection.id()).name(), QStringLiteral("tasks2")); } void shouldStoreTagsAndUpdate() { // GIVEN const auto tag1 = Akonadi::Tag(GenTag().withId(1).asPlain().withName("tag1")); const auto tag2 = Akonadi::Tag(GenTag().withId(2).asContext().withName("tag2")); auto monitor = AkonadiFakeMonitor::Ptr::create(); auto cache = Akonadi::Cache::Ptr::create(Akonadi::Serializer::Ptr(new Akonadi::Serializer), monitor); // THEN QVERIFY(!cache->isTagKnown(tag1.id())); QVERIFY(!cache->isTagPopulated(tag1.id())); QVERIFY(!cache->isTagKnown(tag2.id())); QVERIFY(!cache->isTagPopulated(tag2.id())); // WHEN cache->setTags(Akonadi::Tag::List() << tag1 << tag2); // THEN QVERIFY(cache->isTagListPopulated()); QCOMPARE(cache->tags(), Akonadi::Tag::List() << tag1 << tag2); QCOMPARE(cache->tag(tag1.id()), tag1); QCOMPARE(cache->tag(tag1.id()).name(), tag1.name()); QCOMPARE(cache->tag(tag2.id()), tag2); QCOMPARE(cache->tag(tag2.id()).name(), tag2.name()); QVERIFY(cache->isTagKnown(tag1.id())); QVERIFY(!cache->isTagPopulated(tag1.id())); QVERIFY(cache->isTagKnown(tag2.id())); QVERIFY(!cache->isTagPopulated(tag2.id())); // WHEN cache->setTags(Akonadi::Tag::List() << GenTag(tag1).withName("tag1bis") << tag2); // THEN QCOMPARE(cache->tags(), Akonadi::Tag::List() << tag1 << tag2); QCOMPARE(cache->tag(tag1.id()), tag1); QCOMPARE(cache->tag(tag1.id()).name(), QStringLiteral("tag1bis")); QCOMPARE(cache->tag(tag2.id()), tag2); QCOMPARE(cache->tag(tag2.id()).name(), tag2.name()); QVERIFY(cache->isTagKnown(tag1.id())); QVERIFY(!cache->isTagPopulated(tag1.id())); QVERIFY(cache->isTagKnown(tag2.id())); QVERIFY(!cache->isTagPopulated(tag2.id())); // WHEN monitor->changeTag(GenTag(tag1).withName("tag1ter")); // THEN QCOMPARE(cache->tags(), Akonadi::Tag::List() << tag1 << tag2); QCOMPARE(cache->tag(tag1.id()), tag1); QCOMPARE(cache->tag(tag1.id()).name(), QStringLiteral("tag1ter")); QCOMPARE(cache->tag(tag2.id()), tag2); QCOMPARE(cache->tag(tag2.id()).name(), tag2.name()); QVERIFY(cache->isTagKnown(tag1.id())); QVERIFY(!cache->isTagPopulated(tag1.id())); QVERIFY(cache->isTagKnown(tag2.id())); QVERIFY(!cache->isTagPopulated(tag2.id())); } void shouldHandleTagAdds() { // GIVEN const auto tag = Akonadi::Tag(GenTag().withId(1).asPlain().withName("tag")); auto monitor = AkonadiFakeMonitor::Ptr::create(); auto cache = Akonadi::Cache::Ptr::create(Akonadi::Serializer::Ptr(new Akonadi::Serializer), monitor); // WHEN monitor->addTag(tag); // THEN QVERIFY(cache->tags().isEmpty()); // WHEN cache->setTags(Akonadi::Tag::List()); monitor->addTag(tag); // THEN QVERIFY(cache->isTagListPopulated()); QCOMPARE(cache->tags(), Akonadi::Tag::List() << tag); QCOMPARE(cache->tag(tag.id()), tag); QCOMPARE(cache->tag(tag.id()).name(), QStringLiteral("tag")); } void shouldHandleTagChanges() { // GIVEN const auto tag = Akonadi::Tag(GenTag().withId(1).asPlain().withName("tag")); auto monitor = AkonadiFakeMonitor::Ptr::create(); auto cache = Akonadi::Cache::Ptr::create(Akonadi::Serializer::Ptr(new Akonadi::Serializer), monitor); cache->setTags(Akonadi::Tag::List() << tag); // WHEN const auto tagbis = Akonadi::Tag(GenTag().withId(1).asPlain().withName("tagbis")); monitor->changeTag(tagbis); // THEN QCOMPARE(cache->tag(tag.id()).name(), QStringLiteral("tagbis")); } void shouldPopulateCollectionsWithItems() { // GIVEN const auto taskCollection1 = Akonadi::Collection(GenCollection().withRootAsParent() .withId(1) .withName("tasks1") .withTaskContent()); const auto items1 = Akonadi::Item::List() << Akonadi::Item(GenTodo().withId(1).withTitle("item1")) << Akonadi::Item(GenTodo().withId(2).withTitle("item2")); const auto taskCollection2 = Akonadi::Collection(GenCollection().withRootAsParent() .withId(2) .withName("tasks2") .withTaskContent()); const auto items2 = Akonadi::Item::List() << Akonadi::Item(GenTodo().withId(3).withTitle("item3")) << Akonadi::Item(GenTodo().withId(4).withTitle("item4")); auto monitor = AkonadiFakeMonitor::Ptr::create(); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, monitor); cache->setCollections(Akonadi::Collection::List() << taskCollection1 << taskCollection2); // WHEN cache->populateCollection(taskCollection1, items1); // THEN QVERIFY(cache->isCollectionPopulated(taskCollection1.id())); QCOMPARE(cache->items(taskCollection1), items1); QCOMPARE(cache->item(items1.at(0).id()), items1.at(0)); QCOMPARE(cache->item(items1.at(1).id()), items1.at(1)); // WHEN cache->populateCollection(taskCollection2, items2); // THEN QVERIFY(cache->isCollectionPopulated(taskCollection2.id())); QCOMPARE(cache->items(taskCollection2), items2); QCOMPARE(cache->item(items2.at(0).id()), items2.at(0)); QCOMPARE(cache->item(items2.at(1).id()), items2.at(1)); } void shouldHandleCollectionRemoves() { // GIVEN const auto tag = Akonadi::Tag(GenTag().withId(1).withName("tag")); const auto collection1 = Akonadi::Collection(GenCollection().withRootAsParent() .withId(1) .withName("tasks1") .withTaskContent()); const auto items1 = Akonadi::Item::List() << Akonadi::Item(GenTodo().withId(1).withTitle("item1")) << Akonadi::Item(GenTodo().withId(2).withTitle("item2")); const auto collection2 = Akonadi::Collection(GenCollection().withRootAsParent() .withId(2) .withName("tasks2") .withTaskContent()); const auto items2 = Akonadi::Item::List() << Akonadi::Item(GenTodo().withId(3).withTitle("item3")) << Akonadi::Item(GenTodo().withId(4).withTitle("item4")); auto monitor = AkonadiFakeMonitor::Ptr::create(); auto cache = Akonadi::Cache::Ptr::create(Akonadi::Serializer::Ptr(new Akonadi::Serializer), monitor); cache->setCollections(Akonadi::Collection::List() << collection1 << collection2); cache->populateCollection(collection1, items1); cache->populateCollection(collection2, items2); cache->populateTag(tag, items1 + items2); // WHEN monitor->removeCollection(collection1); // THEN QVERIFY(!cache->isCollectionPopulated(collection1.id())); QVERIFY(cache->items(collection1).isEmpty()); QCOMPARE(cache->item(items1.at(0).id()), Akonadi::Item()); QCOMPARE(cache->item(items1.at(1).id()), Akonadi::Item()); QVERIFY(cache->isCollectionPopulated(collection2.id())); QCOMPARE(cache->items(collection2), items2); QCOMPARE(cache->item(items2.at(0).id()), items2.at(0)); QCOMPARE(cache->item(items2.at(1).id()), items2.at(1)); QVERIFY(cache->isTagPopulated(tag.id())); QCOMPARE(cache->items(tag), items2); } void shouldPopulateTagsWithItems() { // GIVEN const auto tag1 = Akonadi::Tag(GenTag().withId(1).withName("tag1")); const auto items1 = Akonadi::Item::List() << Akonadi::Item(GenTodo().withId(1).withTitle("item1")) << Akonadi::Item(GenTodo().withId(2).withTitle("item2")); const auto tag2 = Akonadi::Tag(GenTag().withId(2).withName("tag2")); const auto items2 = Akonadi::Item::List() << Akonadi::Item(GenTodo().withId(3).withTitle("item3")) << Akonadi::Item(GenTodo().withId(4).withTitle("item4")); auto monitor = AkonadiFakeMonitor::Ptr::create(); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, monitor); cache->setTags(Akonadi::Tag::List() << tag1 << tag2); // WHEN cache->populateTag(tag1, items1); // THEN QVERIFY(cache->isTagPopulated(tag1.id())); QCOMPARE(cache->items(tag1), items1); QCOMPARE(cache->item(items1.at(0).id()), items1.at(0)); QCOMPARE(cache->item(items1.at(1).id()), items1.at(1)); // WHEN cache->populateTag(tag2, items2); // THEN QVERIFY(cache->isTagPopulated(tag2.id())); QCOMPARE(cache->items(tag2), items2); QCOMPARE(cache->item(items2.at(0).id()), items2.at(0)); QCOMPARE(cache->item(items2.at(1).id()), items2.at(1)); } void shouldHandleTagRemoves() { // GIVEN const auto collection = Akonadi::Collection(GenCollection().withRootAsParent() .withId(1) .withName("collection") .withTaskContent()); const auto tag1 = Akonadi::Tag(GenTag().withId(1).withName("tag1")); const auto items1 = Akonadi::Item::List() << Akonadi::Item(GenTodo().withId(1).withTitle("item1")) << Akonadi::Item(GenTodo().withId(2).withTitle("item2")); const auto tag2 = Akonadi::Tag(GenTag().withId(2).withName("tag2")); const auto items2 = Akonadi::Item::List() << Akonadi::Item(GenTodo().withId(3).withTitle("item3")) << Akonadi::Item(GenTodo().withId(4).withTitle("item4")); auto monitor = AkonadiFakeMonitor::Ptr::create(); auto cache = Akonadi::Cache::Ptr::create(Akonadi::Serializer::Ptr(new Akonadi::Serializer), monitor); cache->setTags(Akonadi::Tag::List() << tag1 << tag2); cache->populateCollection(collection, items1 + items2); cache->populateTag(tag1, items1); cache->populateTag(tag2, items2); // WHEN monitor->removeTag(tag1); // THEN QVERIFY(!cache->isTagPopulated(tag1.id())); QVERIFY(cache->items(tag1).isEmpty()); QCOMPARE(cache->item(items1.at(0).id()), items1.at(0)); QCOMPARE(cache->item(items1.at(1).id()), items1.at(1)); QVERIFY(cache->isTagPopulated(tag2.id())); QCOMPARE(cache->items(tag2), items2); QCOMPARE(cache->item(items2.at(0).id()), items2.at(0)); QCOMPARE(cache->item(items2.at(1).id()), items2.at(1)); QVERIFY(cache->isCollectionPopulated(collection.id())); QCOMPARE(cache->items(collection), items1 + items2); } void shouldHandleItemChanges() { // GIVEN const auto collection1 = Akonadi::Collection(GenCollection().withRootAsParent() .withId(1) .withName("tasks1") .withTaskContent()); const auto collection2 = Akonadi::Collection(GenCollection().withRootAsParent() .withId(2) .withName("tasks2") .withTaskContent()); const auto collection3 = Akonadi::Collection(GenCollection().withRootAsParent() .withId(3) .withName("tasks3") .withTaskContent()); const auto tag1 = Akonadi::Tag(GenTag().withId(1).withName("tag1")); const auto tag2 = Akonadi::Tag(GenTag().withId(2).withName("tag2")); const auto tag3 = Akonadi::Tag(GenTag().withId(3).withName("tag3")); - const auto items = Akonadi::Item::List() << Akonadi::Item(GenTodo().withId(1).withParent(1).withTags({1}).withTitle("item1")) - << Akonadi::Item(GenTodo().withId(2).withParent(1).withTags({1}).withTitle("item2")); - const auto item3 = Akonadi::Item(GenTodo().withId(3).withParent(1).withTags({1}).withTitle("item3")); + const auto items = Akonadi::Item::List() << Akonadi::Item(GenTodo().withId(1).withParent(1).withContexts({"ctx-1"}).withTitle("item1")) + << Akonadi::Item(GenTodo().withId(2).withParent(1).withContexts({"ctx-1"}).withTitle("item2")); + const auto item3 = Akonadi::Item(GenTodo().withId(3).withParent(1).withContexts({"ctx-1"}).withTitle("item3")); auto monitor = AkonadiFakeMonitor::Ptr::create(); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, monitor); cache->setCollections(Akonadi::Collection::List() << collection1 << collection2 << collection3); cache->populateCollection(collection1, items); cache->populateCollection(collection2, Akonadi::Item::List()); cache->populateTag(tag1, items); cache->populateTag(tag2, Akonadi::Item::List()); // WHEN monitor->changeItem(GenTodo(items.at(0)).withTitle("item1bis")); // THEN auto todo = serializer->createTaskFromItem(cache->item(items.at(0).id())); QCOMPARE(todo->title(), QStringLiteral("item1bis")); QVERIFY(cache->isCollectionPopulated(collection1.id())); QVERIFY(cache->items(collection1).contains(items.at(0))); QVERIFY(cache->isCollectionPopulated(collection2.id())); QVERIFY(!cache->items(collection2).contains(items.at(0))); QVERIFY(!cache->isCollectionPopulated(collection3.id())); QVERIFY(!cache->items(collection3).contains(items.at(0))); QVERIFY(cache->isTagPopulated(tag1.id())); QVERIFY(cache->items(tag1).contains(items.at(0))); QVERIFY(cache->isTagPopulated(tag2.id())); QVERIFY(!cache->items(tag2).contains(items.at(0))); QVERIFY(!cache->isTagPopulated(tag3.id())); QVERIFY(!cache->items(tag3).contains(items.at(0))); // WHEN monitor->changeItem(GenTodo(items.at(0)).withParent(2)); // THEN QVERIFY(cache->isCollectionPopulated(collection1.id())); QVERIFY(!cache->items(collection1).contains(items.at(0))); QVERIFY(cache->isCollectionPopulated(collection2.id())); QVERIFY(cache->items(collection2).contains(items.at(0))); QVERIFY(!cache->isCollectionPopulated(collection3.id())); QVERIFY(!cache->items(collection3).contains(items.at(0))); QVERIFY(cache->isTagPopulated(tag1.id())); QVERIFY(cache->items(tag1).contains(items.at(0))); QVERIFY(cache->isTagPopulated(tag2.id())); QVERIFY(!cache->items(tag2).contains(items.at(0))); QVERIFY(!cache->isTagPopulated(tag3.id())); QVERIFY(!cache->items(tag3).contains(items.at(0))); // WHEN monitor->changeItem(GenTodo(items.at(0)).withParent(3)); // THEN QVERIFY(cache->isCollectionPopulated(collection1.id())); QVERIFY(!cache->items(collection1).contains(items.at(0))); QVERIFY(cache->isCollectionPopulated(collection2.id())); QVERIFY(!cache->items(collection2).contains(items.at(0))); QVERIFY(!cache->isCollectionPopulated(collection3.id())); QVERIFY(!cache->items(collection3).contains(items.at(0))); QVERIFY(cache->isTagPopulated(tag1.id())); QVERIFY(cache->items(tag1).contains(items.at(0))); QVERIFY(cache->isTagPopulated(tag2.id())); QVERIFY(!cache->items(tag2).contains(items.at(0))); QVERIFY(!cache->isTagPopulated(tag3.id())); QVERIFY(!cache->items(tag3).contains(items.at(0))); // WHEN - monitor->changeItem(GenTodo().withId(1).withParent(2).withTags({2}).withTitle("item1")); + monitor->changeItem(GenTodo().withId(1).withParent(2).withContexts({"ctx-2"}).withTitle("item1")); // THEN QVERIFY(cache->isCollectionPopulated(collection1.id())); QVERIFY(!cache->items(collection1).contains(items.at(0))); QVERIFY(cache->isCollectionPopulated(collection2.id())); QVERIFY(cache->items(collection2).contains(items.at(0))); QVERIFY(!cache->isCollectionPopulated(collection3.id())); QVERIFY(!cache->items(collection3).contains(items.at(0))); QVERIFY(cache->isTagPopulated(tag1.id())); QVERIFY(!cache->items(tag1).contains(items.at(0))); QVERIFY(cache->isTagPopulated(tag2.id())); QVERIFY(cache->items(tag2).contains(items.at(0))); QVERIFY(!cache->isTagPopulated(tag3.id())); QVERIFY(!cache->items(tag3).contains(items.at(0))); // WHEN monitor->changeItem(item3); // THEN QVERIFY(cache->items(collection1).contains(item3)); QVERIFY(cache->items(tag1).contains(item3)); } void shouldHandleItemAdds() { // GIVEN const auto collection1 = Akonadi::Collection(GenCollection().withRootAsParent() .withId(1) .withName("tasks1") .withTaskContent()); const auto collection2 = Akonadi::Collection(GenCollection().withRootAsParent() .withId(2) .withName("tasks2") .withTaskContent()); const auto tag1 = Akonadi::Tag(GenTag().withId(1).withName("tag1")); const auto tag2 = Akonadi::Tag(GenTag().withId(2).withName("tag2")); - const auto item1 = Akonadi::Item(GenTodo().withId(1).withParent(1).withTags({1}).withTitle("item1")); - const auto item2 = Akonadi::Item(GenTodo().withId(2).withParent(2).withTags({2}).withTitle("item2")); + const auto item1 = Akonadi::Item(GenTodo().withId(1).withParent(1).withContexts({"ctx-1"}).withTitle("item1")); + const auto item2 = Akonadi::Item(GenTodo().withId(2).withParent(2).withContexts({"ctx-2"}).withTitle("item2")); auto monitor = AkonadiFakeMonitor::Ptr::create(); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, monitor); cache->setCollections(Akonadi::Collection::List() << collection1 << collection2); cache->populateCollection(collection1, Akonadi::Item::List()); cache->populateTag(tag1, Akonadi::Item::List()); // WHEN monitor->addItem(item1); // THEN QVERIFY(cache->isCollectionPopulated(collection1.id())); QVERIFY(!cache->items(collection1).isEmpty()); QCOMPARE(cache->items(collection1), Akonadi::Item::List() << item1); QVERIFY(!cache->isCollectionPopulated(collection2.id())); QVERIFY(cache->items(collection2).isEmpty()); QVERIFY(cache->isTagPopulated(tag1.id())); QVERIFY(!cache->items(tag1).isEmpty()); QCOMPARE(cache->items(tag1), Akonadi::Item::List() << item1); QVERIFY(!cache->isTagPopulated(tag2.id())); QVERIFY(cache->items(tag2).isEmpty()); // WHEN monitor->addItem(item2); // THEN QVERIFY(cache->isCollectionPopulated(collection1.id())); QVERIFY(!cache->items(collection1).isEmpty()); QCOMPARE(cache->items(collection1), Akonadi::Item::List() << item1); QVERIFY(!cache->isCollectionPopulated(collection2.id())); QVERIFY(cache->items(collection2).isEmpty()); QVERIFY(cache->isTagPopulated(tag1.id())); QVERIFY(!cache->items(tag1).isEmpty()); QCOMPARE(cache->items(tag1), Akonadi::Item::List() << item1); QVERIFY(!cache->isTagPopulated(tag2.id())); QVERIFY(cache->items(tag2).isEmpty()); } void shouldHandleItemRemoves() { // GIVEN const auto collection = Akonadi::Collection(GenCollection().withRootAsParent() .withId(1) .withName("tasks") .withTaskContent()); const auto tag = Akonadi::Tag(GenTag().withId(1).withName("tag")); const auto items = Akonadi::Item::List() << Akonadi::Item(GenTodo().withId(1).withTitle("item1")) << Akonadi::Item(GenTodo().withId(2).withTitle("item2")); auto monitor = AkonadiFakeMonitor::Ptr::create(); auto cache = Akonadi::Cache::Ptr::create(Akonadi::Serializer::Ptr(new Akonadi::Serializer), monitor); cache->setCollections(Akonadi::Collection::List() << collection); cache->setTags(Akonadi::Tag::List() << tag); cache->populateCollection(collection, items); cache->populateTag(tag, items); // WHEN monitor->removeItem(items.at(0)); // THEN QVERIFY(cache->isCollectionPopulated(collection.id())); QCOMPARE(cache->items(collection), Akonadi::Item::List() << items.at(1)); QVERIFY(cache->isTagPopulated(tag.id())); QCOMPARE(cache->items(tag), Akonadi::Item::List() << items.at(1)); QCOMPARE(cache->item(items.at(0).id()), Akonadi::Item()); QCOMPARE(cache->item(items.at(1).id()), items.at(1)); } }; ZANSHIN_TEST_MAIN(AkonadiCacheTest) #include "akonadicachetest.moc" diff --git a/tests/units/akonadi/akonadicachingstoragetest.cpp b/tests/units/akonadi/akonadicachingstoragetest.cpp index d37fd254..64407aa8 100644 --- a/tests/units/akonadi/akonadicachingstoragetest.cpp +++ b/tests/units/akonadi/akonadicachingstoragetest.cpp @@ -1,430 +1,429 @@ /* This file is part of Zanshin Copyright 2017 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/akonadicachingstorage.h" #include "akonadi/akonadiserializer.h" #include "akonadi/akonadicollectionfetchjobinterface.h" #include "akonadi/akonadiitemfetchjobinterface.h" #include "akonadi/akonaditagfetchjobinterface.h" #include "testlib/akonadifakedata.h" #include "testlib/gencollection.h" #include "testlib/gentodo.h" -#include "testlib/gentag.h" #include "testlib/testhelpers.h" Q_DECLARE_METATYPE(Akonadi::StorageInterface::FetchDepth) using namespace Testlib; class AkonadiCachingStorageTest : public QObject { Q_OBJECT public: explicit AkonadiCachingStorageTest(QObject *parent = nullptr) : QObject(parent) { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); } QStringList onlyWithSuffix(const QStringList &list, const QString &suffix) { return onlyWithSuffixes(list, {suffix}); } QStringList onlyWithSuffixes(const QStringList &list, const QStringList &suffixes) { auto res = QStringList(); std::copy_if(list.cbegin(), list.cend(), std::back_inserter(res), [suffixes](const QString &entry) { for (const auto &suffix : suffixes) { if (entry.endsWith(suffix)) return true; } return false; }); return res; } private slots: void shouldCacheAllCollectionsPerFetchType_data() { QTest::addColumn("rootCollection"); QTest::addColumn("fetchDepth"); QTest::addColumn("expectedFetchNames"); QTest::addColumn("expectedCachedNames"); const auto allCollections = QStringList() << "42Task" << "43Task" << "44Note" << "45Stuff" << "46Note" << "47Task" << "48Note" << "49Stuff" << "50Stuff" << "51Task" << "52Note" << "53Stuff" << "54Task" << "55Task" << "56Task" << "57Note" << "58Note" << "59Note" << "60Stuff" << "61Stuff" << "62Stuff"; const auto taskCollections = QStringList() << "42Task" << "43Task" << "46Note" << "47Task" << "50Stuff" << "51Task" << "54Task" << "55Task" << "56Task"; QTest::newRow("rootRecursiveTask") << Akonadi::Collection::root() << Akonadi::StorageInterface::Recursive << onlyWithSuffix(taskCollections, "Task") << taskCollections; QTest::newRow("54RecursiveTask") << Akonadi::Collection(54) << Akonadi::StorageInterface::Recursive << (QStringList() << "55Task" << "56Task") << taskCollections; QTest::newRow("54FirstLevelTask") << Akonadi::Collection(54) << Akonadi::StorageInterface::FirstLevel << (QStringList() << "55Task") << taskCollections; QTest::newRow("54BaseTask") << Akonadi::Collection(54) << Akonadi::StorageInterface::Base << (QStringList() << "54Task") << taskCollections; } void shouldCacheAllCollectionsPerFetchType() { // GIVEN AkonadiFakeData data; data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Task")).withParent(42).withTaskContent()); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44Note")).withParent(42).withNoteContent()); data.createCollection(GenCollection().withId(45).withName(QStringLiteral("45Stuff")).withParent(42)); data.createCollection(GenCollection().withId(46).withName(QStringLiteral("46Note")).withRootAsParent().withNoteContent()); data.createCollection(GenCollection().withId(47).withName(QStringLiteral("47Task")).withParent(46).withTaskContent()); data.createCollection(GenCollection().withId(48).withName(QStringLiteral("48Note")).withParent(46).withNoteContent()); data.createCollection(GenCollection().withId(49).withName(QStringLiteral("49Stuff")).withParent(46)); data.createCollection(GenCollection().withId(50).withName(QStringLiteral("50Stuff")).withRootAsParent()); data.createCollection(GenCollection().withId(51).withName(QStringLiteral("51Task")).withParent(50).withTaskContent()); data.createCollection(GenCollection().withId(52).withName(QStringLiteral("52Note")).withParent(50).withNoteContent()); data.createCollection(GenCollection().withId(53).withName(QStringLiteral("53Stuff")).withParent(50)); data.createCollection(GenCollection().withId(54).withName(QStringLiteral("54Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(55).withName(QStringLiteral("55Task")).withParent(54).withTaskContent()); data.createCollection(GenCollection().withId(56).withName(QStringLiteral("56Task")).withParent(55).withTaskContent()); data.createCollection(GenCollection().withId(57).withName(QStringLiteral("57Note")).withRootAsParent().withNoteContent()); data.createCollection(GenCollection().withId(58).withName(QStringLiteral("58Note")).withParent(57).withNoteContent()); data.createCollection(GenCollection().withId(59).withName(QStringLiteral("59Note")).withParent(58).withNoteContent()); data.createCollection(GenCollection().withId(60).withName(QStringLiteral("60Stuff")).withRootAsParent()); data.createCollection(GenCollection().withId(61).withName(QStringLiteral("61Stuff")).withParent(60)); data.createCollection(GenCollection().withId(62).withName(QStringLiteral("62Stuff")).withParent(61)); auto cache = Akonadi::Cache::Ptr::create(Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor())); Akonadi::CachingStorage storage(cache, Akonadi::StorageInterface::Ptr(data.createStorage())); // WHEN QFETCH(Akonadi::Collection, rootCollection); QFETCH(Akonadi::StorageInterface::FetchDepth, fetchDepth); auto job = storage.fetchCollections(rootCollection, fetchDepth); QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString())); // THEN const auto toCollectionNames = [](const Akonadi::Collection::List &collections) { auto res = QStringList(); res.reserve(collections.size()); std::transform(collections.cbegin(), collections.cend(), std::back_inserter(res), std::mem_fn(&Akonadi::Collection::name)); res.sort(); return res; }; QFETCH(QStringList, expectedFetchNames); QFETCH(QStringList, expectedCachedNames); { const auto collectionFetchNames = toCollectionNames(job->collections()); QCOMPARE(collectionFetchNames, expectedFetchNames); const auto collections = cache->allCollections(); const auto collectionCachedNames = toCollectionNames(collections); QCOMPARE(collectionCachedNames, expectedCachedNames); } // WHEN (second time shouldn't hit the original storage) data.storageBehavior().setFetchCollectionsBehavior(rootCollection.id(), AkonadiFakeStorageBehavior::EmptyFetch); data.storageBehavior().setFetchCollectionsErrorCode(rootCollection.id(), 128); job = storage.fetchCollections(rootCollection, fetchDepth); QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString())); { const auto collectionFetchNames = toCollectionNames(job->collections()); QCOMPARE(collectionFetchNames, expectedFetchNames); const auto collections = cache->allCollections(); const auto collectionCachedNames = toCollectionNames(collections); QCOMPARE(collectionCachedNames, expectedCachedNames); } } void shouldCacheAllItemsPerCollection() { // GIVEN AkonadiFakeData data; data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Col")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Col")).withRootAsParent().withTaskContent()); data.createItem(GenTodo().withId(42).withTitle(QStringLiteral("42Task")).withParent(42)); data.createItem(GenTodo().withId(45).withTitle(QStringLiteral("45Task")).withParent(42)); data.createItem(GenTodo().withId(52).withTitle(QStringLiteral("52Task")).withParent(42)); data.createItem(GenTodo().withId(44).withTitle(QStringLiteral("44Task")).withParent(43)); data.createItem(GenTodo().withId(48).withTitle(QStringLiteral("48Task")).withParent(43)); data.createItem(GenTodo().withId(50).withTitle(QStringLiteral("50Task")).withParent(43)); auto cache = Akonadi::Cache::Ptr::create(Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor())); Akonadi::CachingStorage storage(cache, Akonadi::StorageInterface::Ptr(data.createStorage())); // WHEN auto job = storage.fetchItems(Akonadi::Collection(42)); QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString())); // THEN const auto toItemIds = [](const Akonadi::Item::List &items) { auto res = QVector(); res.reserve(items.size()); std::transform(items.cbegin(), items.cend(), std::back_inserter(res), std::mem_fn(&Akonadi::Item::id)); std::sort(res.begin(), res.end()); return res; }; auto expectedIds = QVector() << 42 << 45 << 52; { const auto itemFetchIds = toItemIds(job->items()); QCOMPARE(itemFetchIds, expectedIds); const auto items = cache->items(Akonadi::Collection(42)); const auto itemCachedIds = toItemIds(items); QCOMPARE(itemCachedIds, expectedIds); } // WHEN (second time shouldn't hit the original storage) data.storageBehavior().setFetchItemsBehavior(42, AkonadiFakeStorageBehavior::EmptyFetch); data.storageBehavior().setFetchItemsErrorCode(42, 128); job = storage.fetchItems(Akonadi::Collection(42)); QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString())); { const auto itemFetchIds = toItemIds(job->items()); QCOMPARE(itemFetchIds, expectedIds); const auto items = cache->items(Akonadi::Collection(42)); const auto itemCachedIds = toItemIds(items); QCOMPARE(itemCachedIds, expectedIds); } } void shouldCacheSingleItems() { // GIVEN AkonadiFakeData data; data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Col")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Col")).withRootAsParent().withTaskContent()); data.createItem(GenTodo().withId(42).withTitle(QStringLiteral("42Task")).withParent(42)); data.createItem(GenTodo().withId(45).withTitle(QStringLiteral("45Task")).withParent(42)); data.createItem(GenTodo().withId(52).withTitle(QStringLiteral("52Task")).withParent(42)); data.createItem(GenTodo().withId(44).withTitle(QStringLiteral("44Task")).withParent(43)); data.createItem(GenTodo().withId(48).withTitle(QStringLiteral("48Task")).withParent(43)); data.createItem(GenTodo().withId(50).withTitle(QStringLiteral("50Task")).withParent(43)); auto cache = Akonadi::Cache::Ptr::create(Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor())); Akonadi::CachingStorage storage(cache, Akonadi::StorageInterface::Ptr(data.createStorage())); // WHEN auto job = storage.fetchItem(Akonadi::Item(44)); QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString())); // THEN const auto toItemIds = [](const Akonadi::Item::List &items) { auto res = QVector(); res.reserve(items.size()); std::transform(items.cbegin(), items.cend(), std::back_inserter(res), std::mem_fn(&Akonadi::Item::id)); std::sort(res.begin(), res.end()); return res; }; auto expectedIds = QVector() << 44; { const auto itemFetchIds = toItemIds(job->items()); QCOMPARE(itemFetchIds, expectedIds); QVERIFY(!cache->item(44).isValid()); } // WHEN (if collection is populated, shouldn't hit the original storage) job = storage.fetchItems(Akonadi::Collection(43)); QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString())); data.storageBehavior().setFetchItemBehavior(44, AkonadiFakeStorageBehavior::EmptyFetch); data.storageBehavior().setFetchItemErrorCode(44, 128); job = storage.fetchItem(Akonadi::Item(44)); QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString())); { const auto itemFetchIds = toItemIds(job->items()); QCOMPARE(itemFetchIds, expectedIds); QVERIFY(cache->item(44).isValid()); } } void shouldCacheAllItemsPerTag() { // GIVEN AkonadiFakeData data; data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Col")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Col")).withRootAsParent().withTaskContent()); - data.createTag(GenTag().withId(42).withName(QStringLiteral("42Plain")).asPlain()); - data.createTag(GenTag().withId(43).withName(QStringLiteral("43Context")).asContext()); + data.createItem(GenTodo().withUid("ctx-42").withTitle(QStringLiteral("42Context")).asContext()); + data.createItem(GenTodo().withUid("ctx-43").withTitle(QStringLiteral("43Context")).asContext()); - data.createItem(GenTodo().withId(42).withTitle(QStringLiteral("42Task")).withParent(42).withTags({42})); - data.createItem(GenTodo().withId(45).withTitle(QStringLiteral("45Task")).withParent(42).withTags({42, 43})); - data.createItem(GenTodo().withId(52).withTitle(QStringLiteral("52Task")).withParent(42).withTags({43})); + data.createItem(GenTodo().withId(42).withTitle(QStringLiteral("42Task")).withParent(42).withContexts({"ctx-42"})); + data.createItem(GenTodo().withId(45).withTitle(QStringLiteral("45Task")).withParent(42).withContexts({"ctx-42", "ctx-43"})); + data.createItem(GenTodo().withId(52).withTitle(QStringLiteral("52Task")).withParent(42).withContexts({"ctx-43"})); - data.createItem(GenTodo().withId(44).withTitle(QStringLiteral("44Task")).withParent(43).withTags({42})); - data.createItem(GenTodo().withId(48).withTitle(QStringLiteral("48Task")).withParent(43).withTags({42, 43})); - data.createItem(GenTodo().withId(50).withTitle(QStringLiteral("50Task")).withParent(43).withTags({43})); + data.createItem(GenTodo().withId(44).withTitle(QStringLiteral("44Task")).withParent(43).withContexts({"ctx-42"})); + data.createItem(GenTodo().withId(48).withTitle(QStringLiteral("48Task")).withParent(43).withContexts({"ctx-42", "ctx-43"})); + data.createItem(GenTodo().withId(50).withTitle(QStringLiteral("50Task")).withParent(43).withContexts({"ctx-43"})); auto cache = Akonadi::Cache::Ptr::create(Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor())); Akonadi::CachingStorage storage(cache, Akonadi::StorageInterface::Ptr(data.createStorage())); // WHEN auto job = storage.fetchTagItems(Akonadi::Tag(43)); QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString())); // THEN const auto toItemIds = [](const Akonadi::Item::List &items) { auto res = QVector(); res.reserve(items.size()); std::transform(items.cbegin(), items.cend(), std::back_inserter(res), std::mem_fn(&Akonadi::Item::id)); std::sort(res.begin(), res.end()); return res; }; auto expectedIds = QVector() << 45 << 48 << 50 << 52; { const auto itemFetchIds = toItemIds(job->items()); QCOMPARE(itemFetchIds, expectedIds); const auto items = cache->items(Akonadi::Tag(43)); const auto itemCachedIds = toItemIds(items); QCOMPARE(itemCachedIds, expectedIds); } // WHEN (second time shouldn't hit the original storage) data.storageBehavior().setFetchTagItemsBehavior(43, AkonadiFakeStorageBehavior::EmptyFetch); data.storageBehavior().setFetchTagItemsErrorCode(43, 128); job = storage.fetchTagItems(Akonadi::Tag(43)); QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString())); { const auto itemFetchIds = toItemIds(job->items()); QCOMPARE(itemFetchIds, expectedIds); const auto items = cache->items(Akonadi::Tag(43)); const auto itemCachedIds = toItemIds(items); QCOMPARE(itemCachedIds, expectedIds); } } void shouldCacheTags() { // GIVEN AkonadiFakeData data; - data.createTag(GenTag().withId(42).withName(QStringLiteral("42Plain")).asPlain()); - data.createTag(GenTag().withId(43).withName(QStringLiteral("43Context")).asContext()); - data.createTag(GenTag().withId(44).withName(QStringLiteral("44Plain")).asPlain()); + data.createItem(GenTodo().withUid("ctx-42").withTitle(QStringLiteral("42Context")).asContext()); + data.createItem(GenTodo().withUid("ctx-43").withTitle(QStringLiteral("43Context")).asContext()); + data.createItem(GenTodo().withUid("ctx-44").withTitle(QStringLiteral("44Context")).asContext()); auto cache = Akonadi::Cache::Ptr::create(Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor())); Akonadi::CachingStorage storage(cache, Akonadi::StorageInterface::Ptr(data.createStorage())); // WHEN auto job = storage.fetchTags(); QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString())); // THEN const auto toTagNames = [](const Akonadi::Tag::List &tags) { auto res = QStringList(); res.reserve(tags.size()); std::transform(tags.cbegin(), tags.cend(), std::back_inserter(res), std::mem_fn(&Akonadi::Tag::name)); res.sort(); return res; }; - auto expectedNames = QStringList() << "42Plain" << "43Context" << "44Plain"; + auto expectedNames = QStringList() << "42Context" << "43Context" << "44Context"; { const auto tagFetchNames = toTagNames(job->tags()); QCOMPARE(tagFetchNames, expectedNames); const auto tags = cache->tags(); const auto tagCachedNames = toTagNames(tags); QCOMPARE(tagCachedNames, expectedNames); } // WHEN (second time shouldn't hit the original storage) data.storageBehavior().setFetchTagsBehavior(AkonadiFakeStorageBehavior::EmptyFetch); data.storageBehavior().setFetchTagsErrorCode(128); job = storage.fetchTags(); QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString())); { const auto tagFetchNames = toTagNames(job->tags()); QCOMPARE(tagFetchNames, expectedNames); const auto tags = cache->tags(); const auto tagCachedNames = toTagNames(tags); QCOMPARE(tagCachedNames, expectedNames); } } }; ZANSHIN_TEST_MAIN(AkonadiCachingStorageTest) #include "akonadicachingstoragetest.moc" diff --git a/tests/units/akonadi/akonadicontextqueriestest.cpp b/tests/units/akonadi/akonadicontextqueriestest.cpp index f8d38d60..0e2ae635 100644 --- a/tests/units/akonadi/akonadicontextqueriestest.cpp +++ b/tests/units/akonadi/akonadicontextqueriestest.cpp @@ -1,513 +1,528 @@ /* This file is part of Zanshin Copyright 2014 Franck Arrecot 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/akonadicachingstorage.h" #include "akonadi/akonadicontextqueries.h" #include "akonadi/akonadiserializer.h" #include "testlib/akonadifakedata.h" #include "testlib/gencollection.h" #include "testlib/gentodo.h" -#include "testlib/gentag.h" #include "testlib/testhelpers.h" using namespace Testlib; class AkonadiContextQueriesTest : public QObject { Q_OBJECT private: Akonadi::StorageInterface::Ptr createCachingStorage(AkonadiFakeData &data, const Akonadi::Cache::Ptr &cache) { auto storage = Akonadi::StorageInterface::Ptr(data.createStorage()); return Akonadi::StorageInterface::Ptr(new Akonadi::CachingStorage(cache, storage)); } private slots: - void shouldDealWithEmptyTagList() + void shouldDealWithEmptyContextList() { // GIVEN AkonadiFakeData data; // WHEN QScopedPointer queries(new Akonadi::ContextQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findAll(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); QCOMPARE(result->data().size(), 0); } void shouldLookInAllReportedForAllContexts() { // GIVEN AkonadiFakeData data; - // Two context tags - data.createTag(GenTag().withId(42).withName(QStringLiteral("42")).asContext()); - data.createTag(GenTag().withId(43).withName(QStringLiteral("43")).asContext()); + // One top level collection + data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); + + // Two contexts + data.createItem(GenTodo().withParent(42).withId(42).withUid("ctx-42").withTitle(QStringLiteral("42")).asContext()); + data.createItem(GenTodo().withParent(42).withId(43).withUid("ctx-43").withTitle(QStringLiteral("43")).asContext()); // WHEN QScopedPointer queries(new Akonadi::ContextQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); 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(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43")); } - void shouldIgnoreWrongTagType() + void shouldIgnoreNonContexts() { // GIVEN AkonadiFakeData data; - // One context tag and one plain tag - data.createTag(GenTag().withId(42).withName(QStringLiteral("42")).asContext()); - data.createTag(GenTag().withId(43).withName(QStringLiteral("43")).asPlain()); + // One top level collection + data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); + + // Two items, one of which not a context + data.createItem(GenTodo().withParent(42).withId(42).withUid("ctx-42").withTitle(QStringLiteral("42")).asContext()); + data.createItem(GenTodo().withParent(42).withId(43).withUid("ctx-43").withTitle(QStringLiteral("43"))); // WHEN QScopedPointer queries(new Akonadi::ContextQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); 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(), 1); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42")); } - void shouldReactToTagAdded() + void shouldReactToContextAdded() { // GIVEN AkonadiFakeData data; + // One top level collection + data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); + QScopedPointer queries(new Akonadi::ContextQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findAll(); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); // WHEN - data.createTag(GenTag().withId(42).withName(QStringLiteral("42")).asContext()); - data.createTag(GenTag().withId(43).withName(QStringLiteral("43")).asContext()); + data.createItem(GenTodo().withParent(42).withId(42).withUid("ctx-42").withTitle(QStringLiteral("42")).asContext()); + data.createItem(GenTodo().withParent(42).withId(43).withUid("ctx-43").withTitle(QStringLiteral("43")).asContext()); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43")); } - void shouldReactToTagRemoved() + void shouldReactToContextRemoved() { // GIVEN AkonadiFakeData data; - // Two context tags - data.createTag(GenTag().withId(42).withName(QStringLiteral("42")).asContext()); - data.createTag(GenTag().withId(43).withName(QStringLiteral("43")).asContext()); + // One top level collection + data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); + + // Two contexts + data.createItem(GenTodo().withParent(42).withId(42).withUid("ctx-42").withTitle(QStringLiteral("42")).asContext()); + data.createItem(GenTodo().withParent(42).withId(43).withUid("ctx-43").withTitle(QStringLiteral("43")).asContext()); QScopedPointer queries(new Akonadi::ContextQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findAll(); QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN - data.removeTag(Akonadi::Tag(43)); + data.removeItem(Akonadi::Item(43)); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42")); } - void shouldReactToTagChanges() + void shouldReactToContextChanges() { // GIVEN AkonadiFakeData data; - // Two context tags - data.createTag(GenTag().withId(42).withName(QStringLiteral("42")).asContext()); - data.createTag(GenTag().withId(43).withName(QStringLiteral("43")).asContext()); + // One top level collection + data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); + + // Two contexts + data.createItem(GenTodo().withParent(42).withId(42).withUid("ctx-42").withTitle(QStringLiteral("42")).asContext()); + data.createItem(GenTodo().withParent(42).withId(43).withUid("ctx-43").withTitle(QStringLiteral("43")).asContext()); QScopedPointer queries(new Akonadi::ContextQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findAll(); QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN - data.modifyTag(GenTag(data.tag(43)).withName(QStringLiteral("43bis"))); + data.modifyItem(GenTodo(data.item(43)).withTitle(QStringLiteral("43bis"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43bis")); } void shouldLookForAllContextTopLevelTasks() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); - // One context tag - data.createTag(GenTag().withId(42).withName(QStringLiteral("42")).asContext()); + // One context + data.createItem(GenTodo().withParent(42).withId(40).withUid("ctx").withTitle(QStringLiteral("Context")).asContext()); // Three tasks in the collection, two related to context, one not - data.createItem(GenTodo().withParent(42).withId(42).withTitle(QStringLiteral("42")).withTags({42})); + data.createItem(GenTodo().withParent(42).withId(42).withTitle(QStringLiteral("42")).withContexts({"ctx"})); data.createItem(GenTodo().withParent(42).withId(43).withTitle(QStringLiteral("43"))); - data.createItem(GenTodo().withParent(42).withId(44).withTitle(QStringLiteral("44")).withTags({42})); + data.createItem(GenTodo().withParent(42).withId(44).withTitle(QStringLiteral("44")).withContexts({"ctx"})); // WHEN auto serializer = Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); QScopedPointer queries(new Akonadi::ContextQueries(createCachingStorage(data, cache), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache)); - auto context = serializer->createContextFromTag(data.tag(42)); + auto context = serializer->createContextFromItem(data.item(40)); auto result = queries->findTopLevelTasks(context); result->data(); result = queries->findTopLevelTasks(context); // 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("44")); // Should not change nothing result = queries->findTopLevelTasks(context); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("44")); } void shouldNotListContextTasksTwiceIfTheyHaveAParentInTheSameContext() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); - // One context tag - data.createTag(GenTag().withId(42).withName(QStringLiteral("42")).asContext()); + // One context item + data.createItem(GenTodo().withParent(42).withId(40).withUid("ctx").withTitle(QStringLiteral("Context")).asContext()); // Five tasks in the collection, two related to context, three not, all forming an ancestry line data.createItem(GenTodo().withParent(42).withId(42).withTitle(QStringLiteral("42")).withUid("42")); - data.createItem(GenTodo().withParent(42).withId(43).withTitle(QStringLiteral("43")).withUid("43").withParentUid("42").withTags({42})); + data.createItem(GenTodo().withParent(42).withId(43).withTitle(QStringLiteral("43")).withUid("43").withParentUid("42").withContexts({"ctx"})); data.createItem(GenTodo().withParent(42).withId(44).withTitle(QStringLiteral("44")).withUid("44").withParentUid("43")); - data.createItem(GenTodo().withParent(42).withId(45).withTitle(QStringLiteral("45")).withUid("45").withParentUid("44").withTags({42})); + data.createItem(GenTodo().withParent(42).withId(45).withTitle(QStringLiteral("45")).withUid("45").withParentUid("44").withContexts({"ctx"})); data.createItem(GenTodo().withParent(42).withId(46).withTitle(QStringLiteral("46")).withUid("46").withParentUid("45")); // WHEN auto serializer = Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); QScopedPointer queries(new Akonadi::ContextQueries(createCachingStorage(data, cache), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache)); - auto context = serializer->createContextFromTag(data.tag(42)); + auto context = serializer->createContextFromItem(data.item(40)); auto result = queries->findTopLevelTasks(context); result->data(); result = queries->findTopLevelTasks(context); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("43")); // Should not change anything result = queries->findTopLevelTasks(context); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("43")); } void shouldReactToItemAddsForTopLevelTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); - // One context tag - data.createTag(GenTag().withId(42).withName(QStringLiteral("42")).asContext()); + // One context item + data.createItem(GenTodo().withParent(42).withId(40).withUid("ctx").withTitle(QStringLiteral("Context")).asContext()); auto serializer = Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); QScopedPointer queries(new Akonadi::ContextQueries(createCachingStorage(data, cache), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache)); - auto context = serializer->createContextFromTag(data.tag(42)); + auto context = serializer->createContextFromItem(data.item(40)); auto result = queries->findTopLevelTasks(context); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); // WHEN - data.createItem(GenTodo().withParent(42).withId(42).withTitle(QStringLiteral("42")).withTags({42})); + data.createItem(GenTodo().withParent(42).withId(42).withTitle(QStringLiteral("42")).withContexts({"ctx"})); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } void shoudlReactToItemChangesForTopLevelTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); - // One context tag - data.createTag(GenTag().withId(42).withName(QStringLiteral("42")).asContext()); + // One context + data.createItem(GenTodo().withParent(42).withId(40).withUid("ctx").withTitle(QStringLiteral("Context")).asContext()); // One task related to the context - data.createItem(GenTodo().withParent(42).withId(42).withTitle(QStringLiteral("42")).withTags({42})); + data.createItem(GenTodo().withParent(42).withId(42).withTitle(QStringLiteral("42")).withContexts({"ctx"})); auto serializer = Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); QScopedPointer queries(new Akonadi::ContextQueries(createCachingStorage(data, cache), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache)); - auto context = serializer->createContextFromTag(data.tag(42)); + auto context = serializer->createContextFromItem(data.item(40)); auto result = queries->findTopLevelTasks(context); 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(42)).withTitle(QStringLiteral("42bis"))); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42bis")); QVERIFY(replaceHandlerCalled); } - void shouldAddItemToCorrespondingResultWhenTagAddedToTopLevelTask() + void shouldAddItemToCorrespondingResultWhenContextAddedToTopLevelTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); - // One context tag - data.createTag(GenTag().withId(42).withName(QStringLiteral("42")).asContext()); + // One context item + data.createItem(GenTodo().withParent(42).withId(40).withUid("ctx").withTitle(QStringLiteral("Context")).asContext()); // One task not related to the context data.createItem(GenTodo().withParent(42).withId(42).withTitle(QStringLiteral("42"))); auto serializer = Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); QScopedPointer queries(new Akonadi::ContextQueries(createCachingStorage(data, cache), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache)); - auto context = serializer->createContextFromTag(data.tag(42)); + auto context = serializer->createContextFromItem(data.item(40)); auto result = queries->findTopLevelTasks(context); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); // WHEN - data.modifyItem(GenTodo(data.item(42)).withTags({42})); + data.modifyItem(GenTodo(data.item(42)).withContexts({"ctx"})); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } - void shouldRemoveItemFromCorrespondingResultWhenTagRemovedFromTopLevelTask() + void shouldRemoveItemFromCorrespondingResultWhenContextRemovedFromTopLevelTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); - // One context tag - data.createTag(GenTag().withId(42).withName(QStringLiteral("42")).asContext()); + // One context item + data.createItem(GenTodo().withParent(42).withId(40).withUid("ctx").withTitle(QStringLiteral("Context")).asContext()); // One task related to the context - data.createItem(GenTodo().withParent(42).withId(42).withTitle(QStringLiteral("42")).withTags({42})); + data.createItem(GenTodo().withParent(42).withId(42).withTitle(QStringLiteral("42")).withContexts({"ctx"})); auto serializer = Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); QScopedPointer queries(new Akonadi::ContextQueries(createCachingStorage(data, cache), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache)); - auto context = serializer->createContextFromTag(data.tag(42)); + auto context = serializer->createContextFromItem(data.item(40)); + QVERIFY(context); auto result = queries->findTopLevelTasks(context); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); // WHEN - data.modifyItem(GenTodo(data.item(42)).withTags({})); + data.modifyItem(GenTodo(data.item(42)).withContexts({})); // THEN QVERIFY(result->data().isEmpty()); } - void shouldMoveItemToCorrespondingResultWhenTagChangedOnTopLevelTask() + void shouldMoveItemToCorrespondingResultWhenContextChangedOnTopLevelTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); - // Two context tags - data.createTag(GenTag().withId(42).withName(QStringLiteral("42")).asContext()); - data.createTag(GenTag().withId(43).withName(QStringLiteral("43")).asContext()); + // Two context items + data.createItem(GenTodo().withParent(42).withId(1).withUid("ctx-1").withTitle(QStringLiteral("Context 1")).asContext()); + data.createItem(GenTodo().withParent(42).withId(2).withUid("ctx-2").withTitle(QStringLiteral("Context 2")).asContext()); // One task related to the first context - data.createItem(GenTodo().withParent(42).withId(42).withTitle(QStringLiteral("42")).withTags({42})); + data.createItem(GenTodo().withParent(42).withId(42).withTitle(QStringLiteral("42")).withContexts({"ctx-1"})); auto serializer = Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); QScopedPointer queries(new Akonadi::ContextQueries(createCachingStorage(data, cache), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache)); - auto context1 = serializer->createContextFromTag(data.tag(42)); + auto context1 = serializer->createContextFromItem(data.item(1)); auto result1 = queries->findTopLevelTasks(context1); - auto context2 = serializer->createContextFromTag(data.tag(43)); + auto context2 = serializer->createContextFromItem(data.item(2)); auto result2 = queries->findTopLevelTasks(context2); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result1->data().size(), 1); QCOMPARE(result1->data().at(0)->title(), QStringLiteral("42")); QVERIFY(result2->data().isEmpty()); // WHEN - data.modifyItem(GenTodo(data.item(42)).withTags({43})); + data.modifyItem(GenTodo(data.item(42)).withContexts({"ctx-2"})); // THEN QVERIFY(result1->data().isEmpty()); QCOMPARE(result2->data().size(), 1); QCOMPARE(result2->data().at(0)->title(), QStringLiteral("42")); } void shoudlReactToItemRemovesForTopLevelTask() { // GIVEN AkonadiFakeData data; - // One context tag - data.createTag(GenTag().withId(42).withName(QStringLiteral("tag-42")).asContext()); - // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); - // A task related to the context - data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).withTags({42})); + // One context item + data.createItem(GenTodo().withParent(42).withId(1).withUid("ctx-1").withTitle(QStringLiteral("Context 1")).asContext()); + // A task related to the context + data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).withContexts({"ctx-1"})); auto serializer = Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); QScopedPointer queries(new Akonadi::ContextQueries(createCachingStorage(data, cache), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache)); - auto context = serializer->createContextFromTag(data.tag(42)); + auto context = serializer->createContextFromItem(data.item(1)); + QVERIFY(context); auto result = queries->findTopLevelTasks(context); bool removeHandlerCalled = false; result->addPostRemoveHandler([&removeHandlerCalled](const Domain::Task::Ptr &, int) { removeHandlerCalled = true; }); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); // WHEN data.removeItem(Akonadi::Item(42)); // THEN QVERIFY(result->data().isEmpty()); QVERIFY(removeHandlerCalled); } }; ZANSHIN_TEST_MAIN(AkonadiContextQueriesTest) #include "akonadicontextqueriestest.moc" diff --git a/tests/units/akonadi/akonadicontextrepositorytest.cpp b/tests/units/akonadi/akonadicontextrepositorytest.cpp index c1cb5e64..f7590a47 100644 --- a/tests/units/akonadi/akonadicontextrepositorytest.cpp +++ b/tests/units/akonadi/akonadicontextrepositorytest.cpp @@ -1,342 +1,339 @@ /* This file is part of Zanshin Copyright 2014 Franck Arrecot 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 "utils/mockobject.h" #include "testlib/akonadifakejobs.h" #include "testlib/akonadifakemonitor.h" #include "akonadi/akonadicontextrepository.h" #include "akonadi/akonadiserializerinterface.h" #include "akonadi/akonadistorageinterface.h" using namespace mockitopp; using namespace mockitopp::matcher; Q_DECLARE_METATYPE(Testlib::AkonadiFakeItemFetchJob*) Q_DECLARE_METATYPE(Testlib::AkonadiFakeTagFetchJob*) class AkonadiContextRepositoryTest : public QObject { Q_OBJECT private slots: void shouldCreateContext() { // GIVEN auto source = Domain::DataSource::Ptr::create(); source->setName(QStringLiteral("Source1")); // A Context and its corresponding item not existing in akonadi Akonadi::Item contextItem; Akonadi::Collection collection(23); auto context = Domain::Context::Ptr::create(); // A mock creating job auto itemCreateJob = new FakeJob(this); // Storage mock returning the context-item creation job Utils::MockObject storageMock; storageMock(&Akonadi::StorageInterface::createItem).when(contextItem, collection) .thenReturn(itemCreateJob); // Serializer mock Utils::MockObject serializerMock; serializerMock(&Akonadi::SerializerInterface::createItemFromContext).when(context).thenReturn(contextItem); serializerMock(&Akonadi::SerializerInterface::createCollectionFromDataSource).when(source).thenReturn(collection); // WHEN QScopedPointer repository(new Akonadi::ContextRepository(storageMock.getInstance(), serializerMock.getInstance())); repository->create(context, source)->exec(); //THEN QVERIFY(storageMock(&Akonadi::StorageInterface::createItem).when(contextItem, collection).exactly(1)); } void shouldUpdateContext() { // GIVEN Akonadi::Item contextItem; contextItem.setId(42); auto context = Domain::Context::Ptr::create(); // A mock job auto itemModifyJob = new FakeJob(this); // Storage mock returning the item modify job Utils::MockObject storageMock; storageMock(&Akonadi::StorageInterface::updateItem).when(contextItem, nullptr) .thenReturn(itemModifyJob); // Serializer mock Utils::MockObject serializerMock; serializerMock(&Akonadi::SerializerInterface::createItemFromContext).when(context).thenReturn(contextItem); // WHEN QScopedPointer repository(new Akonadi::ContextRepository(storageMock.getInstance(), serializerMock.getInstance())); repository->update(context)->exec(); // THEN QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromContext).when(context).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::updateItem).when(contextItem, nullptr).exactly(1)); } void shouldRemoveContext() { // GIVEN Akonadi::Item contextItem; contextItem.setId(42); auto context = Domain::Context::Ptr::create(); // A mock job auto contextItemDeleteJob = new FakeJob(this); // Storage mock returning the tagCreatejob Utils::MockObject storageMock; storageMock(&Akonadi::StorageInterface::removeItem).when(contextItem) .thenReturn(contextItemDeleteJob); // Serializer mock Utils::MockObject serializerMock; serializerMock(&Akonadi::SerializerInterface::createItemFromContext).when(context).thenReturn(contextItem); // WHEN QScopedPointer repository(new Akonadi::ContextRepository(storageMock.getInstance(), serializerMock.getInstance())); repository->remove(context)->exec(); // THEN QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromContext).when(context).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::removeItem).when(contextItem).exactly(1)); } void shouldAssociateATaskToAContext_data() { QTest::addColumn("associatedContextItem"); QTest::addColumn("item"); QTest::addColumn("context"); QTest::addColumn("task"); QTest::addColumn("itemFetchJob"); QTest::addColumn("execJob"); Akonadi::Collection col(40); Akonadi::Item item(42); item.setParentCollection(col); Domain::Task::Ptr task(new Domain::Task); Akonadi::Item associatedContextItem(43); auto associatedContext = Domain::Context::Ptr::create(); auto itemFetchJob = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob->setItems(Akonadi::Item::List() << item); QTest::newRow("nominal case") << associatedContextItem << item << associatedContext << task << itemFetchJob << true; itemFetchJob = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob->setExpectedError(KJob::KilledJobError); QTest::newRow("task job error, cannot find task") << associatedContextItem << item << associatedContext << task << itemFetchJob << false; } void shouldAssociateATaskToAContext() { // GIVEN QFETCH(Akonadi::Item,associatedContextItem); QFETCH(Akonadi::Item,item); QFETCH(Domain::Context::Ptr,context); QFETCH(Domain::Task::Ptr,task); QFETCH(Testlib::AkonadiFakeItemFetchJob*,itemFetchJob); QFETCH(bool,execJob); // A mock update job auto itemModifyJob = new FakeJob(this); // Storage mock returning the create job Utils::MockObject storageMock; storageMock(&Akonadi::StorageInterface::fetchItem).when(item) .thenReturn(itemFetchJob); storageMock(&Akonadi::StorageInterface::updateItem).when(item, nullptr) .thenReturn(itemModifyJob); // Serializer mock returning the item for the task Utils::MockObject serializerMock; serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(task) .thenReturn(item); serializerMock(&Akonadi::SerializerInterface::addContextToTask).when(context, item) .thenReturn(); // WHEN QScopedPointer repository(new Akonadi::ContextRepository(storageMock.getInstance(), serializerMock.getInstance())); auto associateJob = repository->associate(context, task); if (execJob) associateJob->exec(); // THEN QVERIFY(storageMock(&Akonadi::StorageInterface::fetchItem).when(item).exactly(1)); if (execJob) { QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(task).exactly(1)); QVERIFY(serializerMock(&Akonadi::SerializerInterface::addContextToTask).when(context, item).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::updateItem).when(item, nullptr).exactly(1)); } } void shoudDissociateTaskFromContext_data() { - QTest::addColumn("associatedTag"); QTest::addColumn("item"); QTest::addColumn("context"); QTest::addColumn("task"); QTest::addColumn("itemFetchJob"); QTest::addColumn("execJob"); Akonadi::Item item(42); Domain::Task::Ptr task(new Domain::Task); - Akonadi::Tag associatedTag(qint64(43)); auto associatedContext = Domain::Context::Ptr::create(); auto itemFetchJob = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob->setItems(Akonadi::Item::List() << item); - QTest::newRow("nominal case") << associatedTag << item << associatedContext << task << itemFetchJob << true; + QTest::newRow("nominal case") << item << associatedContext << task << itemFetchJob << true; itemFetchJob = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob->setExpectedError(KJob::KilledJobError); - QTest::newRow("task job error, cannot find task") << associatedTag << item << associatedContext << task << itemFetchJob << false; + QTest::newRow("task job error, cannot find task") << item << associatedContext << task << itemFetchJob << false; } void shoudDissociateTaskFromContext() { - QFETCH(Akonadi::Tag,associatedTag); QFETCH(Akonadi::Item,item); QFETCH(Domain::Context::Ptr,context); QFETCH(Domain::Task::Ptr,task); QFETCH(Testlib::AkonadiFakeItemFetchJob*,itemFetchJob); QFETCH(bool,execJob); // A mock update job auto itemModifyJob = new FakeJob(this); // Storage mock returning the create job Utils::MockObject storageMock; storageMock(&Akonadi::StorageInterface::fetchItem).when(item) .thenReturn(itemFetchJob); storageMock(&Akonadi::StorageInterface::updateItem).when(item, nullptr) .thenReturn(itemModifyJob); // Serializer mock returning the item for the task Utils::MockObject serializerMock; serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(task) .thenReturn(item); - serializerMock(&Akonadi::SerializerInterface::createTagFromContext).when(context) - .thenReturn(associatedTag); + serializerMock(&Akonadi::SerializerInterface::removeContextFromTask).when(context, item) + .thenReturn(); // WHEN QScopedPointer repository(new Akonadi::ContextRepository(storageMock.getInstance(), serializerMock.getInstance())); auto dissociateJob = repository->dissociate(context, task); if (execJob) dissociateJob->exec(); // THEN QVERIFY(storageMock(&Akonadi::StorageInterface::fetchItem).when(item).exactly(1)); if (execJob) { - QVERIFY(serializerMock(&Akonadi::SerializerInterface::createTagFromContext).when(context).exactly(1)); + QVERIFY(serializerMock(&Akonadi::SerializerInterface::removeContextFromTask).when(context, item).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::updateItem).when(item, nullptr).exactly(1)); } } void shouldDissociateTaskFromAllContext_data() { QTest::addColumn("item"); QTest::addColumn("task"); QTest::addColumn("itemFetchJob"); QTest::addColumn("execJob"); Akonadi::Item item(42); Domain::Task::Ptr task(new Domain::Task); auto itemFetchJob = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob->setItems(Akonadi::Item::List() << item); QTest::newRow("nominal case") << item << task << itemFetchJob << true; itemFetchJob = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob->setExpectedError(KJob::KilledJobError); QTest::newRow("task job error, cannot find task") << item << task << itemFetchJob << false; } void shouldDissociateTaskFromAllContext() { QFETCH(Akonadi::Item,item); QFETCH(Domain::Task::Ptr,task); QFETCH(Testlib::AkonadiFakeItemFetchJob*,itemFetchJob); QFETCH(bool,execJob); // A mock update job auto itemModifyJob = new FakeJob(this); // Storage mock returning the create job Utils::MockObject storageMock; storageMock(&Akonadi::StorageInterface::fetchItem).when(item) .thenReturn(itemFetchJob); storageMock(&Akonadi::StorageInterface::updateItem).when(item, nullptr) .thenReturn(itemModifyJob); // Serializer mock returning the item for the task Utils::MockObject serializerMock; serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(task) .thenReturn(item); // WHEN QScopedPointer repository(new Akonadi::ContextRepository(storageMock.getInstance(), serializerMock.getInstance())); auto dissociateJob = repository->dissociateAll(task); if (execJob) dissociateJob->exec(); // THEN QVERIFY(storageMock(&Akonadi::StorageInterface::fetchItem).when(item).exactly(1)); if (execJob) { QVERIFY(storageMock(&Akonadi::StorageInterface::updateItem).when(item, nullptr).exactly(1)); } else { delete dissociateJob; } // Give a chance to itemFetchJob to delete itself // in case of an error (since it uses deleteLater() internally) QTest::qWait(10); } }; ZANSHIN_TEST_MAIN(AkonadiContextRepositoryTest) #include "akonadicontextrepositorytest.moc" diff --git a/tests/units/akonadi/akonadilivequeryhelperstest.cpp b/tests/units/akonadi/akonadilivequeryhelperstest.cpp index 84fcb556..b3637ef1 100644 --- a/tests/units/akonadi/akonadilivequeryhelperstest.cpp +++ b/tests/units/akonadi/akonadilivequeryhelperstest.cpp @@ -1,566 +1,566 @@ /* 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 "akonadi/akonadiserializer.h" #include "testlib/akonadifakedata.h" #include "testlib/gencollection.h" -#include "testlib/gentag.h" #include "testlib/gentodo.h" #include "testlib/testhelpers.h" using namespace Testlib; using namespace std::placeholders; static QString titleFromItem(const Akonadi::Item &item) { if (item.hasPayload()) { const auto todo = item.payload(); return todo->summary(); } 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 shouldFetchAllCollections() { // 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 auto fetch = helpers->fetchAllCollections(); 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(); expected << QStringLiteral("43") << QStringLiteral("46") << QStringLiteral("49") << QStringLiteral("52"); 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 shouldFetchCollectionsForRoot_data() { QTest::addColumn("root"); QTest::newRow("all collections from root") << Akonadi::Collection::root(); QTest::newRow("all collections from 'all branch'") << Akonadi::Collection(42); QTest::newRow("all collections from 'task branch'") << Akonadi::Collection(43); QTest::newRow("all collections from 'note branch'") << Akonadi::Collection(44); } void shouldFetchCollectionsForRoot() { // 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); auto fetch = helpers->fetchCollections(root); 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); expected << QString::number(baseId + 1); } 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 shouldFetchItems() { // 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 tasks 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(GenTodo().withId(45).withParent(44).withTitle(QStringLiteral("45"))); data.createItem(GenTodo().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 auto fetch = helpers->fetchItems(); fetch(add); TestHelpers::waitForEmptyJobQueue(); auto result = QStringList(); std::transform(items.constBegin(), items.constEnd(), std::back_inserter(result), titleFromItem); result.sort(); // THEN auto expected = QStringList(); expected << QStringLiteral("43") << QStringLiteral("44") << QStringLiteral("47") << QStringLiteral("48"); 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 shouldFetchItemsByCollection_data() { QTest::addColumn("collection"); QTest::newRow("first collection") << Akonadi::Collection(42); QTest::newRow("second collection") << Akonadi::Collection(43); } void shouldFetchItemsByCollection() { // 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 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(43).withTitle(QStringLiteral("44"))); data.createItem(GenTodo().withId(45).withParent(43).withTitle(QStringLiteral("45"))); // 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::Collection, collection); auto fetch = helpers->fetchItems(collection); 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 (collection.id()) { case 42: expected << QStringLiteral("42") << QStringLiteral("43"); break; case 43: expected << QStringLiteral("44") << QStringLiteral("45"); 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 shouldFetchItemsByTag_data() + void shouldFetchItemsForContext_data() { - QTest::addColumn("tag"); + QTest::addColumn("context"); - QTest::newRow("first tag") << Akonadi::Tag(42); - QTest::newRow("second tag") << Akonadi::Tag(43); + auto context1 = Domain::Context::Ptr::create(); + context1->setProperty("todoUid", "ctx-42"); + auto context2 = Domain::Context::Ptr::create(); + context2->setProperty("todoUid", "ctx-43"); + + QTest::newRow("first") << context1; + QTest::newRow("second") << context2; } - void shouldFetchItemsByTag() + void shouldFetchItemsForContext() { // 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)); + // Two contexts + data.createItem(GenTodo().withId(142).withUid("ctx-42").asContext()); + data.createItem(GenTodo().withId(143).withUid("ctx-43").asContext()); // 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"))); + data.createItem(GenTodo().withId(42).withParent(42).withContexts({}).withTitle(QStringLiteral("42"))); + data.createItem(GenTodo().withId(43).withParent(42).withContexts({"ctx-42"}).withTitle(QStringLiteral("43"))); + data.createItem(GenTodo().withId(44).withParent(42).withContexts({"ctx-43"}).withTitle(QStringLiteral("44"))); + data.createItem(GenTodo().withId(45).withParent(42).withContexts({"ctx-42", "ctx-43"}).withTitle(QStringLiteral("45"))); + data.createItem(GenTodo().withId(46).withParent(43).withContexts({}).withTitle(QStringLiteral("46"))); + data.createItem(GenTodo().withId(47).withParent(43).withContexts({"ctx-42"}).withTitle(QStringLiteral("47"))); + data.createItem(GenTodo().withId(48).withParent(43).withContexts({"ctx-43"}).withTitle(QStringLiteral("48"))); + data.createItem(GenTodo().withId(49).withParent(43).withContexts({"ctx-42", "ctx-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); + QFETCH(Domain::Context::Ptr, context); + auto fetch = helpers->fetchItemsForContext(context); 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: + if (context->property("todoUid") == "ctx-42") expected << QStringLiteral("43") << QStringLiteral("45") << QStringLiteral("47") << QStringLiteral("49"); - break; - case 43: + else if (context->property("todoUid") == "ctx-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(GenTodo().withId(46).withParent(43).withTitle(QStringLiteral("46"))); data.createItem(GenTodo().withId(47).withParent(43).withTitle(QStringLiteral("47"))); data.createItem(GenTodo().withId(48).withParent(43).withTitle(QStringLiteral("48"))); data.createItem(GenTodo().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()); + // Two contexts + data.createItem(GenTodo().withUid("ctx-42").asContext()); + data.createItem(GenTodo().withUid("ctx-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/akonadilivequeryintegratortest.cpp b/tests/units/akonadi/akonadilivequeryintegratortest.cpp index 7914c23f..eea275c4 100644 --- a/tests/units/akonadi/akonadilivequeryintegratortest.cpp +++ b/tests/units/akonadi/akonadilivequeryintegratortest.cpp @@ -1,914 +1,904 @@ /* 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 #include "akonadi/akonadicollectionfetchjobinterface.h" #include "akonadi/akonadiitemfetchjobinterface.h" #include "akonadi/akonaditagfetchjobinterface.h" #include "akonadi/akonadilivequeryintegrator.h" #include "akonadi/akonadiserializer.h" #include "akonadi/akonadistorage.h" #include "testlib/akonadifakedata.h" #include "testlib/gencollection.h" -#include "testlib/gentag.h" #include "testlib/gentodo.h" #include "testlib/testhelpers.h" #include "utils/jobhandler.h" using namespace Testlib; static QString titleFromItem(const Akonadi::Item &item) { if (item.hasPayload()) { const auto todo = item.payload(); return todo->summary(); } else { return QString(); } } class AkonadiLiveQueryIntegratorTest : public QObject { Q_OBJECT private: Akonadi::LiveQueryIntegrator::Ptr createIntegrator(AkonadiFakeData &data) { return Akonadi::LiveQueryIntegrator::Ptr( new Akonadi::LiveQueryIntegrator(createSerializer(), Akonadi::MonitorInterface::Ptr(data.createMonitor()) ) ); } Akonadi::StorageInterface::Ptr createStorage(AkonadiFakeData &data) { return Akonadi::StorageInterface::Ptr(data.createStorage()); } Akonadi::SerializerInterface::Ptr createSerializer() { return Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer); } auto fetchCollectionsFunction(Akonadi::StorageInterface::Ptr storage) { return [storage] (const Domain::LiveQueryInput::AddFunction &add) { auto job = storage->fetchCollections(Akonadi::Collection::root(), Akonadi::Storage::Recursive); Utils::JobHandler::install(job->kjob(), [add, job] { foreach (const auto &col, job->collections()) { add(col); } }); }; } auto fetchItemsInAllCollectionsFunction(Akonadi::StorageInterface::Ptr storage) { return [storage] (const Domain::LiveQueryInput::AddFunction &add) { auto job = storage->fetchCollections(Akonadi::Collection::root(), Akonadi::Storage::Recursive); Utils::JobHandler::install(job->kjob(), [add, job, storage] { foreach (const auto &col, job->collections()) { auto itemJob = storage->fetchItems(col); Utils::JobHandler::install(itemJob->kjob(), [add, itemJob] { foreach (const auto &item, itemJob->items()) add(item); }); } }); }; } auto fetchItemsInSelectedCollectionsFunction(Akonadi::StorageInterface::Ptr storage, Akonadi::SerializerInterface::Ptr serializer) { return [storage, serializer] (const Domain::LiveQueryInput::AddFunction &add) { auto job = storage->fetchCollections(Akonadi::Collection::root(), Akonadi::Storage::Recursive); Utils::JobHandler::install(job->kjob(), [add, job, storage, serializer] { foreach (const auto &col, job->collections()) { if (!serializer->isSelectedCollection(col)) continue; auto itemJob = storage->fetchItems(col); Utils::JobHandler::install(itemJob->kjob(), [add, itemJob] { foreach (const auto &item, itemJob->items()) add(item); }); } }); }; } private slots: void shouldBindContextQueries() { // GIVEN AkonadiFakeData data; - // Three context tags, one not matching the predicate - data.createTag(GenTag().withId(42).asContext().withName(QStringLiteral("42-in"))); - data.createTag(GenTag().withId(43).asContext().withName(QStringLiteral("43-in"))); - data.createTag(GenTag().withId(44).asContext().withName(QStringLiteral("44-ex"))); - - // Couple of plain tags which should not appear or create trouble - data.createTag(GenTag().withId(40).asPlain().withName(QStringLiteral("40"))); - data.createTag(GenTag().withId(41).asPlain().withName(QStringLiteral("41-in"))); + // Three context todos, one not matching the predicate + data.createItem(GenTodo().withUid("ctx-42").asContext().withTitle(QStringLiteral("42-in"))); + data.createItem(GenTodo().withUid("ctx-43").asContext().withTitle(QStringLiteral("43-in"))); + data.createItem(GenTodo().withUid("ctx-44").asContext().withTitle(QStringLiteral("44-ex"))); auto integrator = createIntegrator(data); auto storage = createStorage(data); auto query = Domain::LiveQueryOutput::Ptr(); auto fetch = [storage] (const Domain::LiveQueryInput::AddFunction &add) { auto job = storage->fetchTags(); Utils::JobHandler::install(job->kjob(), [add, job] { foreach (const auto &tag, job->tags()) { add(tag); } }); }; auto predicate = [] (const Akonadi::Tag &tag) { return tag.name().endsWith(QLatin1String("-in")); }; // Initial listing // WHEN integrator->bind("context1", query, fetch, predicate); auto result = query->result(); result->data(); integrator->bind("context2", query, fetch, predicate); result = query->result(); // 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)->name(), QStringLiteral("42-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); // Reacts to add // WHEN - data.createTag(GenTag().withId(45).asContext().withName(QStringLiteral("45-in"))); + data.createItem(GenTodo().withId(45).withUid("ctx-45").asContext().withTitle(QStringLiteral("45-in"))); // THEN QCOMPARE(result->data().size(), 3); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); QCOMPARE(result->data().at(2)->name(), QStringLiteral("45-in")); // Reacts to remove // WHEN data.removeTag(Akonadi::Tag(45)); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); // Reacts to change // WHEN - data.modifyTag(GenTag(data.tag(42)).withName(QStringLiteral("42-bis-in"))); + data.modifyItem(GenTodo(data.item(42)).withTitle(QStringLiteral("42-bis-in"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-bis-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); // Reacts to change (which adds) // WHEN - data.modifyTag(GenTag(data.tag(44)).withName(QStringLiteral("44-in"))); + data.modifyItem(GenTodo(data.item(44)).withTitle(QStringLiteral("44-in"))); // THEN QCOMPARE(result->data().size(), 3); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-bis-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); QCOMPARE(result->data().at(2)->name(), QStringLiteral("44-in")); // Reacts to change (which removes) // WHEN - data.modifyTag(GenTag(data.tag(44)).withName(QStringLiteral("44-ex"))); + data.modifyItem(GenTodo(data.item(44)).withTitle(QStringLiteral("44-ex"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-bis-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); // Don't keep a reference on any result result.clear(); // The bug we're trying to hit here is the following: // - when bind 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 bind 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 integrator->bind("contextN", query, fetch, predicate); auto result = query->result(); // THEN * 2 QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QVERIFY(!result->data().isEmpty()); } } void shouldMoveContextBetweenQueries() { // GIVEN AkonadiFakeData data; // One context tag which shows in one query not the other - data.createTag(GenTag().withId(42).asContext().withName(QStringLiteral("42-in"))); - - // Couple of plain tags which should not appear or create trouble - data.createTag(GenTag().withId(39).asPlain().withName(QStringLiteral("39"))); - data.createTag(GenTag().withId(40).asPlain().withName(QStringLiteral("40-ex"))); - data.createTag(GenTag().withId(41).asPlain().withName(QStringLiteral("41-in"))); + data.createItem(GenTodo().withId(42).withUid("ctx-42").asContext().withTitle(QStringLiteral("42-in"))); auto integrator = createIntegrator(data); auto storage = createStorage(data); auto inQuery = Domain::LiveQueryOutput::Ptr(); auto exQuery = Domain::LiveQueryOutput::Ptr(); auto fetch = [storage] (const Domain::LiveQueryInput::AddFunction &add) { auto job = storage->fetchTags(); Utils::JobHandler::install(job->kjob(), [add, job] { foreach (const auto &tag, job->tags()) { add(tag); } }); }; auto inPredicate = [] (const Akonadi::Tag &tag) { return tag.name().endsWith(QLatin1String("-in")); }; auto exPredicate = [] (const Akonadi::Tag &tag) { return tag.name().endsWith(QLatin1String("-ex")); }; integrator->bind("context-in", inQuery, fetch, inPredicate); auto inResult = inQuery->result(); integrator->bind("context-ex", exQuery, fetch, exPredicate); auto exResult = exQuery->result(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(inResult->data().size(), 1); QCOMPARE(exResult->data().size(), 0); // WHEN - data.modifyTag(GenTag(data.tag(42)).withName(QStringLiteral("42-ex"))); + data.modifyItem(GenTodo(data.item(42)).withTitle(QStringLiteral("42-ex"))); // THEN QCOMPARE(inResult->data().size(), 0); QCOMPARE(exResult->data().size(), 1); } void shouldBindDataSourceQueries() { // GIVEN AkonadiFakeData data; // Three top level collections, one not matching the predicate data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42-in")).withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withName(QStringLiteral("43-in")).withTaskContent()); data.createCollection(GenCollection().withId(44).withRootAsParent().withName(QStringLiteral("44-ex")).withTaskContent()); auto integrator = createIntegrator(data); auto storage = createStorage(data); auto query = Domain::LiveQueryOutput::Ptr(); auto fetch = fetchCollectionsFunction(storage); auto predicate = [] (const Akonadi::Collection &collection) { return collection.name().endsWith(QLatin1String("-in")); }; // Initial listing // WHEN integrator->bind("ds1", query, fetch, predicate); auto result = query->result(); result->data(); integrator->bind("ds2", query, fetch, predicate); result = query->result(); // 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)->name(), QStringLiteral("42-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); // Reacts to add // WHEN data.createCollection(GenCollection().withId(45).withRootAsParent().withName(QStringLiteral("45-in"))); // THEN QCOMPARE(result->data().size(), 3); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); QCOMPARE(result->data().at(2)->name(), QStringLiteral("45-in")); // Reacts to remove // WHEN data.removeCollection(Akonadi::Collection(45)); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); // Reacts to change // WHEN data.modifyCollection(GenCollection(data.collection(42)).withName(QStringLiteral("42-bis-in"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-bis-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); // Reacts to change (which adds) // WHEN data.modifyCollection(GenCollection(data.collection(44)).withName(QStringLiteral("44-in"))); // THEN QCOMPARE(result->data().size(), 3); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-bis-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); QCOMPARE(result->data().at(2)->name(), QStringLiteral("44-in")); // Reacts to change (which removes) // WHEN data.modifyCollection(GenCollection(data.collection(44)).withName(QStringLiteral("44-ex"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-bis-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); // Don't keep a reference on any result result.clear(); // The bug we're trying to hit here is the following: // - when bind 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 bind 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 integrator->bind("dsN", query, fetch, predicate); auto result = query->result(); // THEN * 2 QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QVERIFY(!result->data().isEmpty()); } } void shouldMoveDataSourceBetweenQueries() { // GIVEN AkonadiFakeData data; // One top level collection which shows in one query not the other data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42-in")).withTaskContent()); auto integrator = createIntegrator(data); auto storage = createStorage(data); auto inQuery = Domain::LiveQueryOutput::Ptr(); auto exQuery = Domain::LiveQueryOutput::Ptr(); auto fetch = fetchCollectionsFunction(storage); auto inPredicate = [] (const Akonadi::Collection &collection) { return collection.name().endsWith(QLatin1String("-in")); }; auto exPredicate = [] (const Akonadi::Collection &collection) { return collection.name().endsWith(QLatin1String("-ex")); }; integrator->bind("ds-in", inQuery, fetch, inPredicate); auto inResult = inQuery->result(); integrator->bind("ds-ex", exQuery, fetch, exPredicate); auto exResult = exQuery->result(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(inResult->data().size(), 1); QCOMPARE(exResult->data().size(), 0); // WHEN data.modifyCollection(GenCollection(data.collection(42)).withName(QStringLiteral("42-ex"))); // THEN QCOMPARE(inResult->data().size(), 0); QCOMPARE(exResult->data().size(), 1); } void shouldBindProjectQueries() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42")).withTaskContent()); // Three projects in the collection, one not matching the predicate data.createItem(GenTodo().withId(42).withParent(42).asProject().withTitle(QStringLiteral("42-in"))); data.createItem(GenTodo().withId(43).withParent(42).asProject().withTitle(QStringLiteral("43-in"))); data.createItem(GenTodo().withId(44).withParent(42).asProject().withTitle(QStringLiteral("44-ex"))); // Couple of tasks in the collection which should not appear or create trouble data.createItem(GenTodo().withId(40).withParent(42).withTitle(QStringLiteral("40"))); data.createItem(GenTodo().withId(41).withParent(42).withTitle(QStringLiteral("41-in"))); auto integrator = createIntegrator(data); auto storage = createStorage(data); auto query = Domain::LiveQueryOutput::Ptr(); auto fetch = fetchItemsInAllCollectionsFunction(storage); auto predicate = [] (const Akonadi::Item &item) { return titleFromItem(item).endsWith(QLatin1String("-in")); }; // Initial listing // WHEN integrator->bind("project1", query, fetch, predicate); auto result = query->result(); result->data(); integrator->bind("project2", query, fetch, predicate); result = query->result(); // 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)->name(), QStringLiteral("42-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); // Reacts to add // WHEN data.createItem(GenTodo().withId(45).withParent(42).asProject().withTitle(QStringLiteral("45-in"))); // THEN QCOMPARE(result->data().size(), 3); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); QCOMPARE(result->data().at(2)->name(), QStringLiteral("45-in")); // Reacts to remove // WHEN data.removeItem(Akonadi::Item(45)); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); // Reacts to change // WHEN data.modifyItem(GenTodo(data.item(42)).withTitle(QStringLiteral("42-bis-in"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-bis-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); // Reacts to change (which adds) // WHEN data.modifyItem(GenTodo(data.item(44)).withTitle(QStringLiteral("44-in"))); // THEN QCOMPARE(result->data().size(), 3); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-bis-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); QCOMPARE(result->data().at(2)->name(), QStringLiteral("44-in")); // Reacts to change (which removes) // WHEN data.modifyItem(GenTodo(data.item(44)).withTitle(QStringLiteral("44-ex"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42-bis-in")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43-in")); // Don't keep a reference on any result result.clear(); // The bug we're trying to hit here is the following: // - when bind 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 bind 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 integrator->bind("projectN", query, fetch, predicate); auto result = query->result(); // THEN * 2 QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QVERIFY(!result->data().isEmpty()); } } void shouldMoveProjectsBetweenQueries() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42")).withTaskContent()); // One project which shows in one query and not the other data.createItem(GenTodo().withId(42).withParent(42).asProject().withTitle(QStringLiteral("42-in"))); // Couple of tasks in the collection which should not appear or create trouble data.createItem(GenTodo().withId(39).withParent(42).withTitle(QStringLiteral("39"))); data.createItem(GenTodo().withId(40).withParent(42).withTitle(QStringLiteral("40-ex"))); data.createItem(GenTodo().withId(41).withParent(42).withTitle(QStringLiteral("41-in"))); auto integrator = createIntegrator(data); auto storage = createStorage(data); auto inQuery = Domain::LiveQueryOutput::Ptr(); auto exQuery = Domain::LiveQueryOutput::Ptr(); auto fetch = fetchItemsInAllCollectionsFunction(storage); auto inPredicate = [] (const Akonadi::Item &item) { return titleFromItem(item).endsWith(QLatin1String("-in")); }; auto exPredicate = [] (const Akonadi::Item &item) { return titleFromItem(item).endsWith(QLatin1String("-ex")); }; integrator->bind("project-in", inQuery, fetch, inPredicate); auto inResult = inQuery->result(); integrator->bind("project-ex", exQuery, fetch, exPredicate); auto exResult = exQuery->result(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(inResult->data().size(), 1); QCOMPARE(exResult->data().size(), 0); // WHEN data.modifyItem(GenTodo(data.item(42)).withTitle(QStringLiteral("42-ex"))); // THEN QCOMPARE(inResult->data().size(), 0); QCOMPARE(exResult->data().size(), 1); } void shouldReactToCollectionSelectionChangesForProjectQueries() { // GIVEN AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); // One project in each collection data.createItem(GenTodo().withId(42).withParent(42).asProject().withTitle(QStringLiteral("42"))); data.createItem(GenTodo().withId(43).withParent(43).asProject().withTitle(QStringLiteral("43"))); // Couple of tasks in the collections which should not appear or create trouble data.createItem(GenTodo().withId(40).withParent(42).withTitle(QStringLiteral("40"))); data.createItem(GenTodo().withId(41).withParent(43).withTitle(QStringLiteral("41"))); auto integrator = createIntegrator(data); auto storage = createStorage(data); auto serializer = createSerializer(); auto query = Domain::LiveQueryOutput::Ptr(); auto fetch = fetchItemsInSelectedCollectionsFunction(storage, serializer); auto predicate = [] (const Akonadi::Item &) { return true; }; integrator->bind("project query", query, fetch, predicate); auto result = query->result(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.modifyCollection(GenCollection(data.collection(43)).selected(false)); TestHelpers::waitForEmptyJobQueue(); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42")); } void shouldBindTaskQueries() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42")).withTaskContent()); // Three tasks in the collection, one not matching the predicate data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42-in"))); data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43-in"))); data.createItem(GenTodo().withId(44).withParent(42).withTitle(QStringLiteral("44-ex"))); // Couple of projects in the collection which should not appear or create trouble data.createItem(GenTodo().withId(38).withParent(42).asProject().withTitle(QStringLiteral("38"))); data.createItem(GenTodo().withId(39).withParent(42).asProject().withTitle(QStringLiteral("39-in"))); auto integrator = createIntegrator(data); auto storage = createStorage(data); auto query = Domain::LiveQueryOutput::Ptr(); auto fetch = fetchItemsInAllCollectionsFunction(storage); auto predicate = [] (const Akonadi::Item &item) { return titleFromItem(item).endsWith(QLatin1String("-in")); }; // Initial listing // WHEN integrator->bind("task1", query, fetch, predicate); auto result = query->result(); result->data(); integrator->bind("task2", query, fetch, predicate); result = query->result(); // 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-in")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43-in")); // Reacts to add // WHEN data.createItem(GenTodo().withId(45).withParent(42).withTitle(QStringLiteral("45-in"))); // THEN QCOMPARE(result->data().size(), 3); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42-in")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43-in")); QCOMPARE(result->data().at(2)->title(), QStringLiteral("45-in")); // Reacts to remove // WHEN data.removeItem(Akonadi::Item(45)); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42-in")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43-in")); // Reacts to change // WHEN data.modifyItem(GenTodo(data.item(42)).withTitle(QStringLiteral("42-bis-in"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42-bis-in")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43-in")); // Reacts to change (which adds) // WHEN data.modifyItem(GenTodo(data.item(44)).withTitle(QStringLiteral("44-in"))); // THEN QCOMPARE(result->data().size(), 3); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42-bis-in")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43-in")); QCOMPARE(result->data().at(2)->title(), QStringLiteral("44-in")); // Reacts to change (which removes) // WHEN data.modifyItem(GenTodo(data.item(44)).withTitle(QStringLiteral("44-ex"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42-bis-in")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43-in")); // Don't keep a reference on any result result.clear(); // The bug we're trying to hit here is the following: // - when bind 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 bind 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 integrator->bind("taskN", query, fetch, predicate); auto result = query->result(); // THEN * 2 QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QVERIFY(!result->data().isEmpty()); } } void shouldMoveTasksBetweenQueries() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42")).withTaskContent()); // One task which shows in one query and not the other data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42-in"))); auto integrator = createIntegrator(data); auto storage = createStorage(data); auto inQuery = Domain::LiveQueryOutput::Ptr(); auto exQuery = Domain::LiveQueryOutput::Ptr(); auto fetch = fetchItemsInAllCollectionsFunction(storage); auto inPredicate = [] (const Akonadi::Item &item) { return titleFromItem(item).endsWith(QLatin1String("-in")); }; auto exPredicate = [] (const Akonadi::Item &item) { return titleFromItem(item).endsWith(QLatin1String("-ex")); }; integrator->bind("task-in", inQuery, fetch, inPredicate); auto inResult = inQuery->result(); integrator->bind("task-ex", exQuery, fetch, exPredicate); auto exResult = exQuery->result(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(inResult->data().size(), 1); QCOMPARE(exResult->data().size(), 0); // WHEN data.modifyItem(GenTodo(data.item(42)).withTitle(QStringLiteral("42-ex"))); // THEN QCOMPARE(inResult->data().size(), 0); QCOMPARE(exResult->data().size(), 1); } void shouldReactToCollectionSelectionChangesForTaskQueries() { // 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"))); // Couple of projects in the collections which should not appear or create trouble data.createItem(GenTodo().withId(40).withParent(42).asProject().withTitle(QStringLiteral("40"))); data.createItem(GenTodo().withId(41).withParent(43).asProject().withTitle(QStringLiteral("41"))); auto integrator = createIntegrator(data); auto storage = createStorage(data); auto serializer = createSerializer(); auto query = Domain::LiveQueryOutput::Ptr(); auto fetch = fetchItemsInSelectedCollectionsFunction(storage, serializer); auto predicate = [] (const Akonadi::Item &) { return true; }; integrator->bind("task query", query, fetch, predicate); auto result = query->result(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // 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 shouldCallCollectionRemoveHandlers() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42"))); auto integrator = createIntegrator(data); qint64 removedId = -1; integrator->addRemoveHandler([&removedId] (const Akonadi::Collection &collection) { removedId = collection.id(); }); // WHEN data.removeCollection(Akonadi::Collection(42)); // THEN QCOMPARE(removedId, qint64(42)); } void shouldCallItemRemoveHandlers() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42"))); // One item in the collection data.createItem(GenTodo().withId(42).withParent(42)); auto integrator = createIntegrator(data); qint64 removedId = -1; integrator->addRemoveHandler([&removedId] (const Akonadi::Item &item) { removedId = item.id(); }); // WHEN data.removeItem(Akonadi::Item(42)); // THEN QCOMPARE(removedId, qint64(42)); } void shouldCallTagRemoveHandlers() { // GIVEN AkonadiFakeData data; // One tag - data.createTag(GenTag().withId(42).withName(QStringLiteral("42"))); + data.createItem(GenTodo().withId(42).withUid("ctx-42").withTitle(QStringLiteral("42"))); auto integrator = createIntegrator(data); qint64 removedId = -1; integrator->addRemoveHandler([&removedId] (const Akonadi::Tag &tag) { removedId = tag.id(); }); // WHEN data.removeTag(Akonadi::Tag(42)); // THEN QCOMPARE(removedId, qint64(42)); } }; ZANSHIN_TEST_MAIN(AkonadiLiveQueryIntegratorTest) #include "akonadilivequeryintegratortest.moc" diff --git a/tests/units/akonadi/akonaditaskqueriestest.cpp b/tests/units/akonadi/akonaditaskqueriestest.cpp index 668fcb0c..a2abc715 100644 --- a/tests/units/akonadi/akonaditaskqueriestest.cpp +++ b/tests/units/akonadi/akonaditaskqueriestest.cpp @@ -1,1815 +1,1814 @@ /* 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/akonadicachingstorage.h" #include "akonadi/akonaditaskqueries.h" #include "akonadi/akonadiserializer.h" #include "akonadi/akonadiitemfetchjobinterface.h" #include "testlib/akonadifakedata.h" #include "testlib/gencollection.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: Akonadi::StorageInterface::Ptr createCachingStorage(AkonadiFakeData &data, const Akonadi::Cache::Ptr &cache) { auto storage = Akonadi::StorageInterface::Ptr(data.createStorage()); return Akonadi::StorageInterface::Ptr(new Akonadi::CachingStorage(cache, storage)); } 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()), Akonadi::Cache::Ptr())); 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()), Akonadi::Cache::Ptr())); 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()), Akonadi::Cache::Ptr())); 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()), Akonadi::Cache::Ptr())); 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()), Akonadi::Cache::Ptr())); 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()), Akonadi::Cache::Ptr())); 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()), Akonadi::Cache::Ptr())); 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()), Akonadi::Cache::Ptr())); 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()), Akonadi::Cache::Ptr())); 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()), Akonadi::Cache::Ptr())); 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()), Akonadi::Cache::Ptr())); 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()), Akonadi::Cache::Ptr())); 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()), Akonadi::Cache::Ptr())); 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()), Akonadi::Cache::Ptr())); 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()), Akonadi::Cache::Ptr())); 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()), Akonadi::Cache::Ptr())); 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()), Akonadi::Cache::Ptr())); 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()), Akonadi::Cache::Ptr())); 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()), Akonadi::Cache::Ptr())); 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()), Akonadi::Cache::Ptr())); 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()), Akonadi::Cache::Ptr())); 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, Akonadi::Cache::Ptr())); 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, Akonadi::Cache::Ptr())); 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, Akonadi::Cache::Ptr())); 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()), Akonadi::Cache::Ptr())); 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().selected(false)); // 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()), Akonadi::Cache::Ptr())); 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()), Akonadi::Cache::Ptr())); 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()), Akonadi::Cache::Ptr())); 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 context item + data.createItem(GenTodo().withParent(42).withId(1).withUid("ctx-42").withTitle(QStringLiteral("Context 42")).asContext()); // One item in the collection QFETCH(bool, hasContexts); - auto tagIds = QList(); - if (hasContexts) tagIds << 42; + auto contextUids = QStringList(); + if (hasContexts) contextUids << "ctx-42"; - data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).withTags(tagIds)); + data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).withContexts(contextUids)); // WHEN QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); 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()); + // One context item + data.createItem(GenTodo().withParent(42).withId(1).withUid("ctx-42").withTitle(QStringLiteral("Context 1")).asContext()); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findInboxTopLevel(); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); // WHEN QFETCH(QString, relatedUid); QFETCH(bool, hasContexts); - auto tagIds = QList(); - if (hasContexts) tagIds << 42; + auto contextUids = QStringList(); + if (hasContexts) contextUids << "ctx-42"; - data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).withTags(tagIds).withParentUid(relatedUid)); + data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).withContexts(contextUids).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()), Akonadi::Cache::Ptr())); 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()); + // One context item + data.createItem(GenTodo().withParent(42).withId(1).withUid("ctx-42").withTitle(QStringLiteral("Context 1")).asContext()); // Task 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()), Akonadi::Cache::Ptr())); 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()), Akonadi::Cache::Ptr())); 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::currentDate(); 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(today)); QTest::newRow("startTodayDoneTask") << true << Akonadi::Item(GenTodo() .withStartDate(today) .done() .withDoneDate(today)); QTest::newRow("endTodayDoneTask") << true << Akonadi::Item(GenTodo() .withDueDate(today) .done() .withDoneDate(today)); } void shouldLookInAllWorkdayReportedForAllTasks() { // GIVEN const auto today = Utils::DateTime::currentDate(); 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 (from data driven set) QFETCH(Akonadi::Item, item2); data.createItem(GenTodo(item2).withId(43).withParent(43) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); // WHEN auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); QScopedPointer queries(new Akonadi::TaskQueries(createCachingStorage(data, cache), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache)); 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_DATE", "2015-03-10"); const auto today = Utils::DateTime::currentDate(); 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)); // WHEN auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); QScopedPointer queries(new Akonadi::TaskQueries(createCachingStorage(data, cache), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache)); 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_DATE", "2015-03-10"); const auto today = Utils::DateTime::currentDate(); 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 serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); auto akqueries = new Akonadi::TaskQueries(createCachingStorage(data, cache), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache); 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_DATE", "2015-03-11"); 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")); } void shouldNotListWorkdayTasksTwiceIfTheyHaveAParentInWorkday() { // GIVEN const auto today = Utils::DateTime::currentDate(); AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); - // One context tag - data.createTag(GenTag().withId(42).withName(QStringLiteral("42")).asContext()); + // One context item + data.createItem(GenTodo().withParent(42).withId(1).withUid("ctx-42").withTitle(QStringLiteral("Context 1")).asContext()); // Five tasks in the collection, two start today, three not, all forming an ancestry line data.createItem(GenTodo().withParent(42).withId(42).withTitle(QStringLiteral("42")).withUid("42")); data.createItem(GenTodo().withParent(42).withId(43).withTitle(QStringLiteral("43")).withUid("43").withParentUid("42").withStartDate(today)); data.createItem(GenTodo().withParent(42).withId(44).withTitle(QStringLiteral("44")).withUid("44").withParentUid("43")); data.createItem(GenTodo().withParent(42).withId(45).withTitle(QStringLiteral("45")).withUid("45").withParentUid("44").withStartDate(today)); data.createItem(GenTodo().withParent(42).withId(46).withTitle(QStringLiteral("46")).withUid("46").withParentUid("45")); // WHEN auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); QScopedPointer queries(new Akonadi::TaskQueries(createCachingStorage(data, cache), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache)); 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(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("43")); // Should not change anything result = queries->findWorkdayTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("43")); } void findProjectShouldLookInCollection() { // GIVEN AkonadiFakeData data; // One top level collection auto collection = GenCollection().withId(42).withRootAsParent().withTaskContent(); data.createCollection(collection); // Three tasks in the collection (two being children of the first one) data.createItem(GenTodo().withId(42).asProject().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); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); auto storage = createCachingStorage(data, cache); QScopedPointer queries(new Akonadi::TaskQueries(storage, serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache)); auto task = serializer->createTaskFromItem(data.item(44)); // populate cache for collection auto *fetchJob = storage->fetchItems(collection); QVERIFY2(fetchJob->kjob()->exec(), qPrintable(fetchJob->kjob()->errorString())); auto result = queries->findProject(task); // THEN QVERIFY(result); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42")); // Should not change anything result = queries->findProject(task); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42")); } void findProjectShouldReactToRelationshipChange() { // GIVEN AkonadiFakeData data; // One top level collection const Akonadi::Collection::Id colId = 42; auto collection = GenCollection().withId(colId).withRootAsParent().withTaskContent(); data.createCollection(collection); // Three tasks in the collection (two being children of the first one) // 1->2->3 (project) where 1 changes to 1->4->5 (project) data.createItem(GenTodo().withId(3).asProject().withParent(colId) .withTitle(QStringLiteral("Project 3")).withUid(QStringLiteral("uid-3"))); data.createItem(GenTodo().withId(2).withParent(colId) .withTitle(QStringLiteral("Intermediate item 2")).withUid(QStringLiteral("uid-2")) .withParentUid(QStringLiteral("uid-3"))); data.createItem(GenTodo().withId(1).withParent(colId) .withTitle(QStringLiteral("Item 1")).withUid(QStringLiteral("uid-1")) .withParentUid(QStringLiteral("uid-2"))); data.createItem(GenTodo().withId(5).asProject().withParent(colId) .withTitle(QStringLiteral("Project 5")).withUid(QStringLiteral("uid-5"))); data.createItem(GenTodo().withId(4).withParent(colId) .withTitle(QStringLiteral("Intermediate item 4")).withUid(QStringLiteral("uid-4")) .withParentUid(QStringLiteral("uid-5"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); auto storage = createCachingStorage(data, cache); QScopedPointer queries(new Akonadi::TaskQueries(storage, serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache)); auto task = serializer->createTaskFromItem(data.item(1)); auto result = queries->findProject(task); QVERIFY(result); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->name(), QStringLiteral("Project 3")); // WHEN data.modifyItem(GenTodo(data.item(1)).withParentUid(QStringLiteral("uid-4"))); // THEN TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->name(), QStringLiteral("Project 5")); } void findProjectShouldReactToIntermediateParentChange() { // GIVEN AkonadiFakeData data; // One top level collection const Akonadi::Collection::Id colId = 42; data.createCollection(GenCollection().withId(colId).withRootAsParent().withTaskContent()); // Three tasks in the collection (two being children of the first one) // 1->2->3 (project) where 2 changes to 1->2->4 (project) data.createItem(GenTodo().withId(3).asProject().withParent(colId) .withTitle(QStringLiteral("Project 3")).withUid(QStringLiteral("uid-3"))); data.createItem(GenTodo().withId(2).withParent(colId) .withTitle(QStringLiteral("Intermediate item 2")).withUid(QStringLiteral("uid-2")) .withParentUid(QStringLiteral("uid-3"))); data.createItem(GenTodo().withId(1).withParent(colId) .withTitle(QStringLiteral("Item 1")).withUid(QStringLiteral("uid-1")) .withParentUid(QStringLiteral("uid-2"))); data.createItem(GenTodo().withId(4).asProject().withParent(colId) .withTitle(QStringLiteral("Project 4")).withUid(QStringLiteral("uid-4"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); auto storage = createCachingStorage(data, cache); QScopedPointer queries(new Akonadi::TaskQueries(storage, serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache)); auto task = serializer->createTaskFromItem(data.item(1)); auto result = queries->findProject(task); QVERIFY(result); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->name(), QStringLiteral("Project 3")); // WHEN data.modifyItem(GenTodo(data.item(2)).withParentUid(QStringLiteral("uid-4"))); // THEN TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->name(), QStringLiteral("Project 4")); // AND WHEN data.removeItem(GenTodo(data.item(2))); // THEN TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 0); } void findProjectShouldReactToChildItemRemoved() { // GIVEN AkonadiFakeData data; // One top level collection const Akonadi::Collection::Id colId = 42; data.createCollection(GenCollection().withId(colId).withRootAsParent().withTaskContent()); // Three task in the collection: 1->2(project) data.createItem(GenTodo().withId(2).asProject().withParent(colId) .withTitle(QStringLiteral("Project 2")).withUid(QStringLiteral("uid-2"))); data.createItem(GenTodo().withId(1).withParent(colId) .withTitle(QStringLiteral("Item 1")).withUid(QStringLiteral("uid-1")) .withParentUid(QStringLiteral("uid-2"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); auto storage = createCachingStorage(data, cache); QScopedPointer queries(new Akonadi::TaskQueries(storage, serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache)); auto task = serializer->createTaskFromItem(data.item(1)); auto result = queries->findProject(task); QVERIFY(result); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->name(), QStringLiteral("Project 2")); // WHEN data.removeItem(Akonadi::Item(1)); // THEN TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 0); } }; ZANSHIN_TEST_MAIN(AkonadiTaskQueriesTest) #include "akonaditaskqueriestest.moc" diff --git a/tests/units/testlib/gentodotest.cpp b/tests/units/testlib/gentodotest.cpp index 4b4e6e53..576c200c 100644 --- a/tests/units/testlib/gentodotest.cpp +++ b/tests/units/testlib/gentodotest.cpp @@ -1,219 +1,221 @@ /* 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/gentodo.h" #include #include using namespace Testlib; class GenTodoTest : public QObject { Q_OBJECT private slots: void shouldImplicitlyConvertBackToItem() { // GIVEN auto item = Akonadi::Item(42); auto gen = GenTodo(item); // WHEN Akonadi::Item newItem = gen; // THEN QCOMPARE(newItem, item); QCOMPARE(newItem.mimeType(), KCalCore::Todo::todoMimeType()); QVERIFY(newItem.hasPayload()); } void shouldAllowToSetId() { // GIVEN Akonadi::Item item = GenTodo().withId(42); // THEN QCOMPARE(item.id(), 42LL); } void shouldAllowToSetParent() { // GIVEN Akonadi::Item item = GenTodo().withParent(42); // THEN QCOMPARE(item.parentCollection().id(), 42LL); } void shouldAllowToSetTags() { // GIVEN - Akonadi::Item item = GenTodo().withTags({42, 43, 44}); + Akonadi::Item item = GenTodo().withContexts({"42", "43", "44"}); // THEN - QCOMPARE(item.tags().size(), 3); - QCOMPARE(item.tags().at(0).id(), 42LL); - QCOMPARE(item.tags().at(1).id(), 43LL); - QCOMPARE(item.tags().at(2).id(), 44LL); + auto todo = item.payload(); + QStringList contextUids = todo->customProperty("Zanshin", "ContextList").split(','); + QCOMPARE(contextUids.size(), 3); + QCOMPARE(contextUids.at(0), "42"); + QCOMPARE(contextUids.at(1), "43"); + QCOMPARE(contextUids.at(2), "44"); } void shouldAllowToSetProjectType() { // GIVEN Akonadi::Item item = GenTodo().asProject(); // THEN QVERIFY(!item.payload()->customProperty("Zanshin", "Project").isEmpty()); // WHEN item = GenTodo(item).asProject(false); // THEN QVERIFY(item.payload()->customProperty("Zanshin", "Project").isEmpty()); } void shouldAllowToSetContextType() { // GIVEN Akonadi::Item item = GenTodo().asContext(); // THEN QVERIFY(!item.payload()->customProperty("Zanshin", "Context").isEmpty()); // WHEN item = GenTodo(item).asContext(false); // THEN QVERIFY(item.payload()->customProperty("Zanshin", "Context").isEmpty()); } void shouldAllowToSetUid() { // GIVEN Akonadi::Item item = GenTodo().withUid(QStringLiteral("42")); // THEN QCOMPARE(item.payload()->uid(), QStringLiteral("42")); } void shouldAllowToSetParentUid() { // GIVEN Akonadi::Item item = GenTodo().withParentUid(QStringLiteral("42")); // THEN QCOMPARE(item.payload()->relatedTo(), QStringLiteral("42")); } void shouldAllowToSetTitle() { // GIVEN Akonadi::Item item = GenTodo().withTitle(QStringLiteral("42")); // THEN QCOMPARE(item.payload()->summary(), QStringLiteral("42")); } void shouldAllowToSetText() { // GIVEN Akonadi::Item item = GenTodo().withText(QStringLiteral("42")); // THEN QCOMPARE(item.payload()->description(), QStringLiteral("42")); } void shouldAllowToSetDoneState() { // GIVEN Akonadi::Item item = GenTodo().done(); // THEN QVERIFY(item.payload()->isCompleted()); // WHEN item = GenTodo(item).done(false); // THEN QVERIFY(!item.payload()->isCompleted()); } void shouldAllowToSetDoneDate() { // GIVEN Akonadi::Item item = GenTodo().withDoneDate(QDate(2015, 4, 12)); // THEN QCOMPARE(item.payload()->completed().toLocalTime().date(), QDate(2015, 04, 12)); } void shouldAllowToSetDoneDateString() { // GIVEN Akonadi::Item item = GenTodo().withDoneDate(QStringLiteral("2015-04-12")); // THEN QCOMPARE(item.payload()->completed().toLocalTime().date(), QDate(2015, 04, 12)); } void shouldAllowToSetStartDate() { // GIVEN Akonadi::Item item = GenTodo().withStartDate(QDate(2015, 4, 12)); // THEN QCOMPARE(item.payload()->dtStart().date(), QDate(2015, 04, 12)); } void shouldAllowToSetStartDateString() { // GIVEN Akonadi::Item item = GenTodo().withStartDate(QStringLiteral("2015-04-12")); // THEN QCOMPARE(item.payload()->dtStart().date(), QDate(2015, 04, 12)); } void shouldAllowToSetDueDate() { // GIVEN Akonadi::Item item = GenTodo().withDueDate(QDate(2015, 4, 12)); // THEN QCOMPARE(item.payload()->dtDue().date(), QDate(2015, 04, 12)); } void shouldAllowToSetDueDateString() { // GIVEN Akonadi::Item item = GenTodo().withDueDate(QStringLiteral("2015-04-12")); // THEN QCOMPARE(item.payload()->dtDue().date(), QDate(2015, 04, 12)); } }; ZANSHIN_TEST_MAIN(GenTodoTest) #include "gentodotest.moc"