diff --git a/src/akonadi/akonaditaskqueries.cpp b/src/akonadi/akonaditaskqueries.cpp --- a/src/akonadi/akonaditaskqueries.cpp +++ b/src/akonadi/akonaditaskqueries.cpp @@ -108,9 +108,31 @@ TaskQueries::TaskResult::Ptr TaskQueries::findTopLevel() const { + Q_ASSERT(m_cache); auto fetch = m_helpers->fetchItems(); auto predicate = [this] (const Akonadi::Item &item) { - return m_serializer->relatedUidFromItem(item).isEmpty() && m_serializer->isTaskItem(item); + // Tasks with no parent, or whose parent is a project (not a task) + if (!m_serializer->isTaskItem(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->isTaskItem(*parent)) + return false; + + currentItem = *parent; + parentUid = m_serializer->relatedUidFromItem(currentItem); + } + return true; }; m_integrator->bind("TaskQueries::findTopLevel", m_findTopLevel, fetch, predicate); return m_findTopLevel->result(); @@ -120,10 +142,8 @@ { auto fetch = m_helpers->fetchItems(); auto predicate = [this] (const Akonadi::Item &item) { - const bool excluded = !m_serializer->isTaskItem(item) - || !m_serializer->relatedUidFromItem(item).isEmpty(); - - return !excluded; + // Tasks without a parent (neither task nor project) + return m_serializer->isTaskItem(item) && m_serializer->relatedUidFromItem(item).isEmpty(); }; m_integrator->bind("TaskQueries::findInboxTopLevel", m_findInboxTopLevel, fetch, predicate); return m_findInboxTopLevel->result(); diff --git a/tests/units/akonadi/akonaditaskqueriestest.cpp b/tests/units/akonadi/akonaditaskqueriestest.cpp --- a/tests/units/akonadi/akonaditaskqueriestest.cpp +++ b/tests/units/akonadi/akonaditaskqueriestest.cpp @@ -540,18 +540,19 @@ // 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"))); + // Two toplevel tasks in the second collection, one with a child + data.createItem(GenTodo().withId(43).withUid("parent-43").withParent(43).withTitle(QStringLiteral("43"))); + data.createItem(GenTodo().withId(44).withParent(43).withTitle(QStringLiteral("44")).withParentUid(QStringLiteral("2"))); // non-existing parent + data.createItem(GenTodo().withId(45).withParent(43).withTitle(QStringLiteral("45")).withParentUid(QStringLiteral("parent-43"))); // existing parent // 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(); + auto result = queries->findInboxTopLevel(); result->data(); - result = queries->findTopLevel(); // Should not cause any problem or wrong data + result = queries->findInboxTopLevel(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); @@ -574,7 +575,7 @@ Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); - auto result = queries->findTopLevel(); + auto result = queries->findInboxTopLevel(); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); @@ -608,7 +609,7 @@ Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); - auto result = queries->findTopLevel(); + auto result = queries->findInboxTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); @@ -641,7 +642,7 @@ Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); - auto result = queries->findTopLevel(); + auto result = queries->findInboxTopLevel(); // Even though the pointer didn't change it's convenient to user if we call // the replace handlers bool replaceHandlerCalled = false; @@ -682,7 +683,7 @@ Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); - auto result = queries->findTopLevel(); + auto result = queries->findInboxTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); @@ -716,7 +717,7 @@ Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); - auto result = queries->findTopLevel(); + auto result = queries->findInboxTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); @@ -750,7 +751,7 @@ Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); - auto result = queries->findTopLevel(); + auto result = queries->findInboxTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); @@ -1014,7 +1015,7 @@ // WHEN - auto result = queries->findTopLevel(); + auto result = queries->findInboxTopLevel(); if (deleteQuery) delete queries.take(); @@ -1052,9 +1053,9 @@ Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); - auto result = queries->findTopLevel(); + auto result = queries->findInboxTopLevel(); result->data(); - result = queries->findTopLevel(); // Should not cause any problem or wrong data + result = queries->findInboxTopLevel(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); @@ -1807,6 +1808,47 @@ QCOMPARE(result->data().size(), 0); } + void shouldOnlyReturnTopLevelTasks() + { + // GIVEN + AkonadiFakeData data; + + // One collection + data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); + + // One empty project + data.createItem(GenTodo().withId(1).asProject().withParent(42) + .withTitle(QStringLiteral("Empty Project")).withUid(QStringLiteral("project-1"))); + // One real project + data.createItem(GenTodo().withId(2).asProject().withParent(42) + .withTitle(QStringLiteral("Real Project")).withUid(QStringLiteral("project-2"))); + + // Two toplevel tasks in the second collection, one with a child + data.createItem(GenTodo().withId(3).withParent(42).withTitle(QStringLiteral("43")).withUid("parent-43").withParentUid("project-2")); + data.createItem(GenTodo().withId(4).withParent(42).withTitle(QStringLiteral("44")).withParentUid(QStringLiteral("2"))); // non-existing parent + data.createItem(GenTodo().withId(5).withParent(42).withTitle(QStringLiteral("45")).withParentUid(QStringLiteral("parent-43"))); // existing parent + + // 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 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("43")); + QCOMPARE(result->data().at(1)->title(), QStringLiteral("44")); + } + }; ZANSHIN_TEST_MAIN(AkonadiTaskQueriesTest)