diff --git a/src/akonadi/akonadilivequeryhelpers.cpp b/src/akonadi/akonadilivequeryhelpers.cpp index 83c9d27c..2b18eb92 100644 --- a/src/akonadi/akonadilivequeryhelpers.cpp +++ b/src/akonadi/akonadilivequeryhelpers.cpp @@ -1,172 +1,187 @@ /* 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 "utils/jobhandler.h" using namespace Akonadi; LiveQueryHelpers::LiveQueryHelpers(const SerializerInterface::Ptr &serializer, const StorageInterface::Ptr &storage) : m_serializer(serializer), m_storage(storage) { } LiveQueryHelpers::CollectionFetchFunction LiveQueryHelpers::fetchAllCollections(StorageInterface::FetchContentTypes contentTypes) const { auto storage = m_storage; return [storage, contentTypes] (const Domain::LiveQueryInput::AddFunction &add) { auto job = storage->fetchCollections(Collection::root(), StorageInterface::Recursive, contentTypes); Utils::JobHandler::install(job->kjob(), [job, add] { if (job->kjob()->error()) return; foreach (const auto &collection, job->collections()) add(collection); }); }; } LiveQueryHelpers::CollectionFetchFunction LiveQueryHelpers::fetchCollections(const Collection &root, StorageInterface::FetchContentTypes contentTypes) const { auto storage = m_storage; return [storage, contentTypes, root] (const Domain::LiveQueryInput::AddFunction &add) { auto job = storage->fetchCollections(root, StorageInterface::Recursive, contentTypes); Utils::JobHandler::install(job->kjob(), [root, job, add] { if (job->kjob()->error()) return; auto directChildren = QHash(); foreach (const auto &collection, job->collections()) { auto directChild = collection; while (directChild.parentCollection() != root) directChild = directChild.parentCollection(); if (!directChildren.contains(directChild.id())) directChildren[directChild.id()] = directChild; } foreach (const auto &directChild, directChildren) add(directChild); }); }; } LiveQueryHelpers::ItemFetchFunction LiveQueryHelpers::fetchItems(StorageInterface::FetchContentTypes contentTypes) const { auto serializer = m_serializer; auto storage = m_storage; return [serializer, storage, contentTypes] (const Domain::LiveQueryInput::AddFunction &add) { auto job = storage->fetchCollections(Akonadi::Collection::root(), StorageInterface::Recursive, contentTypes); Utils::JobHandler::install(job->kjob(), [serializer, storage, job, add] { if (job->kjob()->error() != KJob::NoError) return; foreach (const auto &collection, job->collections()) { if (!serializer->isSelectedCollection(collection)) continue; auto job = storage->fetchItems(collection); Utils::JobHandler::install(job->kjob(), [job, add] { if (job->kjob()->error() != KJob::NoError) return; foreach (const auto &item, job->items()) add(item); }); } }); }; } +LiveQueryHelpers::ItemFetchFunction LiveQueryHelpers::fetchItems(const 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 { // TODO: Qt5, use the proper implementation once we got a working akonadi #if 0 auto storage = m_storage; return [storage, tag] (const Domain::LiveQueryInput::AddFunction &add) { auto job = storage->fetchTagItems(tag); Utils::JobHandler::install(job->kjob(), [job, add] { if (job->kjob()->error() != KJob::NoError) return; foreach (const auto &item, job->items()) add(item); }); }; #else auto fetchFunction = fetchItems(StorageInterface::Tasks | StorageInterface::Notes); return [tag, fetchFunction] (const Domain::LiveQueryInput::AddFunction &add) { auto filterAdd = [tag, add] (const Item &item) { if (item.tags().contains(tag)) add(item); }; fetchFunction(filterAdd); }; #endif } LiveQueryHelpers::ItemFetchFunction LiveQueryHelpers::fetchSiblings(const Item &item) const { auto storage = m_storage; return [storage, item] (const Domain::LiveQueryInput::AddFunction &add) { auto job = storage->fetchItem(item); Utils::JobHandler::install(job->kjob(), [storage, job, add] { if (job->kjob()->error() != KJob::NoError) return; Q_ASSERT(job->items().size() == 1); auto item = job->items().at(0); Q_ASSERT(item.parentCollection().isValid()); auto job = storage->fetchItems(item.parentCollection()); Utils::JobHandler::install(job->kjob(), [job, add] { if (job->kjob()->error() != KJob::NoError) return; foreach (const auto &item, job->items()) add(item); }); }); }; } LiveQueryHelpers::TagFetchFunction LiveQueryHelpers::fetchTags() const { auto storage = m_storage; return [storage] (const Domain::LiveQueryInput::AddFunction &add) { auto job = storage->fetchTags(); Utils::JobHandler::install(job->kjob(), [job, add] { foreach (const auto &tag, job->tags()) add(tag); }); }; } diff --git a/src/akonadi/akonadilivequeryhelpers.h b/src/akonadi/akonadilivequeryhelpers.h index 33dd9d28..9b8cb7a9 100644 --- a/src/akonadi/akonadilivequeryhelpers.h +++ b/src/akonadi/akonadilivequeryhelpers.h @@ -1,63 +1,64 @@ /* This file is part of Zanshin Copyright 2015 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_LIVEQUERYHELPERS_H #define AKONADI_LIVEQUERYHELPERS_H #include "akonadi/akonadiserializerinterface.h" #include "akonadi/akonadistorageinterface.h" #include "domain/livequery.h" namespace Akonadi { class LiveQueryHelpers { public: typedef QSharedPointer Ptr; typedef Domain::LiveQueryInput::FetchFunction CollectionFetchFunction; typedef Domain::LiveQueryInput::FetchFunction ItemFetchFunction; typedef Domain::LiveQueryInput::FetchFunction TagFetchFunction; LiveQueryHelpers(const SerializerInterface::Ptr &serializer, const StorageInterface::Ptr &storage); CollectionFetchFunction fetchAllCollections(StorageInterface::FetchContentTypes contentTypes) const; CollectionFetchFunction fetchCollections(const Collection &root, StorageInterface::FetchContentTypes contentTypes) const; ItemFetchFunction fetchItems(StorageInterface::FetchContentTypes contentTypes) const; + ItemFetchFunction fetchItems(const Collection &collection) const; ItemFetchFunction fetchItems(const Tag &tag) const; ItemFetchFunction fetchSiblings(const Item &item) const; TagFetchFunction fetchTags() const; private: SerializerInterface::Ptr m_serializer; StorageInterface::Ptr m_storage; }; } #endif // AKONADI_LIVEQUERYHELPERS_H diff --git a/tests/units/akonadi/akonadilivequeryhelperstest.cpp b/tests/units/akonadi/akonadilivequeryhelperstest.cpp index 17fe614f..a55759b5 100644 --- a/tests/units/akonadi/akonadilivequeryhelperstest.cpp +++ b/tests/units/akonadi/akonadilivequeryhelperstest.cpp @@ -1,588 +1,661 @@ /* This file is part of Zanshin Copyright 2015 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "akonadi/akonadilivequeryhelpers.h" #include #include #include #include #include "akonadi/akonadiserializer.h" #include "testlib/akonadifakedata.h" #include "testlib/gencollection.h" #include "testlib/gennote.h" #include "testlib/gentag.h" #include "testlib/gentodo.h" #include "testlib/testhelpers.h" using namespace Testlib; using namespace std::placeholders; Q_DECLARE_METATYPE(Akonadi::StorageInterface::FetchContentTypes) static QString titleFromItem(const Akonadi::Item &item) { if (item.hasPayload()) { const auto todo = item.payload(); return todo->summary(); } else if (item.hasPayload()) { const auto message = item.payload(); const Akonadi::NoteUtils::NoteMessageWrapper note(message); return note.title(); } else { return QString(); } } class AkonadiLiveQueryHelpersTest : public QObject { Q_OBJECT private: Akonadi::LiveQueryHelpers::Ptr createHelpers(AkonadiFakeData &data) { return Akonadi::LiveQueryHelpers::Ptr(new Akonadi::LiveQueryHelpers(createSerializer(), createStorage(data))); } Akonadi::StorageInterface::Ptr createStorage(AkonadiFakeData &data) { return Akonadi::StorageInterface::Ptr(data.createStorage()); } Akonadi::SerializerInterface::Ptr createSerializer() { return Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer); } private slots: void shouldFetchAllCollectionsForType_data() { QTest::addColumn("contentTypes"); QTest::newRow("task collections only") << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Tasks); QTest::newRow("note collections only") << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Notes); QTest::newRow("task and note collections only") << (Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes); QTest::newRow("all collections") << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::AllContent); } void shouldFetchAllCollectionsForType() { // GIVEN auto data = AkonadiFakeData(); auto helpers = createHelpers(data); // Three top level collections (any content, tasks and notes) data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42"))); data.createCollection(GenCollection().withId(43).withRootAsParent().withName(QStringLiteral("43")).withTaskContent()); data.createCollection(GenCollection().withId(44).withRootAsParent().withName(QStringLiteral("44")).withNoteContent()); // Three children under each of the top level for each content type data.createCollection(GenCollection().withId(45).withParent(42).withName(QStringLiteral("45"))); data.createCollection(GenCollection().withId(46).withParent(42).withName(QStringLiteral("46")).withTaskContent()); data.createCollection(GenCollection().withId(47).withParent(42).withName(QStringLiteral("47")).withNoteContent()); data.createCollection(GenCollection().withId(48).withParent(43).withName(QStringLiteral("48"))); data.createCollection(GenCollection().withId(49).withParent(43).withName(QStringLiteral("49")).withTaskContent()); data.createCollection(GenCollection().withId(50).withParent(43).withName(QStringLiteral("50")).withNoteContent()); data.createCollection(GenCollection().withId(51).withParent(44).withName(QStringLiteral("51"))); data.createCollection(GenCollection().withId(52).withParent(44).withName(QStringLiteral("52")).withTaskContent()); data.createCollection(GenCollection().withId(53).withParent(44).withName(QStringLiteral("53")).withNoteContent()); // The list which will be filled by the fetch function auto collections = Akonadi::Collection::List(); auto add = [&collections] (const Akonadi::Collection &collection) { collections.append(collection); }; // WHEN QFETCH(Akonadi::StorageInterface::FetchContentTypes, contentTypes); auto fetch = helpers->fetchAllCollections(contentTypes); fetch(add); TestHelpers::waitForEmptyJobQueue(); auto result = QStringList(); std::transform(collections.constBegin(), collections.constEnd(), std::back_inserter(result), std::bind(&Akonadi::Collection::displayName, _1)); result.sort(); // THEN auto expected = QStringList(); if (contentTypes == Akonadi::StorageInterface::AllContent) { expected << QStringLiteral("42") << QStringLiteral("45") << QStringLiteral("48") << QStringLiteral("51"); } if ((contentTypes & Akonadi::StorageInterface::Tasks) || contentTypes == Akonadi::StorageInterface::AllContent) { expected << QStringLiteral("43") << QStringLiteral("46") << QStringLiteral("49") << QStringLiteral("52"); } if ((contentTypes & Akonadi::StorageInterface::Notes) || contentTypes == Akonadi::StorageInterface::AllContent) { expected << QStringLiteral("44") << QStringLiteral("47") << QStringLiteral("50") << QStringLiteral("53"); } expected.sort(); QCOMPARE(result, expected); // WHEN (should not crash when the helpers object is deleted) helpers.clear(); collections.clear(); fetch(add); TestHelpers::waitForEmptyJobQueue(); // THEN result.clear(); std::transform(collections.constBegin(), collections.constEnd(), std::back_inserter(result), std::bind(&Akonadi::Collection::displayName, _1)); result.sort(); QCOMPARE(result, expected); } void shouldFetchCollectionsForRootAndType_data() { QTest::addColumn("root"); QTest::addColumn("contentTypes"); QTest::newRow("task collections only from root") << Akonadi::Collection::root() << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Tasks); QTest::newRow("note collections only from root") << Akonadi::Collection::root() << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Notes); QTest::newRow("task and note collections only from root") << Akonadi::Collection::root() << (Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes); QTest::newRow("all collections from root") << Akonadi::Collection::root() << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::AllContent); QTest::newRow("task collections only from 'all branch'") << Akonadi::Collection(42) << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Tasks); QTest::newRow("note collections only from 'all branch'") << Akonadi::Collection(42) << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Notes); QTest::newRow("task and note collections only from 'all branch'") << Akonadi::Collection(42) << (Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes); QTest::newRow("all collections from 'all branch'") << Akonadi::Collection(42) << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::AllContent); QTest::newRow("task collections only from 'task branch'") << Akonadi::Collection(43) << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Tasks); QTest::newRow("note collections only from 'task branch'") << Akonadi::Collection(43) << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Notes); QTest::newRow("task and note collections only from 'task branch'") << Akonadi::Collection(43) << (Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes); QTest::newRow("all collections from 'task branch'") << Akonadi::Collection(43) << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::AllContent); QTest::newRow("task collections only from 'note branch'") << Akonadi::Collection(44) << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Tasks); QTest::newRow("note collections only from 'note branch'") << Akonadi::Collection(44) << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Notes); QTest::newRow("task and note collections only from 'note branch'") << Akonadi::Collection(44) << (Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes); QTest::newRow("all collections from 'note branch'") << Akonadi::Collection(44) << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::AllContent); } void shouldFetchCollectionsForRootAndType() { // GIVEN auto data = AkonadiFakeData(); auto helpers = createHelpers(data); // Three top level collections (any content, tasks and notes) data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42"))); data.createCollection(GenCollection().withId(43).withRootAsParent().withName(QStringLiteral("43")).withTaskContent()); data.createCollection(GenCollection().withId(44).withRootAsParent().withName(QStringLiteral("44")).withNoteContent()); // Three children under each of the top level for each content type data.createCollection(GenCollection().withId(45).withParent(42).withName(QStringLiteral("45"))); data.createCollection(GenCollection().withId(46).withParent(42).withName(QStringLiteral("46")).withTaskContent()); data.createCollection(GenCollection().withId(47).withParent(42).withName(QStringLiteral("47")).withNoteContent()); data.createCollection(GenCollection().withId(48).withParent(43).withName(QStringLiteral("48"))); data.createCollection(GenCollection().withId(49).withParent(43).withName(QStringLiteral("49")).withTaskContent()); data.createCollection(GenCollection().withId(50).withParent(43).withName(QStringLiteral("50")).withNoteContent()); data.createCollection(GenCollection().withId(51).withParent(44).withName(QStringLiteral("51"))); data.createCollection(GenCollection().withId(52).withParent(44).withName(QStringLiteral("52")).withTaskContent()); data.createCollection(GenCollection().withId(53).withParent(44).withName(QStringLiteral("53")).withNoteContent()); // The list which will be filled by the fetch function auto collections = Akonadi::Collection::List(); auto add = [&collections] (const Akonadi::Collection &collection) { collections.append(collection); }; // WHEN QFETCH(Akonadi::Collection, root); QFETCH(Akonadi::StorageInterface::FetchContentTypes, contentTypes); auto fetch = helpers->fetchCollections(root, contentTypes); fetch(add); TestHelpers::waitForEmptyJobQueue(); auto result = QStringList(); std::transform(collections.constBegin(), collections.constEnd(), std::back_inserter(result), std::bind(&Akonadi::Collection::displayName, _1)); result.sort(); // THEN auto expected = QStringList(); if (root == Akonadi::Collection::root()) { expected << QStringLiteral("42") << QStringLiteral("43") << QStringLiteral("44"); } else { const qint64 baseId = root.id() == 42 ? 45 : root.id() == 43 ? 48 : root.id() == 44 ? 51 : -1; QVERIFY(baseId > 0); if (contentTypes == Akonadi::StorageInterface::AllContent) { expected << QString::number(baseId); } if ((contentTypes & Akonadi::StorageInterface::Tasks) || contentTypes == Akonadi::StorageInterface::AllContent) { expected << QString::number(baseId + 1); } if ((contentTypes & Akonadi::StorageInterface::Notes) || contentTypes == Akonadi::StorageInterface::AllContent) { expected << QString::number(baseId + 2); } } expected.sort(); QCOMPARE(result, expected); // WHEN (should not crash when the helpers object is deleted) helpers.clear(); collections.clear(); fetch(add); TestHelpers::waitForEmptyJobQueue(); // THEN result.clear(); std::transform(collections.constBegin(), collections.constEnd(), std::back_inserter(result), std::bind(&Akonadi::Collection::displayName, _1)); result.sort(); QCOMPARE(result, expected); } void shouldFetchItemsByContentTypes_data() { QTest::addColumn("contentTypes"); QTest::newRow("task collections only") << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Tasks); QTest::newRow("note collections only") << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Notes); QTest::newRow("task and note collections only") << (Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes); QTest::newRow("all collections") << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::AllContent); } void shouldFetchItemsByContentTypes() { // GIVEN auto data = AkonadiFakeData(); auto helpers = createHelpers(data); // Two top level collections, one with no particular content, one with tasks data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42"))); data.createCollection(GenCollection().withId(43).withRootAsParent().withName(QStringLiteral("43")).withTaskContent()); // One note collection as child of the first one data.createCollection(GenCollection().withId(44).withParent(42).withName(QStringLiteral("44")).withNoteContent()); // One task collection as child of the note collection data.createCollection(GenCollection().withId(45).withParent(44).withName(QStringLiteral("45")).withTaskContent()); // One task in the first collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); // Two items (tasks or notes) in all the other collections data.createItem(GenTodo().withId(43).withParent(43).withTitle(QStringLiteral("43"))); data.createItem(GenTodo().withId(44).withParent(43).withTitle(QStringLiteral("44"))); data.createItem(GenNote().withId(45).withParent(44).withTitle(QStringLiteral("45"))); data.createItem(GenNote().withId(46).withParent(44).withTitle(QStringLiteral("46"))); data.createItem(GenTodo().withId(47).withParent(45).withTitle(QStringLiteral("47"))); data.createItem(GenTodo().withId(48).withParent(45).withTitle(QStringLiteral("48"))); // The list which will be filled by the fetch function auto items = Akonadi::Item::List(); auto add = [&items] (const Akonadi::Item &item) { items.append(item); }; // WHEN QFETCH(Akonadi::StorageInterface::FetchContentTypes, contentTypes); auto fetch = helpers->fetchItems(contentTypes); fetch(add); TestHelpers::waitForEmptyJobQueue(); auto result = QStringList(); std::transform(items.constBegin(), items.constEnd(), std::back_inserter(result), titleFromItem); result.sort(); // THEN auto expected = QStringList(); if ((contentTypes & Akonadi::StorageInterface::Tasks) || contentTypes == Akonadi::StorageInterface::AllContent) { expected << QStringLiteral("43") << QStringLiteral("44") << QStringLiteral("47") << QStringLiteral("48"); } if ((contentTypes & Akonadi::StorageInterface::Notes) || contentTypes == Akonadi::StorageInterface::AllContent) { expected << QStringLiteral("45") << QStringLiteral("46"); } expected.sort(); QCOMPARE(result, expected); // WHEN (should not crash when the helpers object is deleted) helpers.clear(); items.clear(); fetch(add); TestHelpers::waitForEmptyJobQueue(); // THEN result.clear(); std::transform(items.constBegin(), items.constEnd(), std::back_inserter(result), titleFromItem); result.sort(); QCOMPARE(result, expected); } + void 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() { QTest::addColumn("tag"); QTest::newRow("first tag") << Akonadi::Tag(42); QTest::newRow("second tag") << Akonadi::Tag(43); } void shouldFetchItemsByTag() { // GIVEN auto data = AkonadiFakeData(); auto helpers = createHelpers(data); // Two top level collections with tasks data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42")).withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withName(QStringLiteral("43")).withTaskContent()); // Two tags data.createTag(GenTag().withId(42)); data.createTag(GenTag().withId(43)); // Four items in each collection, one with no tag, one with the first tag, // one with the second tag, last one with both tags data.createItem(GenTodo().withId(42).withParent(42).withTags({}).withTitle(QStringLiteral("42"))); data.createItem(GenTodo().withId(43).withParent(42).withTags({42}).withTitle(QStringLiteral("43"))); data.createItem(GenTodo().withId(44).withParent(42).withTags({43}).withTitle(QStringLiteral("44"))); data.createItem(GenTodo().withId(45).withParent(42).withTags({42, 43}).withTitle(QStringLiteral("45"))); data.createItem(GenTodo().withId(46).withParent(43).withTags({}).withTitle(QStringLiteral("46"))); data.createItem(GenTodo().withId(47).withParent(43).withTags({42}).withTitle(QStringLiteral("47"))); data.createItem(GenTodo().withId(48).withParent(43).withTags({43}).withTitle(QStringLiteral("48"))); data.createItem(GenTodo().withId(49).withParent(43).withTags({42, 43}).withTitle(QStringLiteral("49"))); // The list which will be filled by the fetch function auto items = Akonadi::Item::List(); auto add = [&items] (const Akonadi::Item &item) { items.append(item); }; // WHEN QFETCH(Akonadi::Tag, tag); auto fetch = helpers->fetchItems(tag); fetch(add); TestHelpers::waitForEmptyJobQueue(); auto result = QStringList(); std::transform(items.constBegin(), items.constEnd(), std::back_inserter(result), titleFromItem); result.sort(); // THEN auto expected = QStringList(); switch (tag.id()) { case 42: expected << QStringLiteral("43") << QStringLiteral("45") << QStringLiteral("47") << QStringLiteral("49"); break; case 43: expected << QStringLiteral("44") << QStringLiteral("45") << QStringLiteral("48") << QStringLiteral("49"); break; } QVERIFY(!expected.isEmpty()); expected.sort(); QCOMPARE(result, expected); // WHEN (should not crash when the helpers object is deleted) helpers.clear(); items.clear(); fetch(add); TestHelpers::waitForEmptyJobQueue(); // THEN result.clear(); std::transform(items.constBegin(), items.constEnd(), std::back_inserter(result), titleFromItem); result.sort(); QCOMPARE(result, expected); } void shouldFetchSiblings_data() { QTest::addColumn("item"); QTest::newRow("item in first collection") << Akonadi::Item(43); QTest::newRow("item in second collection") << Akonadi::Item(48); } void shouldFetchSiblings() { // GIVEN auto data = AkonadiFakeData(); auto helpers = createHelpers(data); // Two top level collections (one with notes, one with tasks) data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42")).withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withName(QStringLiteral("43")).withNoteContent()); // Four items in each collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43"))); data.createItem(GenTodo().withId(44).withParent(42).withTitle(QStringLiteral("44"))); data.createItem(GenTodo().withId(45).withParent(42).withTitle(QStringLiteral("45"))); data.createItem(GenNote().withId(46).withParent(43).withTitle(QStringLiteral("46"))); data.createItem(GenNote().withId(47).withParent(43).withTitle(QStringLiteral("47"))); data.createItem(GenNote().withId(48).withParent(43).withTitle(QStringLiteral("48"))); data.createItem(GenNote().withId(49).withParent(43).withTitle(QStringLiteral("49"))); // The list which will be filled by the fetch function auto items = Akonadi::Item::List(); auto add = [&items] (const Akonadi::Item &item) { items.append(item); }; // WHEN QFETCH(Akonadi::Item, item); auto fetch = helpers->fetchSiblings(item); fetch(add); TestHelpers::waitForEmptyJobQueue(); auto result = QStringList(); std::transform(items.constBegin(), items.constEnd(), std::back_inserter(result), titleFromItem); result.sort(); // THEN auto expected = QStringList(); switch (item.id()) { case 43: expected << QStringLiteral("42") << QStringLiteral("43") << QStringLiteral("44") << QStringLiteral("45"); break; case 48: expected << QStringLiteral("46") << QStringLiteral("47") << QStringLiteral("48") << QStringLiteral("49"); break; } QVERIFY(!expected.isEmpty()); expected.sort(); QCOMPARE(result, expected); // WHEN (should not crash when the helpers object is deleted) helpers.clear(); items.clear(); fetch(add); TestHelpers::waitForEmptyJobQueue(); // THEN result.clear(); std::transform(items.constBegin(), items.constEnd(), std::back_inserter(result), titleFromItem); result.sort(); QCOMPARE(result, expected); } void shouldFetchTags() { // GIVEN auto data = AkonadiFakeData(); auto helpers = createHelpers(data); // Two tags (one plain, one context) data.createTag(GenTag().withId(42).withName(QStringLiteral("42")).asPlain()); data.createTag(GenTag().withId(43).withName(QStringLiteral("43")).asContext()); // The list which will be filled by the fetch function auto tags = Akonadi::Tag::List(); auto add = [&tags] (const Akonadi::Tag &tag) { tags.append(tag); }; // WHEN auto fetch = helpers->fetchTags(); fetch(add); TestHelpers::waitForEmptyJobQueue(); auto result = QStringList(); std::transform(tags.constBegin(), tags.constEnd(), std::back_inserter(result), std::bind(&Akonadi::Tag::name, _1)); result.sort(); // THEN auto expected = QStringList({"42", "43"}); expected.sort(); QCOMPARE(result, expected); // WHEN (should not crash when the helpers object is deleted) helpers.clear(); tags.clear(); fetch(add); TestHelpers::waitForEmptyJobQueue(); // THEN result.clear(); std::transform(tags.constBegin(), tags.constEnd(), std::back_inserter(result), std::bind(&Akonadi::Tag::name, _1)); result.sort(); QCOMPARE(result, expected); } }; ZANSHIN_TEST_MAIN(AkonadiLiveQueryHelpersTest) #include "akonadilivequeryhelperstest.moc"