diff --git a/src/akonadi/akonadidatasourcequeries.cpp b/src/akonadi/akonadidatasourcequeries.cpp index 3bd819cd..02c203f4 100644 --- a/src/akonadi/akonadidatasourcequeries.cpp +++ b/src/akonadi/akonadidatasourcequeries.cpp @@ -1,90 +1,103 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "akonadidatasourcequeries.h" #include "akonadistoragesettings.h" using namespace Akonadi; DataSourceQueries::DataSourceQueries(StorageInterface::FetchContentTypes contentTypes, const StorageInterface::Ptr &storage, const SerializerInterface::Ptr &serializer, const MonitorInterface::Ptr &monitor) : m_contentTypes(contentTypes), m_serializer(serializer), m_helpers(new LiveQueryHelpers(serializer, storage)), m_integrator(new LiveQueryIntegrator(serializer, monitor)) { m_integrator->addRemoveHandler([this] (const Collection &collection) { m_findChildren.remove(collection.id()); }); } bool DataSourceQueries::isDefaultSource(Domain::DataSource::Ptr source) const { auto sourceCollection = m_serializer->createCollectionFromDataSource(source); if (m_contentTypes == StorageInterface::Tasks) return sourceCollection == StorageSettings::instance().defaultTaskCollection(); else if (m_contentTypes == StorageInterface::Notes) return sourceCollection == StorageSettings::instance().defaultNoteCollection(); else return false; } void DataSourceQueries::changeDefaultSource(Domain::DataSource::Ptr source) { auto sourceCollection = m_serializer->createCollectionFromDataSource(source); if (m_contentTypes == StorageInterface::Tasks) { StorageSettings::instance().setDefaultTaskCollection(sourceCollection); } else if (m_contentTypes == StorageInterface::Notes) { StorageSettings::instance().setDefaultNoteCollection(sourceCollection); } } DataSourceQueries::DataSourceResult::Ptr DataSourceQueries::findTopLevel() const { auto fetch = m_helpers->fetchCollections(Collection::root(), m_contentTypes); auto predicate = createFetchPredicate(Collection::root()); m_integrator->bind("DataSourceQueries::findTopLevel", m_findTopLevel, fetch, predicate); return m_findTopLevel->result(); } DataSourceQueries::DataSourceResult::Ptr DataSourceQueries::findChildren(Domain::DataSource::Ptr source) const { Collection root = m_serializer->createCollectionFromDataSource(source); auto &query = m_findChildren[root.id()]; auto fetch = m_helpers->fetchCollections(root, m_contentTypes); auto predicate = createFetchPredicate(root); m_integrator->bind("DataSourceQueries::findChildren", query, fetch, predicate); return query->result(); } +DataSourceQueries::ProjectResult::Ptr DataSourceQueries::findProjects(Domain::DataSource::Ptr source) const +{ + Collection root = m_serializer->createCollectionFromDataSource(source); + auto &query = m_findProjects[root.id()]; + auto fetch = m_helpers->fetchItems(root); + auto predicate = [this, root] (const Akonadi::Item &item) { + return root == item.parentCollection() + && m_serializer->isProjectItem(item); + }; + m_integrator->bind("DataSourceQueries::findProjects", query, fetch, predicate); + return query->result(); +} + DataSourceQueries::CollectionInputQuery::PredicateFunction DataSourceQueries::createFetchPredicate(const Collection &root) const { return [this, root] (const Collection &collection) { return collection.isValid() && collection.parentCollection() == root; }; } diff --git a/src/akonadi/akonadidatasourcequeries.h b/src/akonadi/akonadidatasourcequeries.h index 96d53b5a..399cca53 100644 --- a/src/akonadi/akonadidatasourcequeries.h +++ b/src/akonadi/akonadidatasourcequeries.h @@ -1,71 +1,78 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_DATASOURCEQUERIES_H #define AKONADI_DATASOURCEQUERIES_H #include "domain/datasourcequeries.h" #include "akonadi/akonadilivequeryhelpers.h" #include "akonadi/akonadilivequeryintegrator.h" namespace Akonadi { class DataSourceQueries : public Domain::DataSourceQueries { public: typedef QSharedPointer Ptr; typedef Domain::LiveQueryInput CollectionInputQuery; typedef Domain::LiveQueryOutput DataSourceQueryOutput; typedef Domain::QueryResultProvider DataSourceProvider; typedef Domain::QueryResult DataSourceResult; + typedef Domain::LiveQueryInput ItemInputQuery; + typedef Domain::LiveQueryOutput ProjectQueryOutput; + typedef Domain::QueryResultProvider ProjectProvider; + typedef Domain::QueryResult ProjectResult; + DataSourceQueries(StorageInterface::FetchContentTypes contentTypes, const StorageInterface::Ptr &storage, const SerializerInterface::Ptr &serializer, const MonitorInterface::Ptr &monitor); bool isDefaultSource(Domain::DataSource::Ptr source) const Q_DECL_OVERRIDE; private: void changeDefaultSource(Domain::DataSource::Ptr source) Q_DECL_OVERRIDE; public: DataSourceResult::Ptr findTopLevel() const Q_DECL_OVERRIDE; DataSourceResult::Ptr findChildren(Domain::DataSource::Ptr source) const Q_DECL_OVERRIDE; + ProjectResult::Ptr findProjects(Domain::DataSource::Ptr source) const Q_DECL_OVERRIDE; private: CollectionInputQuery::PredicateFunction createFetchPredicate(const Collection &root) const; StorageInterface::FetchContentTypes m_contentTypes; SerializerInterface::Ptr m_serializer; LiveQueryHelpers::Ptr m_helpers; LiveQueryIntegrator::Ptr m_integrator; mutable DataSourceQueryOutput::Ptr m_findTopLevel; mutable QHash m_findChildren; + mutable QHash m_findProjects; }; } #endif // AKONADI_DATASOURCEQUERIES_H diff --git a/src/domain/datasourcequeries.h b/src/domain/datasourcequeries.h index 64ac9490..1b0eefea 100644 --- a/src/domain/datasourcequeries.h +++ b/src/domain/datasourcequeries.h @@ -1,74 +1,76 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef DOMAIN_DATASOURCEQUERIES_H #define DOMAIN_DATASOURCEQUERIES_H #include #include "datasource.h" +#include "project.h" #include "queryresult.h" namespace Domain { class DataSourceQueries; class DataSourceQueriesNotifier : public QObject { Q_OBJECT signals: void defaultSourceChanged(); private: friend class DataSourceQueries; }; class DataSourceQueries { public: typedef QSharedPointer Ptr; DataSourceQueries(); virtual ~DataSourceQueries(); DataSourceQueriesNotifier *notifier() const; virtual bool isDefaultSource(DataSource::Ptr source) const = 0; void setDefaultSource(DataSource::Ptr source); // HACK: Ugly right? Find me another way to mock changeDefaultSource then... #ifdef ZANSHIN_I_SWEAR_I_AM_IN_A_PRESENTATION_TEST public: #else private: #endif virtual void changeDefaultSource(DataSource::Ptr source) = 0; public: virtual QueryResult::Ptr findTopLevel() const = 0; virtual QueryResult::Ptr findChildren(DataSource::Ptr source) const = 0; + virtual QueryResult::Ptr findProjects(DataSource::Ptr source) const = 0; }; } #endif // DOMAIN_DATASOURCEQUERIES_H diff --git a/tests/units/akonadi/akonadidatasourcequeriestest.cpp b/tests/units/akonadi/akonadidatasourcequeriestest.cpp index 1a069c49..a5be4103 100644 --- a/tests/units/akonadi/akonadidatasourcequeriestest.cpp +++ b/tests/units/akonadi/akonadidatasourcequeriestest.cpp @@ -1,592 +1,772 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "akonadi/akonadidatasourcequeries.h" #include "akonadi/akonadiserializer.h" #include "akonadi/akonadistoragesettings.h" #include "utils/jobhandler.h" #include "utils/mem_fn.h" #include "testlib/akonadifakedata.h" #include "testlib/gencollection.h" +#include "testlib/gentodo.h" #include "testlib/testhelpers.h" using namespace Testlib; typedef std::function::Ptr(Domain::DataSourceQueries*)> QueryFunction; Q_DECLARE_METATYPE(QueryFunction) typedef std::function SetDefaultCollectionFunction; Q_DECLARE_METATYPE(SetDefaultCollectionFunction) typedef std::function GetDefaultCollectionFunction; Q_DECLARE_METATYPE(GetDefaultCollectionFunction) Q_DECLARE_METATYPE(Akonadi::StorageInterface::FetchContentType) class AkonadiDataSourceQueriesTest : public QObject { Q_OBJECT public: explicit AkonadiDataSourceQueriesTest(QObject *parent = Q_NULLPTR) : QObject(parent) { qRegisterMetaType(); } private slots: void shouldCheckIfASourceIsDefaultFromSettings_data() { QTest::addColumn("contentType"); QTest::addColumn("setDefaultCollection"); QTest::addColumn("getDefaultCollection"); { SetDefaultCollectionFunction setFunction = Utils::mem_fn(&Akonadi::StorageSettings::setDefaultNoteCollection); GetDefaultCollectionFunction getFunction = Utils::mem_fn(&Akonadi::StorageSettings::defaultNoteCollection); QTest::newRow("notes") << Akonadi::StorageInterface::Notes << setFunction << getFunction; } { SetDefaultCollectionFunction setFunction = Utils::mem_fn(&Akonadi::StorageSettings::setDefaultTaskCollection); GetDefaultCollectionFunction getFunction = Utils::mem_fn(&Akonadi::StorageSettings::defaultTaskCollection); QTest::newRow("tasks") << Akonadi::StorageInterface::Tasks << setFunction << getFunction; } } void shouldCheckIfASourceIsDefaultFromSettings() { // GIVEN const auto minId = qint64(42); const auto maxId = qint64(44); const auto defaultId = qint64(43); QFETCH(Akonadi::StorageInterface::FetchContentType, contentType); QFETCH(SetDefaultCollectionFunction, setDefaultCollection); // A default collection for saving setDefaultCollection(&Akonadi::StorageSettings::instance(), Akonadi::Collection(defaultId)); // A few data sources AkonadiFakeData data; for (auto id = minId; id <= maxId; ++id) { auto col = GenCollection().withId(id).withName(QString::number(id)).withRootAsParent(); if (contentType == Akonadi::StorageInterface::Tasks) data.createCollection(col.withTaskContent()); else data.createCollection(col.withTaskContent()); } // WHEN auto serializer = Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer); QScopedPointer queries(new Akonadi::DataSourceQueries(contentType, Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); // THEN for (auto id = minId; id <= maxId; ++id) { auto source = serializer->createDataSourceFromCollection(data.collection(id), Akonadi::SerializerInterface::BaseName); QCOMPARE(queries->isDefaultSource(source), id == defaultId); } } void shouldStoreDefaultSourceInTheSettingsAndNotify_data() { shouldCheckIfASourceIsDefaultFromSettings_data(); } void shouldStoreDefaultSourceInTheSettingsAndNotify() { // GIVEN const auto minId = qint64(42); const auto maxId = qint64(44); const auto defaultId = qint64(43); QFETCH(Akonadi::StorageInterface::FetchContentType, contentType); QFETCH(SetDefaultCollectionFunction, setDefaultCollection); // A default collection for saving setDefaultCollection(&Akonadi::StorageSettings::instance(), Akonadi::Collection(minId)); // A few data sources AkonadiFakeData data; for (auto id = minId; id <= maxId; ++id) { auto col = GenCollection().withId(id).withName(QString::number(id)).withRootAsParent(); if (contentType == Akonadi::StorageInterface::Tasks) data.createCollection(col.withTaskContent()); else data.createCollection(col.withTaskContent()); } // WHEN auto serializer = Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer); QScopedPointer queries(new Akonadi::DataSourceQueries(contentType, Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); QSignalSpy spy(queries->notifier(), &Domain::DataSourceQueriesNotifier::defaultSourceChanged); auto defaultSource = serializer->createDataSourceFromCollection(data.collection(defaultId), Akonadi::SerializerInterface::BaseName); queries->setDefaultSource(defaultSource); // THEN QFETCH(GetDefaultCollectionFunction, getDefaultCollection); QCOMPARE(getDefaultCollection(&Akonadi::StorageSettings::instance()).id(), defaultId); QCOMPARE(spy.count(), 1); } void shouldLookInAllReportedForTopLevelSources_data() { QTest::addColumn("contentTypes"); QTest::addColumn("expectedNames"); auto expectedNames = QStringList(); expectedNames << QStringLiteral("42Task") << QStringLiteral("44Note"); QTest::newRow("tasks and notes") << int(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes) << expectedNames; expectedNames.clear(); expectedNames << QStringLiteral("42Task"); QTest::newRow("tasks") << int(Akonadi::StorageInterface::Tasks) << expectedNames; expectedNames.clear(); expectedNames << QStringLiteral("44Note"); QTest::newRow("notes") << int(Akonadi::StorageInterface::Notes) << expectedNames; } void shouldLookInAllReportedForTopLevelSources() { // GIVEN AkonadiFakeData data; // Two top level collections, one with tasks, one with notes data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44Note")).withRootAsParent().withNoteContent()); // One with a note child collection data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43TaskChild")).withParent(42).withTaskContent()); // One with a task child collection data.createCollection(GenCollection().withId(45).withName(QStringLiteral("45NoteChild")).withParent(44).withNoteContent()); // WHEN QFETCH(int, contentTypes); QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::FetchContentTypes(contentTypes), Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult::Ptr result = queries->findTopLevel(); result->data(); result = queries->findTopLevel(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); const auto sources = result->data(); auto actualNames = QStringList(); std::transform(sources.constBegin(), sources.constEnd(), std::back_inserter(actualNames), [] (const Domain::DataSource::Ptr &source) { return source->name(); }); actualNames.sort(); QFETCH(QStringList, expectedNames); expectedNames.sort(); QCOMPARE(actualNames, expectedNames); } void shouldReactToCollectionAddsForTopLevelSources() { // GIVEN AkonadiFakeData data; QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult::Ptr result = queries->findTopLevel(); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); // WHEN data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Note")).withRootAsParent().withNoteContent()); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42Task")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43Note")); } void shouldReactToCollectionRemovesForTopLevelSources() { // GIVEN AkonadiFakeData data; // Two top level collections and two child collections data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Note")).withRootAsParent().withNoteContent()); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("43TaskChild")).withParent(42).withTaskContent()); data.createCollection(GenCollection().withId(45).withName(QStringLiteral("43NoteChild")).withParent(43).withNoteContent()); QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult::Ptr result = queries->findTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.removeCollection(Akonadi::Collection(42)); data.removeCollection(Akonadi::Collection(43)); // THEN QCOMPARE(result->data().size(), 0); } void shouldReactToItemChangesForTopLevelTasks() { // GIVEN AkonadiFakeData data; // Two top level collections and one child collection data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Note")).withRootAsParent().withNoteContent()); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44NoteChild")).withParent(43).withNoteContent()); QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult::Ptr result = queries->findTopLevel(); bool replaceHandlerCalled = false; result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::DataSource::Ptr &, int) { replaceHandlerCalled = true; }); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.modifyCollection(GenCollection(data.collection(42)).withName(QStringLiteral("42TaskBis"))); data.modifyCollection(GenCollection(data.collection(43)).withName(QStringLiteral("43NoteBis"))); TestHelpers::waitForEmptyJobQueue(); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42TaskBis")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43NoteBis")); QVERIFY(replaceHandlerCalled); } void shouldNotCrashDuringFindTopLevelWhenFetchJobFailedOrEmpty_data() { QTest::addColumn("colErrorCode"); QTest::addColumn("colFetchBehavior"); QTest::addColumn("deleteQuery"); QTest::newRow("No error with empty collection list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("No error with empty collection list (+ query delete)") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << true; QTest::newRow("Error with empty collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("Error with empty collection list (+ query delete)") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << true; QTest::newRow("Error with collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << false; QTest::newRow("Error with collection list (+ query delete)") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << true; } void shouldNotCrashDuringFindTopLevelWhenFetchJobFailedOrEmpty() { // GIVEN AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withNoteContent()); QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); QFETCH(int, colErrorCode); QFETCH(int, colFetchBehavior); data.storageBehavior().setFetchCollectionsErrorCode(Akonadi::Collection::root().id(), colErrorCode); data.storageBehavior().setFetchCollectionsBehavior(Akonadi::Collection::root().id(), AkonadiFakeStorageBehavior::FetchBehavior(colFetchBehavior)); QFETCH(bool, deleteQuery); // WHEN Domain::QueryResult::Ptr result = queries->findTopLevel(); if (deleteQuery) delete queries.take(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 0); } void shouldLookInAllReportedForChildSources_data() { QTest::addColumn("contentTypes"); QTest::addColumn("expectedNames"); auto expectedNames = QStringList(); expectedNames << QStringLiteral("43TaskFirstChild") << QStringLiteral("45NoteSecondChild"); QTest::newRow("tasks and notes") << int(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes) << expectedNames; expectedNames.clear(); expectedNames << QStringLiteral("43TaskFirstChild"); QTest::newRow("tasks") << int(Akonadi::StorageInterface::Tasks) << expectedNames; expectedNames.clear(); expectedNames << QStringLiteral("45NoteSecondChild"); QTest::newRow("notes") << int(Akonadi::StorageInterface::Notes) << expectedNames; } void shouldLookInAllReportedForChildSources() { // GIVEN AkonadiFakeData data; // One top level collection with two children (one of them also having a child) data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43TaskFirstChild")).withParent(42).withTaskContent()); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44TaskFirstChildChild")).withParent(43).withTaskContent()); data.createCollection(GenCollection().withId(45).withName(QStringLiteral("45NoteSecondChild")).withParent(42).withNoteContent()); // Serializer auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); Domain::DataSource::Ptr topLevelDataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); // WHEN QFETCH(int, contentTypes); QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::FetchContentTypes(contentTypes), Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult::Ptr result = queries->findChildren(topLevelDataSource); result->data(); result = queries->findChildren(topLevelDataSource); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); const auto sources = result->data(); auto actualNames = QStringList(); std::transform(sources.constBegin(), sources.constEnd(), std::back_inserter(actualNames), [] (const Domain::DataSource::Ptr &source) { return source->name(); }); actualNames.sort(); QFETCH(QStringList, expectedNames); expectedNames.sort(); QCOMPARE(actualNames, expectedNames); } void shouldReactToCollectionAddsForChildSources() { // GIVEN AkonadiFakeData data; // One top level collection with no child yet data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); // Serializer auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); Domain::DataSource::Ptr topLevelDataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult::Ptr result = queries->findChildren(topLevelDataSource); result->data(); result = queries->findChildren(topLevelDataSource); // Should not cause any problem or wrong data TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 0); // WHEN data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43TaskChild")).withParent(42).withTaskContent()); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().first()->name(), QStringLiteral("43TaskChild")); } void shouldReactToCollectionRemovesForChildSources() { // GIVEN AkonadiFakeData data; // One top level collection with two children data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43TaskFirstChild")).withParent(42).withTaskContent()); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("45NoteSecondChild")).withParent(42).withNoteContent()); // Serializer auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); Domain::DataSource::Ptr topLevelDataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult::Ptr result = queries->findChildren(topLevelDataSource); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.removeCollection(Akonadi::Collection(44)); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().first()->name(), QStringLiteral("43TaskFirstChild")); } void shouldReactToCollectionChangesForChildSources() { // GIVEN AkonadiFakeData data; // One top level collection with two children data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43TaskFirstChild")).withParent(42).withTaskContent()); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44NoteSecondChild")).withParent(42).withNoteContent()); // Serializer auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); Domain::DataSource::Ptr topLevelDataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult::Ptr result = queries->findChildren(topLevelDataSource); bool replaceHandlerCalled = false; result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::DataSource::Ptr &, int) { replaceHandlerCalled = true; }); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.modifyCollection(GenCollection(data.collection(43)).withName(QStringLiteral("43TaskFirstChildBis"))); // THEN TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().first()->name(), QStringLiteral("43TaskFirstChildBis")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("44NoteSecondChild")); QVERIFY(replaceHandlerCalled); } void shouldNotCrashDuringFindChildrenWhenFetchJobFailedOrEmpty_data() { QTest::addColumn("colErrorCode"); QTest::addColumn("colFetchBehavior"); QTest::addColumn("deleteQuery"); QTest::newRow("No error with empty collection list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("No error with empty collection list (+ query delete)") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << true; QTest::newRow("Error with empty collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("Error with empty collection list (+ query delete)") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << true; QTest::newRow("Error with collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << false; QTest::newRow("Error with collection list (+ query delete)") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << true; } void shouldNotCrashDuringFindChildrenWhenFetchJobFailedOrEmpty() { // GIVEN AkonadiFakeData data; // One top level collection with two children data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43TaskFirstChild")).withParent(42).withTaskContent()); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44NoteSecondChild")).withParent(42).withNoteContent()); // Serializer auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); Domain::DataSource::Ptr topLevelDataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); QFETCH(bool, deleteQuery); QFETCH(int, colErrorCode); QFETCH(int, colFetchBehavior); data.storageBehavior().setFetchCollectionsErrorCode(data.collection(42).id(), colErrorCode); data.storageBehavior().setFetchCollectionsBehavior(data.collection(42).id(), AkonadiFakeStorageBehavior::FetchBehavior(colFetchBehavior)); // WHEN QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult::Ptr result = queries->findChildren(topLevelDataSource); if (deleteQuery) delete queries.take(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 0); } + + void shouldLookInCollectionForProjects() + { + // GIVEN + AkonadiFakeData data; + + // Two top level collections + data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); + data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); + + // One project in the first collection + data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).asProject()); + + // Two projects in the second collection + data.createItem(GenTodo().withId(43).withParent(43).withTitle(QStringLiteral("43")).asProject()); + data.createItem(GenTodo().withId(44).withParent(43).withTitle(QStringLiteral("44")).asProject()); + + // WHEN + auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); + QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, + Akonadi::StorageInterface::Ptr(data.createStorage()), + serializer, + Akonadi::MonitorInterface::Ptr(data.createMonitor()))); + Domain::DataSource::Ptr dataSource1 = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); + auto result1 = queries->findProjects(dataSource1); + result1->data(); + result1 = queries->findProjects(dataSource1); // Should not cause any problem or wrong data + + // THEN + QVERIFY(result1->data().isEmpty()); + TestHelpers::waitForEmptyJobQueue(); + + QCOMPARE(result1->data().size(), 1); + QCOMPARE(result1->data().at(0)->name(), QStringLiteral("42")); + + // WHEN + Domain::DataSource::Ptr dataSource2 = serializer->createDataSourceFromCollection(data.collection(43), Akonadi::SerializerInterface::BaseName); + auto result2 = queries->findProjects(dataSource2); + result2->data(); + result2 = queries->findProjects(dataSource2); // Should not cause any problem or wrong data + + // THEN + QVERIFY(result2->data().isEmpty()); + TestHelpers::waitForEmptyJobQueue(); + + QCOMPARE(result1->data().size(), 1); + QCOMPARE(result1->data().at(0)->name(), QStringLiteral("42")); + + QCOMPARE(result2->data().size(), 2); + QCOMPARE(result2->data().at(0)->name(), QStringLiteral("43")); + QCOMPARE(result2->data().at(1)->name(), QStringLiteral("44")); + } + + void shouldIgnoreItemsWhichAreNotProjects() + { + // GIVEN + AkonadiFakeData data; + + // One top level collection + data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); + + // One project and one regular task in the collection + data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).asProject()); + data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43"))); + + // WHEN + auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); + QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, + Akonadi::StorageInterface::Ptr(data.createStorage()), + serializer, + Akonadi::MonitorInterface::Ptr(data.createMonitor()))); + Domain::DataSource::Ptr dataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); + auto result = queries->findProjects(dataSource); + QVERIFY(result->data().isEmpty()); + TestHelpers::waitForEmptyJobQueue(); + + // THEN + QCOMPARE(result->data().size(), 1); + QCOMPARE(result->data().at(0)->name(), QStringLiteral("42")); + } + + void shouldReactToItemAddsForProjectsOnly() + { + // GIVEN + AkonadiFakeData data; + + // One empty collection + data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); + + auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); + QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, + Akonadi::StorageInterface::Ptr(data.createStorage()), + serializer, + Akonadi::MonitorInterface::Ptr(data.createMonitor()))); + Domain::DataSource::Ptr dataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); + auto result = queries->findProjects(dataSource); + TestHelpers::waitForEmptyJobQueue(); + QVERIFY(result->data().isEmpty()); + + // WHEN + data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).asProject()); + data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43"))); + + // THEN + QCOMPARE(result->data().size(), 1); + QCOMPARE(result->data().at(0)->name(), QStringLiteral("42")); + } + + void shouldReactToItemRemovesForProjects() + { + // GIVEN + AkonadiFakeData data; + + // One top level collection + data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); + + // Three projects in the collection + data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).asProject()); + data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43")).asProject()); + data.createItem(GenTodo().withId(44).withParent(42).withTitle(QStringLiteral("44")).asProject()); + + auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); + QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, + Akonadi::StorageInterface::Ptr(data.createStorage()), + serializer, + Akonadi::MonitorInterface::Ptr(data.createMonitor()))); + Domain::DataSource::Ptr dataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); + auto result = queries->findProjects(dataSource); + TestHelpers::waitForEmptyJobQueue(); + QCOMPARE(result->data().size(), 3); + + // WHEN + data.removeItem(Akonadi::Item(43)); + + // THEN + QCOMPARE(result->data().size(), 2); + QCOMPARE(result->data().at(0)->name(), QStringLiteral("42")); + QCOMPARE(result->data().at(1)->name(), QStringLiteral("44")); + } + + void shouldReactToItemChangesForProjects() + { + // GIVEN + AkonadiFakeData data; + + // One top level collection + data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); + + // Three projects in the collection + data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).asProject()); + data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43")).asProject()); + data.createItem(GenTodo().withId(44).withParent(42).withTitle(QStringLiteral("44")).asProject()); + + auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); + QScopedPointer queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, + Akonadi::StorageInterface::Ptr(data.createStorage()), + serializer, + Akonadi::MonitorInterface::Ptr(data.createMonitor()))); + Domain::DataSource::Ptr dataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); + auto result = queries->findProjects(dataSource); + // 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::Project::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)->name(), QStringLiteral("42")); + QCOMPARE(result->data().at(1)->name(), QStringLiteral("43bis")); + QCOMPARE(result->data().at(2)->name(), QStringLiteral("44")); + QVERIFY(replaceHandlerCalled); + } }; ZANSHIN_TEST_MAIN(AkonadiDataSourceQueriesTest) #include "akonadidatasourcequeriestest.moc"