diff --git a/CMakeLists.txt b/CMakeLists.txt index 0de24b51..9e590561 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,90 +1,89 @@ project(zanshin) cmake_minimum_required(VERSION 3.2) find_package(ECM REQUIRED CONFIG) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/ ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH}) include(GenerateExportHeader) include(KDEInstallDirs) include(KDECompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) include(FeatureSummary) include(ECMInstallIcons) include(ECMMarkAsTest) include(ECMPoQmTools) find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED Core Gui Widgets Qml Test) find_package(Boost REQUIRED) find_package(Threads REQUIRED) macro(assert_min_ver version) set(error_msg "${CMAKE_CXX_COMPILER} ${CMAKE_CXX_COMPILER_VERSION} not supported") if("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS "${version}") message(FATAL_ERROR "${msg}") endif() endmacro() if(APPLE) if((NOT "${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS "6.0.0.0") AND "${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS "6.0.0.6000058") # Apple Clang 6.0.0.6000057 is known to fail on some of our code using std::mem_fn # but have no issues with boost::mem_fn message("problematic Apple Clang version ${CMAKE_CXX_COMPILER_VERSION}, using boost::mem_fn") add_definitions(-DZANSHIN_USE_BOOST_MEM_FN) endif() endif() if(UNIX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") set(CMAKE_CXX_STANDARD 14) # Enable C++14, with cmake >= 3.1 set(CMAKE_CXX_EXTENSIONS OFF) # Don't enable gcc-specific extensions endif() kde_enable_exceptions() if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") endif() option(ZANSHIN_BUILD_COVERAGE "Build Zanshin with gcov support" OFF) if(ZANSHIN_BUILD_COVERAGE AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") endif() option(ZANSHIN_BUILD_ASAN "Build Zanshin with asan support" OFF) if(ZANSHIN_BUILD_ASAN) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls") link_libraries("asan") endif() add_definitions(-DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS -DQT_NO_URL_CAST_FROM_STRING) include_directories ( ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${Boost_INCLUDE_DIR} 3rdparty/kdepim/ 3rdparty/kdepim/libkdepim/ ) find_package(KF5 REQUIRED COMPONENTS AkonadiCalendar - AkonadiNotes AkonadiSearch IdentityManagement KontactInterface Ldap Runner Wallet WindowSystem I18n KDELibs4Support ) find_package(KF5Akonadi "5.1" CONFIG REQUIRED) add_subdirectory(3rdparty) add_subdirectory(src) if(BUILD_TESTING) add_subdirectory(tests) endif() diff --git a/src/akonadi/CMakeLists.txt b/src/akonadi/CMakeLists.txt index 002ebc5f..3e122c33 100644 --- a/src/akonadi/CMakeLists.txt +++ b/src/akonadi/CMakeLists.txt @@ -1,39 +1,38 @@ set(akonadi_SRCS akonadiapplicationselectedattribute.cpp akonadicache.cpp akonadicachingstorage.cpp akonadicollectionfetchjobinterface.cpp akonadiconfigdialog.cpp akonadicontextqueries.cpp akonadicontextrepository.cpp akonadidatasourcequeries.cpp akonadidatasourcerepository.cpp akonadiitemfetchjobinterface.cpp akonadilivequeryhelpers.cpp akonadilivequeryintegrator.cpp akonadimonitorimpl.cpp akonadimonitorinterface.cpp akonadiprojectqueries.cpp akonadiprojectrepository.cpp akonadiserializer.cpp akonadiserializerinterface.cpp akonadistorage.cpp akonadistorageinterface.cpp akonadistoragesettings.cpp akonaditagfetchjobinterface.cpp akonaditaskqueries.cpp akonaditaskrepository.cpp akonaditimestampattribute.cpp ) add_library(akonadi STATIC ${akonadi_SRCS}) target_link_libraries(akonadi KF5::AkonadiCalendar KF5::AkonadiCore KF5::AkonadiWidgets - KF5::Mime KF5::CalendarCore KF5::IdentityManagement KF5::ConfigCore KF5::KDELibs4Support ) diff --git a/src/akonadi/akonadiserializer.cpp b/src/akonadi/akonadiserializer.cpp index aadfa4bf..57fac470 100644 --- a/src/akonadi/akonadiserializer.cpp +++ b/src/akonadi/akonadiserializer.cpp @@ -1,521 +1,520 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens Copyright 2014 Rémi Benoit This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "akonadiserializer.h" #include #include #include #include -#include #include #include #include "utils/mem_fn.h" #include "akonadi/akonadiapplicationselectedattribute.h" #include "akonadi/akonaditimestampattribute.h" using namespace Akonadi; Serializer::Serializer() { } Serializer::~Serializer() { } bool Serializer::representsCollection(SerializerInterface::QObjectPtr object, Collection collection) { return object->property("collectionId").toLongLong() == collection.id(); } bool Serializer::representsItem(QObjectPtr object, Item item) { return object->property("itemId").toLongLong() == item.id(); } QString Serializer::itemUid(const Item &item) { if (item.hasPayload()) { const auto todo = item.payload(); return todo->uid(); } else { return QString(); } } Domain::DataSource::Ptr Serializer::createDataSourceFromCollection(Collection collection, DataSourceNameScheme naming) { if (!collection.isValid()) return Domain::DataSource::Ptr(); auto dataSource = Domain::DataSource::Ptr::create(); updateDataSourceFromCollection(dataSource, collection, naming); return dataSource; } void Serializer::updateDataSourceFromCollection(Domain::DataSource::Ptr dataSource, Collection collection, DataSourceNameScheme naming) { if (!collection.isValid()) return; QString name = collection.displayName(); if (naming == FullPath) { auto parent = collection.parentCollection(); while (parent.isValid() && parent != Akonadi::Collection::root()) { name = parent.displayName() + " » " + name; parent = parent.parentCollection(); } } dataSource->setName(name); const auto mimeTypes = collection.contentMimeTypes(); auto types = Domain::DataSource::ContentTypes(); if (mimeTypes.contains(KCalCore::Todo::todoMimeType())) types |= Domain::DataSource::Tasks; dataSource->setContentTypes(types); if (collection.hasAttribute()) { auto iconName = collection.attribute()->iconName(); dataSource->setIconName(iconName); } if (!collection.hasAttribute()) { dataSource->setSelected(true); } else { auto isSelected = collection.attribute()->isSelected(); dataSource->setSelected(isSelected); } dataSource->setProperty("collectionId", collection.id()); } Collection Serializer::createCollectionFromDataSource(Domain::DataSource::Ptr dataSource) { const auto id = dataSource->property("collectionId").value(); auto collection = Collection(id); collection.attribute(Akonadi::Collection::AddIfMissing); auto selectedAttribute = collection.attribute(Akonadi::Collection::AddIfMissing); selectedAttribute->setSelected(dataSource->isSelected()); return collection; } bool Serializer::isSelectedCollection(Collection collection) { if (!isTaskCollection(collection)) return false; if (!collection.hasAttribute()) return true; return collection.attribute()->isSelected(); } bool Akonadi::Serializer::isTaskCollection(Akonadi::Collection collection) { return collection.contentMimeTypes().contains(KCalCore::Todo::todoMimeType()); } bool Serializer::isTaskItem(Item item) { if (!item.hasPayload()) return false; auto todo = item.payload(); return todo->customProperty("Zanshin", "Project").isEmpty(); } Domain::Task::Ptr Serializer::createTaskFromItem(Item item) { if (!isTaskItem(item)) return Domain::Task::Ptr(); auto task = Domain::Task::Ptr::create(); updateTaskFromItem(task, item); return task; } void Serializer::updateTaskFromItem(Domain::Task::Ptr task, Item item) { if (!isTaskItem(item)) return; auto todo = item.payload(); task->setTitle(todo->summary()); task->setText(todo->description()); task->setDone(todo->isCompleted()); task->setDoneDate(todo->completed().toLocalTime().date()); task->setStartDate(todo->dtStart().toLocalTime().date()); task->setDueDate(todo->dtDue().toLocalTime().date()); task->setProperty("itemId", item.id()); task->setProperty("parentCollectionId", item.parentCollection().id()); task->setProperty("todoUid", todo->uid()); task->setProperty("relatedUid", todo->relatedTo()); task->setRunning(todo->customProperty("Zanshin", "Running") == QLatin1String("1")); switch (todo->recurrence()->recurrenceType()) { case KCalCore::Recurrence::rDaily: task->setRecurrence(Domain::Task::RecursDaily); break; case KCalCore::Recurrence::rWeekly: task->setRecurrence(Domain::Task::RecursWeekly); break; case KCalCore::Recurrence::rMonthlyDay: task->setRecurrence(Domain::Task::RecursMonthly); break; default: // Other cases are not supported for now and as such just ignored break; } QMimeDatabase mimeDb; const auto attachmentsInput = todo->attachments(); Domain::Task::Attachments attachments; attachments.reserve(attachmentsInput.size()); std::transform(attachmentsInput.cbegin(), attachmentsInput.cend(), std::back_inserter(attachments), [&mimeDb] (const KCalCore::Attachment::Ptr &attach) { Domain::Task::Attachment attachment; if (attach->isUri()) attachment.setUri(QUrl(attach->uri())); else attachment.setData(attach->decodedData()); attachment.setLabel(attach->label()); attachment.setMimeType(attach->mimeType()); attachment.setIconName(mimeDb.mimeTypeForName(attach->mimeType()).iconName()); return attachment; }); task->setAttachments(attachments); } bool Serializer::isTaskChild(Domain::Task::Ptr task, Akonadi::Item item) { if (!isTaskItem(item)) return false; auto todo = item.payload(); if (todo->relatedTo() == task->property("todoUid")) return true; return false; } Akonadi::Item Serializer::createItemFromTask(Domain::Task::Ptr task) { auto todo = KCalCore::Todo::Ptr::create(); todo->setSummary(task->title()); todo->setDescription(task->text()); // We only support all-day todos, so ignore timezone information and possible effect from timezone on dates // KCalCore reads "DUE;VALUE=DATE:20171130" as QDateTime(QDate(2017, 11, 30), QTime(), Qt::LocalTime), for lack of timezone information // so we should never call toUtc() on that, it would mess up the date. // If one day we want to support time information, we need to add a task->isAllDay()/setAllDay(). todo->setDtStart(QDateTime(task->startDate())); todo->setDtDue(QDateTime(task->dueDate())); todo->setAllDay(true); if (task->property("todoUid").isValid()) { todo->setUid(task->property("todoUid").toString()); } if (task->property("relatedUid").isValid()) { todo->setRelatedTo(task->property("relatedUid").toString()); } switch (task->recurrence()) { case Domain::Task::NoRecurrence: break; case Domain::Task::RecursDaily: todo->recurrence()->setDaily(1); break; case Domain::Task::RecursWeekly: todo->recurrence()->setWeekly(1); break; case Domain::Task::RecursMonthly: todo->recurrence()->setMonthly(1); break; } for (const auto &attachment : task->attachments()) { KCalCore::Attachment::Ptr attach(new KCalCore::Attachment(QByteArray())); if (attachment.isUri()) attach->setUri(attachment.uri().toString()); else attach->setDecodedData(attachment.data()); attach->setMimeType(attachment.mimeType()); attach->setLabel(attachment.label()); todo->addAttachment(attach); } if (task->isRunning()) { todo->setCustomProperty("Zanshin", "Running", "1"); } else { todo->removeCustomProperty("Zanshin", "Running"); } // Needs to be done after all other dates are positioned // since this applies the recurrence logic if (task->isDone()) todo->setCompleted(QDateTime(task->doneDate(), QTime(), Qt::UTC)); else todo->setCompleted(false); Akonadi::Item item; if (task->property("itemId").isValid()) { item.setId(task->property("itemId").value()); } if (task->property("parentCollectionId").isValid()) { auto parentId = task->property("parentCollectionId").value(); item.setParentCollection(Akonadi::Collection(parentId)); } item.setMimeType(KCalCore::Todo::todoMimeType()); item.setPayload(todo); return item; } QString Serializer::relatedUidFromItem(Akonadi::Item item) { if (isTaskItem(item)) { const auto todo = item.payload(); return todo->relatedTo(); } else { return QString(); } } void Serializer::updateItemParent(Akonadi::Item item, Domain::Task::Ptr parent) { if (!isTaskItem(item)) return; auto todo = item.payload(); todo->setRelatedTo(parent->property("todoUid").toString()); } void Serializer::updateItemProject(Item item, Domain::Project::Ptr project) { if (isTaskItem(item)) { auto todo = item.payload(); todo->setRelatedTo(project->property("todoUid").toString()); } } void Serializer::removeItemParent(Akonadi::Item item) { if (!isTaskItem(item)) return; auto todo = item.payload(); todo->setRelatedTo(QString()); } void Serializer::promoteItemToProject(Akonadi::Item item) { if (!isTaskItem(item)) return; auto todo = item.payload(); todo->setRelatedTo(QString()); todo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); } void Serializer::clearItem(Akonadi::Item *item) { Q_ASSERT(item); if (!isTaskItem(*item)) return; // NOTE : Currently not working, when akonadistorage test will make it pass, we will use it // item->clearTags(); foreach (const Tag& tag, item->tags()) item->clearTag(tag); } Akonadi::Item::List Serializer::filterDescendantItems(const Akonadi::Item::List &potentialChildren, const Akonadi::Item &ancestorItem) { if (potentialChildren.isEmpty()) return Akonadi::Item::List(); Akonadi::Item::List itemsToProcess = potentialChildren; Q_ASSERT(ancestorItem.isValid() && ancestorItem.hasPayload()); KCalCore::Todo::Ptr todo = ancestorItem.payload(); const auto bound = std::partition(itemsToProcess.begin(), itemsToProcess.end(), [ancestorItem, todo](Akonadi::Item currentItem) { return (!currentItem.hasPayload() || currentItem == ancestorItem || currentItem.payload()->relatedTo() != todo->uid()); }); Akonadi::Item::List itemsRemoved; itemsRemoved.reserve(std::distance(itemsToProcess.begin(), bound)); std::copy(itemsToProcess.begin(), bound, std::back_inserter(itemsRemoved)); itemsToProcess.erase(itemsToProcess.begin(), bound); auto result = std::accumulate(itemsToProcess.begin(), itemsToProcess.end(), Akonadi::Item::List(), [this, itemsRemoved](Akonadi::Item::List result, Akonadi::Item currentItem) { result << currentItem; return result += filterDescendantItems(itemsRemoved, currentItem); }); return result; } bool Serializer::isProjectItem(Item item) { if (!item.hasPayload()) return false; return !isTaskItem(item); } Domain::Project::Ptr Serializer::createProjectFromItem(Item item) { if (!isProjectItem(item)) return Domain::Project::Ptr(); auto project = Domain::Project::Ptr::create(); updateProjectFromItem(project, item); return project; } void Serializer::updateProjectFromItem(Domain::Project::Ptr project, Item item) { if (!isProjectItem(item)) return; auto todo = item.payload(); project->setName(todo->summary()); project->setProperty("itemId", item.id()); project->setProperty("parentCollectionId", item.parentCollection().id()); project->setProperty("todoUid", todo->uid()); } Item Serializer::createItemFromProject(Domain::Project::Ptr project) { auto todo = KCalCore::Todo::Ptr::create(); todo->setSummary(project->name()); todo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); if (project->property("todoUid").isValid()) { todo->setUid(project->property("todoUid").toString()); } Akonadi::Item item; if (project->property("itemId").isValid()) { item.setId(project->property("itemId").value()); } if (project->property("parentCollectionId").isValid()) { auto parentId = project->property("parentCollectionId").value(); item.setParentCollection(Akonadi::Collection(parentId)); } item.setMimeType(KCalCore::Todo::todoMimeType()); item.setPayload(todo); return item; } bool Serializer::isProjectChild(Domain::Project::Ptr project, Item item) { const QString todoUid = project->property("todoUid").toString(); const QString relatedUid = relatedUidFromItem(item); return !todoUid.isEmpty() && !relatedUid.isEmpty() && todoUid == relatedUid; } Domain::Context::Ptr Serializer::createContextFromTag(Akonadi::Tag tag) { if (!isContext(tag)) return Domain::Context::Ptr(); auto context = Domain::Context::Ptr::create(); updateContextFromTag(context, tag); return context; } Akonadi::Tag Serializer::createTagFromContext(Domain::Context::Ptr context) { auto tag = Akonadi::Tag(); tag.setName(context->name()); tag.setType(Akonadi::SerializerInterface::contextTagType()); tag.setGid(QByteArray(context->name().toLatin1())); if (context->property("tagId").isValid()) tag.setId(context->property("tagId").value()); return tag; } void Serializer::updateContextFromTag(Domain::Context::Ptr context, Akonadi::Tag tag) { if (!isContext(tag)) return; context->setProperty("tagId", tag.id()); context->setName(tag.name()); } bool Serializer::hasContextTags(Item item) const { using namespace std::placeholders; Tag::List tags = item.tags(); return std::any_of(tags.constBegin(), tags.constEnd(), std::bind(Utils::mem_fn(&Serializer::isContext), this, _1)); } bool Serializer::isContext(const Akonadi::Tag &tag) const { return (tag.type() == Akonadi::SerializerInterface::contextTagType()); } bool Serializer::isContextTag(const Domain::Context::Ptr &context, const Akonadi::Tag &tag) const { return (context->property("tagId").value() == tag.id()); } bool Serializer::isContextChild(Domain::Context::Ptr context, Item item) const { if (!context->property("tagId").isValid()) return false; auto tagId = context->property("tagId").value(); Akonadi::Tag tag(tagId); return item.hasTag(tag); } diff --git a/tests/testlib/CMakeLists.txt b/tests/testlib/CMakeLists.txt index 3ea447d4..05c852fc 100644 --- a/tests/testlib/CMakeLists.txt +++ b/tests/testlib/CMakeLists.txt @@ -1,30 +1,28 @@ set(testlib_SRCS akonadidebug.cpp akonadifakedata.cpp akonadifakedataxmlloader.cpp akonadifakejobs.cpp akonadifakemonitor.cpp akonadifakestorage.cpp akonadifakestoragebehavior.cpp akonadistoragetestbase.cpp fakejob.cpp gencollection.cpp gentag.cpp gentodo.cpp modeltest.cpp monitorspy.cpp testhelpers.cpp ) include_directories(${CMAKE_SOURCE_DIR}/tests ${CMAKE_SOURCE_DIR}/src) add_library(testlib STATIC ${testlib_SRCS}) target_link_libraries(testlib KF5::AkonadiCore - KF5::AkonadiNotes KF5::AkonadiXml KF5::CalendarCore - KF5::Mime Qt5::Test KF5::KDELibs4Support ) diff --git a/tests/testlib/akonadifakedata.cpp b/tests/testlib/akonadifakedata.cpp index d1cf248d..a268cdfb 100644 --- a/tests/testlib/akonadifakedata.cpp +++ b/tests/testlib/akonadifakedata.cpp @@ -1,442 +1,440 @@ /* 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 "akonadifakedata.h" #include "akonadifakemonitor.h" #include "akonadifakestorage.h" -#include #include #include "akonadi/akonadiapplicationselectedattribute.h" #include using namespace Testlib; template static Akonadi::Collection::Id findParentId(const Entity &entity) { const auto parent = entity.parentCollection(); return parent.isValid() ? parent.id() : Akonadi::Collection::root().id(); } AkonadiFakeData::AkonadiFakeData() : m_monitor(new AkonadiFakeMonitor) { } AkonadiFakeData::AkonadiFakeData(const AkonadiFakeData &other) : m_collections(other.m_collections), m_childCollections(other.m_childCollections), m_items(other.m_items), m_childItems(other.m_childItems), m_monitor(new AkonadiFakeMonitor) { } AkonadiFakeData::~AkonadiFakeData() { } AkonadiFakeData &AkonadiFakeData::operator=(const AkonadiFakeData &other) { m_collections = other.m_collections; m_childCollections = other.m_childCollections; m_items = other.m_items; m_childItems = other.m_childItems; return *this; } Akonadi::Collection::List AkonadiFakeData::collections() const { return m_collections.values().toVector(); } Akonadi::Collection::List AkonadiFakeData::childCollections(Akonadi::Collection::Id parentId) const { if (!m_childCollections.contains(parentId)) return {}; const auto ids = m_childCollections.value(parentId); auto result = Akonadi::Collection::List(); std::transform(std::begin(ids), std::end(ids), std::back_inserter(result), [this] (Akonadi::Collection::Id id) { Q_ASSERT(m_collections.contains(id)); return m_collections.value(id); }); return result; } Akonadi::Collection AkonadiFakeData::collection(Akonadi::Collection::Id id) const { if (!m_collections.contains(id)) return Akonadi::Collection(); return m_collections.value(id); } void AkonadiFakeData::createCollection(const Akonadi::Collection &collection) { Q_ASSERT(!m_collections.contains(collection.id())); m_collections[collection.id()] = collection; const auto parentId = findParentId(collection); m_childCollections[parentId] << collection.id(); m_monitor->addCollection(reconstructAncestors(collection)); } void AkonadiFakeData::modifyCollection(const Akonadi::Collection &collection) { Q_ASSERT(m_collections.contains(collection.id())); const auto oldParentId = findParentId(m_collections[collection.id()]); const auto oldCollection = m_collections.take(collection.id()); auto newCollection = collection; newCollection.setRemoteId(oldCollection.remoteId()); if (!newCollection.parentCollection().isValid()) newCollection.setParentCollection(oldCollection.parentCollection()); if (newCollection.name().isEmpty()) newCollection.setName(oldCollection.name()); if (newCollection.contentMimeTypes().isEmpty()) newCollection.setContentMimeTypes(oldCollection.contentMimeTypes()); m_collections[newCollection.id()] = newCollection; const auto parentId = findParentId(newCollection); if (oldParentId != parentId) { m_childCollections[oldParentId].removeAll(newCollection.id()); m_childCollections[parentId] << newCollection.id(); } auto notifiedCollection = reconstructAncestors(newCollection); m_monitor->changeCollection(notifiedCollection); const auto mimeTypes = collection.contentMimeTypes(); - if (mimeTypes.contains(KCalCore::Todo::todoMimeType()) - || mimeTypes.contains(Akonadi::NoteUtils::noteMimeType())) { + if (mimeTypes.contains(KCalCore::Todo::todoMimeType())) { const auto oldAttribute = oldCollection.attribute(); const auto oldSelected = oldAttribute ? oldAttribute->isSelected() : true; const auto newAttribute = newCollection.attribute(); const auto newSelected = newAttribute ? newAttribute->isSelected() : true; if (oldSelected != newSelected) { m_monitor->changeCollectionSelection(notifiedCollection); } } } void AkonadiFakeData::removeCollection(const Akonadi::Collection &collection) { Q_ASSERT(m_collections.contains(collection.id())); const auto childCollections = m_childCollections[collection.id()]; foreach (const auto &childId, childCollections) { removeCollection(Akonadi::Collection(childId)); } m_childCollections.remove(collection.id()); const auto childItems = m_childItems[collection.id()]; foreach (const auto &childId, childItems) { removeItem(Akonadi::Item(childId)); } m_childItems.remove(collection.id()); const auto parentId = findParentId(m_collections[collection.id()]); const auto col = m_collections.take(collection.id()); m_childCollections[parentId].removeAll(collection.id()); m_monitor->removeCollection(col); } Akonadi::Tag::List AkonadiFakeData::tags() const { return m_tags.values().toVector(); } Akonadi::Tag AkonadiFakeData::tag(Akonadi::Tag::Id id) const { if (!m_tags.contains(id)) return Akonadi::Tag(); return m_tags.value(id); } void AkonadiFakeData::createTag(const Akonadi::Tag &tag) { Q_ASSERT(!m_tags.contains(tag.id())); m_tags[tag.id()] = tag; m_monitor->addTag(tag); } void AkonadiFakeData::modifyTag(const Akonadi::Tag &tag) { Q_ASSERT(m_tags.contains(tag.id())); const auto oldTag = m_tags.take(tag.id()); auto newTag = tag; newTag.setGid(oldTag.gid()); newTag.setRemoteId(oldTag.remoteId()); m_tags[tag.id()] = newTag; m_monitor->changeTag(newTag); } void AkonadiFakeData::removeTag(const Akonadi::Tag &tag) { Q_ASSERT(m_tags.contains(tag.id())); const auto ids = m_tagItems[tag.id()]; foreach (const auto &id, ids) { Q_ASSERT(m_items.contains(id)); auto item = m_items.value(id); item.clearTag(tag); m_items[id] = item; m_monitor->changeItem(item); } m_tagItems.remove(tag.id()); m_tags.remove(tag.id()); m_monitor->removeTag(tag); } Akonadi::Item::List AkonadiFakeData::items() const { return m_items.values().toVector(); } Akonadi::Item::List AkonadiFakeData::childItems(Akonadi::Collection::Id parentId) const { if (!m_childItems.contains(parentId)) return {}; const auto ids = m_childItems.value(parentId); auto result = Akonadi::Item::List(); std::transform(std::begin(ids), std::end(ids), std::back_inserter(result), [this] (Akonadi::Item::Id id) { Q_ASSERT(m_items.contains(id)); return m_items.value(id); }); return result; } Akonadi::Item::List AkonadiFakeData::tagItems(Akonadi::Tag::Id tagId) const { if (!m_tagItems.contains(tagId)) return {}; const auto ids = m_tagItems.value(tagId); auto result = Akonadi::Item::List(); std::transform(std::begin(ids), std::end(ids), std::back_inserter(result), [this] (Akonadi::Item::Id id) { Q_ASSERT(m_items.contains(id)); return m_items.value(id); }); return result; } Akonadi::Item AkonadiFakeData::item(Akonadi::Item::Id id) const { if (!m_items.contains(id)) return {}; return m_items.value(id); } void AkonadiFakeData::createItem(const Akonadi::Item &item) { Q_ASSERT(!m_items.contains(item.id())); m_items[item.id()] = item; const auto parentId = findParentId(item); m_childItems[parentId] << item.id(); foreach (const auto &tag, item.tags()) { Q_ASSERT(m_tags.contains(tag.id())); m_tagItems[tag.id()] << item.id(); } m_monitor->addItem(reconstructItemDependencies(item)); } void AkonadiFakeData::modifyItem(const Akonadi::Item &item) { Q_ASSERT(m_items.contains(item.id())); const auto oldTags = m_items[item.id()].tags(); const auto oldParentId = findParentId(m_items[item.id()]); const auto oldItem = m_items.take(item.id()); auto newItem = item; newItem.setRemoteId(oldItem.remoteId()); if (!newItem.parentCollection().isValid()) newItem.setParentCollection(oldItem.parentCollection()); m_items[newItem.id()] = newItem; const auto parentId = findParentId(newItem); if (oldParentId != parentId) { m_childItems[oldParentId].removeAll(newItem.id()); m_childItems[parentId] << newItem.id(); m_monitor->moveItem(reconstructItemDependencies(newItem)); } foreach (const auto &tag, oldTags) { m_tagItems[tag.id()].removeAll(newItem.id()); } foreach (const auto &tag, newItem.tags()) { Q_ASSERT(m_tags.contains(tag.id())); m_tagItems[tag.id()] << newItem.id(); } m_monitor->changeItem(reconstructItemDependencies(newItem)); } void AkonadiFakeData::removeItem(const Akonadi::Item &item) { Q_ASSERT(m_items.contains(item.id())); const auto parentId = findParentId(m_items[item.id()]); const auto i = m_items.take(item.id()); m_childItems[parentId].removeAll(item.id()); foreach (const Akonadi::Tag &tag, item.tags()) { m_tagItems[tag.id()].removeAll(item.id()); } m_monitor->removeItem(reconstructItemDependencies(i)); } Akonadi::MonitorInterface *AkonadiFakeData::createMonitor() { auto monitor = new AkonadiFakeMonitor; QObject::connect(m_monitor.data(), &AkonadiFakeMonitor::collectionAdded, monitor, &AkonadiFakeMonitor::addCollection); QObject::connect(m_monitor.data(), &AkonadiFakeMonitor::collectionChanged, monitor, &AkonadiFakeMonitor::changeCollection); QObject::connect(m_monitor.data(), &AkonadiFakeMonitor::collectionSelectionChanged, monitor, &AkonadiFakeMonitor::changeCollectionSelection); QObject::connect(m_monitor.data(), &AkonadiFakeMonitor::collectionRemoved, monitor, &AkonadiFakeMonitor::removeCollection); QObject::connect(m_monitor.data(), &AkonadiFakeMonitor::tagAdded, monitor, &AkonadiFakeMonitor::addTag); QObject::connect(m_monitor.data(), &AkonadiFakeMonitor::tagChanged, monitor, &AkonadiFakeMonitor::changeTag); QObject::connect(m_monitor.data(), &AkonadiFakeMonitor::tagRemoved, monitor, &AkonadiFakeMonitor::removeTag); QObject::connect(m_monitor.data(), &AkonadiFakeMonitor::itemAdded, monitor, &AkonadiFakeMonitor::addItem); QObject::connect(m_monitor.data(), &AkonadiFakeMonitor::itemChanged, monitor, &AkonadiFakeMonitor::changeItem); QObject::connect(m_monitor.data(), &AkonadiFakeMonitor::itemRemoved, monitor, &AkonadiFakeMonitor::removeItem); QObject::connect(m_monitor.data(), &AkonadiFakeMonitor::itemMoved, monitor, &AkonadiFakeMonitor::moveItem); return monitor; } Akonadi::StorageInterface *AkonadiFakeData::createStorage() { return new AkonadiFakeStorage(this); } template bool idLessThan(const T &left, const T &right) { return left.id() < right.id(); } Akonadi::Collection::Id AkonadiFakeData::maxCollectionId() const { if (m_collections.isEmpty()) return 0; auto it = std::max_element(m_collections.constBegin(), m_collections.constEnd(), idLessThan); return it.key(); } Akonadi::Item::Id AkonadiFakeData::maxItemId() const { if (m_items.isEmpty()) return 0; auto it = std::max_element(m_items.constBegin(), m_items.constEnd(), idLessThan); return it.key(); } Akonadi::Tag::Id AkonadiFakeData::maxTagId() const { if (m_tags.isEmpty()) return 0; auto it = std::max_element(m_tags.constBegin(), m_tags.constEnd(), idLessThan); return it.key(); } Akonadi::Collection AkonadiFakeData::reconstructAncestors(const Akonadi::Collection &collection, const Akonadi::Collection &root) const { if (!collection.isValid()) return Akonadi::Collection::root(); if (collection == root) return collection; auto parent = collection.parentCollection(); auto reconstructedParent = reconstructAncestors(m_collections.value(parent.id()), root); auto result = collection; result.setParentCollection(reconstructedParent); return result; } Akonadi::Item AkonadiFakeData::reconstructItemDependencies(const Akonadi::Item &item, const Akonadi::Collection &root) const { auto result = item; result.setParentCollection(reconstructAncestors(item.parentCollection(), root)); auto tags = item.tags(); std::transform(tags.constBegin(), tags.constEnd(), tags.begin(), [=] (const Akonadi::Tag &t) { return tag(t.id()); }); result.setTags(tags); return result; } const AkonadiFakeStorageBehavior &AkonadiFakeData::storageBehavior() const { return m_storageBehavior; } AkonadiFakeStorageBehavior &AkonadiFakeData::storageBehavior() { return m_storageBehavior; } diff --git a/tests/testlib/gencollection.cpp b/tests/testlib/gencollection.cpp index 1fec4279..c60ac32a 100644 --- a/tests/testlib/gencollection.cpp +++ b/tests/testlib/gencollection.cpp @@ -1,109 +1,109 @@ /* This file is part of Zanshin Copyright 2015 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "gencollection.h" #include -#include #include #include "akonadi/akonadiapplicationselectedattribute.h" using namespace Testlib; GenCollection::GenCollection(const Akonadi::Collection &collection) : m_collection(collection) { } GenCollection::operator Akonadi::Collection() { return m_collection; } GenCollection &GenCollection::withId(Akonadi::Collection::Id id) { m_collection.setId(id); return *this; } GenCollection &GenCollection::withParent(Akonadi::Collection::Id id) { m_collection.setParentCollection(Akonadi::Collection(id)); return *this; } GenCollection &GenCollection::withRootAsParent() { m_collection.setParentCollection(Akonadi::Collection::root()); return *this; } GenCollection &GenCollection::withName(const QString &name) { m_collection.setName(name); return *this; } GenCollection &GenCollection::withIcon(const QString &iconName) { auto attr = m_collection.attribute(Akonadi::Collection::AddIfMissing); attr->setIconName(iconName); return *this; } GenCollection &GenCollection::selected(bool value) { if (!value) { auto attr = m_collection.attribute(Akonadi::Collection::AddIfMissing); attr->setSelected(false); } else { m_collection.removeAttribute(); } return *this; } GenCollection &GenCollection::withTaskContent(bool value) { auto mimeTypes = m_collection.contentMimeTypes(); if (!value) { mimeTypes.removeAll(KCalCore::Todo::todoMimeType()); } else if (!mimeTypes.contains(KCalCore::Todo::todoMimeType())) { mimeTypes.append(KCalCore::Todo::todoMimeType()); } m_collection.setContentMimeTypes(mimeTypes); return *this; } GenCollection &GenCollection::withNoteContent(bool value) { + const auto noteMime = QString("text/x-vnd.akonadi.note"); auto mimeTypes = m_collection.contentMimeTypes(); if (!value) { - mimeTypes.removeAll(Akonadi::NoteUtils::noteMimeType()); - } else if (!mimeTypes.contains(Akonadi::NoteUtils::noteMimeType())) { - mimeTypes.append(Akonadi::NoteUtils::noteMimeType()); + mimeTypes.removeAll(noteMime); + } else if (!mimeTypes.contains(noteMime)) { + mimeTypes.append(noteMime); } m_collection.setContentMimeTypes(mimeTypes); return *this; } diff --git a/tests/units/akonadi/akonadilivequeryhelperstest.cpp b/tests/units/akonadi/akonadilivequeryhelperstest.cpp index 93f5c9b7..84fcb556 100644 --- a/tests/units/akonadi/akonadilivequeryhelperstest.cpp +++ b/tests/units/akonadi/akonadilivequeryhelperstest.cpp @@ -1,574 +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 -#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 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 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() { 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(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()); // 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 714cb593..7914c23f 100644 --- a/tests/units/akonadi/akonadilivequeryintegratortest.cpp +++ b/tests/units/akonadi/akonadilivequeryintegratortest.cpp @@ -1,922 +1,914 @@ /* 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 -#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 if (item.hasPayload()) { - const auto message = item.payload(); - const Akonadi::NoteUtils::NoteMessageWrapper note(message); - return note.title(); - } 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"))); 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"))); // 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"))); // 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"))); // 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"))); // 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"))); 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"))); // 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"))); 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/akonadiserializertest.cpp b/tests/units/akonadi/akonadiserializertest.cpp index cbe47377..dac5e315 100644 --- a/tests/units/akonadi/akonadiserializertest.cpp +++ b/tests/units/akonadi/akonadiserializertest.cpp @@ -1,2044 +1,2025 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens Copyright 2014 Rémi Benoit This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "akonadi/akonadiserializer.h" #include "akonadi/akonadiapplicationselectedattribute.h" #include "akonadi/akonaditimestampattribute.h" #include #include #include -#include #include #include -#include Q_DECLARE_METATYPE(Akonadi::Item*) static void setTodoDates(KCalCore::Todo::Ptr todo, const QDate &start, const QDate &due) { todo->setDtStart(QDateTime(start)); todo->setDtDue(QDateTime(due)); } class AkonadiSerializerTest : public QObject { Q_OBJECT private slots: void shouldKnowWhenAnObjectRepresentsACollection() { // GIVEN Akonadi::Serializer serializer; auto object = Akonadi::Serializer::QObjectPtr::create(); Akonadi::Collection collection(42); // WHEN // Nothing yet // THEN QVERIFY(!serializer.representsCollection(object, collection)); // WHEN object->setProperty("collectionId", 42); // THEN QVERIFY(serializer.representsCollection(object, collection)); // WHEN object->setProperty("collectionId", 43); // THEN QVERIFY(!serializer.representsCollection(object, collection)); } void shouldKnowWhenAnObjectRepresentsAnItem() { // GIVEN Akonadi::Serializer serializer; auto object = Akonadi::Serializer::QObjectPtr::create(); Akonadi::Item item(42); // WHEN // Nothing yet // THEN QVERIFY(!serializer.representsItem(object, item)); // WHEN object->setProperty("itemId", 42); // THEN QVERIFY(serializer.representsItem(object, item)); // WHEN object->setProperty("itemId", 43); // THEN QVERIFY(!serializer.representsItem(object, item)); } void shouldKnowTaskItemUid_data() { QTest::addColumn("item"); QTest::addColumn("expectedUid"); Akonadi::Item item1; KCalCore::Todo::Ptr todo1(new KCalCore::Todo); todo1->setUid(QString()); item1.setPayload(todo1); Akonadi::Item item2; KCalCore::Todo::Ptr todo2(new KCalCore::Todo); todo2->setUid(QStringLiteral("1")); item2.setPayload(todo2); - Akonadi::Item item3; - KMime::Message::Ptr message(new KMime::Message); - message->subject(true)->fromUnicodeString(QStringLiteral("foo"), "utf-8"); - message->mainBodyPart()->fromUnicodeString(QStringLiteral("bar")); - item3.setMimeType(Akonadi::NoteUtils::noteMimeType()); - item3.setPayload(message); - QTest::newRow("task without uid") << item1 << QString(); QTest::newRow("task with uid") << item2 << "1"; - QTest::newRow("note") << item3 << QString(); } void shouldKnowTaskItemUid() { // GIVEN QFETCH(Akonadi::Item, item); QFETCH(QString, expectedUid); // WHEN Akonadi::Serializer serializer; QString uid = serializer.itemUid(item); // THEN QCOMPARE(uid, expectedUid); } void shouldCreateDataSourceFromCollection_data() { QTest::addColumn("name"); QTest::addColumn("iconName"); QTest::addColumn("mimeTypes"); QTest::addColumn("hasSelectedAttribute"); QTest::addColumn("isSelected"); const auto noteMimeTypes = QStringList() << QStringLiteral("text/x-vnd.akonadi.note"); const auto taskMimeTypes = QStringList() << QStringLiteral("application/x-vnd.akonadi.calendar.todo"); const auto bogusMimeTypes = QStringList() << QStringLiteral("foo/bar"); const auto allMimeTypes = noteMimeTypes + taskMimeTypes + bogusMimeTypes; QTest::newRow("nominal case") << "name" << "icon" << allMimeTypes << true << false; QTest::newRow("only notes") << "name" << "icon" << noteMimeTypes << true << false; QTest::newRow("only tasks") << "name" << "icon" << taskMimeTypes << true << false; QTest::newRow("only bogus") << "name" << "icon" << bogusMimeTypes << true << false; QTest::newRow("no selected attribute") << "name" << "icon" << allMimeTypes << false << false; QTest::newRow("selected attribute (false)") << "name" << "icon" << allMimeTypes << true << false; QTest::newRow("selected attribute (true)") << "name" << "icon" << allMimeTypes << true << true; QTest::newRow("empty case") << QString() << QString() << QStringList() << false << false; } void shouldCreateDataSourceFromCollection() { // GIVEN // Data... QFETCH(QString, name); QFETCH(QString, iconName); QFETCH(QStringList, mimeTypes); QFETCH(bool, hasSelectedAttribute); QFETCH(bool, isSelected); Domain::DataSource::ContentTypes expectedContentTypes; if (mimeTypes.contains(QStringLiteral("application/x-vnd.akonadi.calendar.todo"))) { expectedContentTypes |= Domain::DataSource::Tasks; } // ... stored in a collection Akonadi::Collection collection(42); collection.setContentMimeTypes(mimeTypes); collection.setName(name); auto displayAttribute = new Akonadi::EntityDisplayAttribute; displayAttribute->setIconName(iconName); collection.addAttribute(displayAttribute); if (hasSelectedAttribute) { auto selectedAttribute = new Akonadi::ApplicationSelectedAttribute; selectedAttribute->setSelected(isSelected); collection.addAttribute(selectedAttribute); } // WHEN Akonadi::Serializer serializer; auto dataSource = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::BaseName); // THEN QCOMPARE(dataSource->name(), name); QCOMPARE(dataSource->iconName(), iconName); QCOMPARE(dataSource->contentTypes(), expectedContentTypes); QCOMPARE(dataSource->isSelected(), !hasSelectedAttribute || isSelected); QCOMPARE(dataSource->property("collectionId").value(), collection.id()); } void shouldCreateNullDataSourceFromInvalidCollection() { // GIVEN Akonadi::Collection collection; // WHEN Akonadi::Serializer serializer; auto dataSource = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::BaseName); // THEN QVERIFY(dataSource.isNull()); } void shouldUpdateDataSourceFromCollection_data() { QTest::addColumn("updatedName"); QTest::newRow("no change") << "name"; QTest::newRow("changed") << "new name"; } void shouldUpdateDataSourceFromCollection() { // GIVEN // A collection... Akonadi::Collection originalCollection(42); originalCollection.setName(QStringLiteral("name")); // ... deserialized as a data source Akonadi::Serializer serializer; auto dataSource = serializer.createDataSourceFromCollection(originalCollection, Akonadi::SerializerInterface::BaseName); // WHEN // Data... QFETCH(QString, updatedName); // ... in a new collection Akonadi::Collection updatedCollection(42); updatedCollection.setName(updatedName); serializer.updateDataSourceFromCollection(dataSource, updatedCollection, Akonadi::SerializerInterface::BaseName); // THEN QCOMPARE(dataSource->name(), updatedName); } void shouldNotUpdateDataSourceFromInvalidCollection() { // GIVEN // Data... const QString name = QStringLiteral("name"); // ... stored in a collection... Akonadi::Collection originalCollection(42); originalCollection.setName(name); // ... deserialized as a data source Akonadi::Serializer serializer; auto dataSource = serializer.createDataSourceFromCollection(originalCollection, Akonadi::SerializerInterface::BaseName); // WHEN Akonadi::Collection invalidCollection; invalidCollection.setName(QStringLiteral("foo")); serializer.updateDataSourceFromCollection(dataSource, invalidCollection, Akonadi::SerializerInterface::BaseName); // THEN QCOMPARE(dataSource->name(), name); } void shouldNameDataSourceFromCollectionPathIfRequested() { // GIVEN // Data... const QString name = QStringLiteral("name"); const QString parentName = QStringLiteral("parent"); // ... stored in a collection with a parent Akonadi::Collection collection(42); collection.setName(name); Akonadi::Collection parentCollection(41); parentCollection.setName(QStringLiteral("Foo")); auto attribute = new Akonadi::EntityDisplayAttribute; attribute->setDisplayName(parentName); parentCollection.addAttribute(attribute); collection.setParentCollection(parentCollection); // WHEN Akonadi::Serializer serializer; auto dataSource1 = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::FullPath); auto dataSource2 = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::BaseName); // Give it another try with the root parentCollection.setParentCollection(Akonadi::Collection::root()); collection.setParentCollection(parentCollection); auto dataSource3 = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::FullPath); auto dataSource4 = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::BaseName); // THEN QCOMPARE(dataSource1->name(), QString(parentName + " » " + name)); QCOMPARE(dataSource2->name(), name); QCOMPARE(dataSource3->name(), QString(parentName + " » " + name)); QCOMPARE(dataSource4->name(), name); } void shouldCreateCollectionFromDataSource_data() { QTest::addColumn("name"); QTest::addColumn("iconName"); QTest::addColumn("contentTypes"); QTest::addColumn("isSelected"); const auto noType = Domain::DataSource::ContentTypes(Domain::DataSource::NoContent); const auto taskType = Domain::DataSource::ContentTypes(Domain::DataSource::Tasks); QTest::newRow("only tasks") << "name" << "icon-name" << taskType << true; QTest::newRow("only nothing ;)") << "name" << "icon-name" << noType << true; QTest::newRow("not selected") << "name" << "icon-name" << taskType << false; QTest::newRow("selected") << "name" << "icon-name" << taskType << true; QTest::newRow("empty case") << QString() << QString() << noType << true; } void shouldCreateCollectionFromDataSource() { // GIVEN const auto timestamp = QDateTime::currentMSecsSinceEpoch(); // Data... QFETCH(QString, name); QFETCH(QString, iconName); QFETCH(Domain::DataSource::ContentTypes, contentTypes); QFETCH(bool, isSelected); QStringList mimeTypes; if (contentTypes & Domain::DataSource::Tasks) mimeTypes << QStringLiteral("application/x-vnd.akonadi.calendar.todo"); // ... stored in a data source auto source = Domain::DataSource::Ptr::create(); source->setName(name); source->setIconName(iconName); source->setContentTypes(contentTypes); source->setSelected(isSelected); source->setProperty("collectionId", 42); // WHEN Akonadi::Serializer serializer; auto collection = serializer.createCollectionFromDataSource(source); // THEN QCOMPARE(collection.id(), source->property("collectionId").value()); QVERIFY(collection.hasAttribute()); QCOMPARE(collection.attribute()->isSelected(), isSelected); QVERIFY(collection.hasAttribute()); QVERIFY(collection.attribute()->timestamp() >= timestamp); } void shouldVerifyIfCollectionIsSelected_data() { QTest::addColumn("mimeTypes"); QTest::addColumn("hasSelectedAttribute"); QTest::addColumn("isSelected"); QTest::addColumn("expectedSelected"); const auto taskMimeTypes = QStringList() << QStringLiteral("application/x-vnd.akonadi.calendar.todo"); const auto bogusMimeTypes = QStringList() << QStringLiteral("foo/bar"); const auto allMimeTypes = taskMimeTypes + bogusMimeTypes; QTest::newRow("nominal case") << allMimeTypes << true << false << false; QTest::newRow("only tasks") << taskMimeTypes << true << false << false; QTest::newRow("only bogus") << bogusMimeTypes << true << false << false; QTest::newRow("selected, only tasks") << taskMimeTypes << true << true << true; QTest::newRow("selected, only bogus") << bogusMimeTypes << true << true << false; QTest::newRow("no selected attribute") << allMimeTypes << false << false << true; QTest::newRow("selected attribute (false)") << allMimeTypes << true << false << false; QTest::newRow("selected attribute (true)") << allMimeTypes << true << true << true; QTest::newRow("empty case") << QStringList() << false << false << false; } void shouldVerifyIfCollectionIsSelected() { // GIVEN QFETCH(QStringList, mimeTypes); QFETCH(bool, hasSelectedAttribute); QFETCH(bool, isSelected); Domain::DataSource::ContentTypes expectedContentTypes; if (mimeTypes.contains(QStringLiteral("application/x-vnd.akonadi.calendar.todo"))) { expectedContentTypes |= Domain::DataSource::Tasks; } // ... stored in a collection Akonadi::Collection collection(42); collection.setContentMimeTypes(mimeTypes); if (hasSelectedAttribute) { auto selectedAttribute = new Akonadi::ApplicationSelectedAttribute; selectedAttribute->setSelected(isSelected); collection.addAttribute(selectedAttribute); } // WHEN Akonadi::Serializer serializer; // THEN QFETCH(bool, expectedSelected); QCOMPARE(serializer.isSelectedCollection(collection), expectedSelected); } void shouldVerifyCollectionContents_data() { QTest::addColumn("mimeType"); QTest::addColumn("expectedTasks"); QTest::newRow("task collection") << "application/x-vnd.akonadi.calendar.todo" << true; QTest::newRow("note collection") << "text/x-vnd.akonadi.note" << false; } void shouldVerifyCollectionContents() { // GIVEN // Data... QFETCH(QString, mimeType); // ... stored in a collection Akonadi::Collection collection(42); collection.setContentMimeTypes(QStringList() << mimeType); // WHEN Akonadi::Serializer serializer; QFETCH(bool, expectedTasks); // THEN QCOMPARE(serializer.isTaskCollection(collection), expectedTasks); } void shouldCreateTaskFromItem_data() { QTest::addColumn("summary"); QTest::addColumn("content"); QTest::addColumn("isDone"); QTest::addColumn("doneDate"); QTest::addColumn("startDate"); QTest::addColumn("dueDate"); QTest::newRow("nominal case") << "summary" << "content" << false << QDate() << QDate(2013, 11, 24) << QDate(2014, 03, 01); QTest::newRow("done case") << "summary" << "content" << true << QDate(2013, 11, 30) << QDate(2013, 11, 24) << QDate(2014, 03, 01); QTest::newRow("done without doneDate case") << "summary" << "content" << true << QDate() << QDate(2013, 11, 24) << QDate(2014, 03, 01); QTest::newRow("empty case") << QString() << QString() << false << QDate() << QDate() << QDate(); } void shouldCreateTaskFromItem() { // GIVEN // Data... QFETCH(QString, summary); QFETCH(QString, content); QFETCH(bool, isDone); QFETCH(QDate, doneDate); QFETCH(QDate, startDate); QFETCH(QDate, dueDate); // ... stored in a todo... KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(summary); todo->setDescription(content); if (isDone) todo->setCompleted(QDateTime(doneDate)); else todo->setCompleted(isDone); setTodoDates(todo, startDate, dueDate); todo->setRelatedTo(QStringLiteral("my-uid")); // ... as payload of an item Akonadi::Item item; item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); // which has a parent collection Akonadi::Collection collection(43); item.setParentCollection(collection); // WHEN Akonadi::Serializer serializer; auto task = serializer.createTaskFromItem(item); // THEN QVERIFY(!task.isNull()); QCOMPARE(task->title(), summary); QCOMPARE(task->text(), content); QCOMPARE(task->isDone(), isDone); QCOMPARE(task->doneDate(), doneDate); QCOMPARE(task->startDate(), startDate); QCOMPARE(task->dueDate(), dueDate); QCOMPARE(task->property("todoUid").toString(), todo->uid()); QCOMPARE(task->property("relatedUid").toString(), todo->relatedTo()); QCOMPARE(task->property("itemId").toLongLong(), item.id()); QCOMPARE(task->property("parentCollectionId").toLongLong(), collection.id()); } void shouldCreateNullTaskFromInvalidItem() { // GIVEN Akonadi::Item item; // WHEN Akonadi::Serializer serializer; auto task = serializer.createTaskFromItem(item); // THEN QVERIFY(task.isNull()); } void shouldCreateNullTaskFromProjectItem() { // GIVEN // A todo with the project flag KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(QStringLiteral("foo")); todo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); // ... as payload of an item Akonadi::Item item; item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); // WHEN Akonadi::Serializer serializer; auto task = serializer.createTaskFromItem(item); // THEN QVERIFY(task.isNull()); } void shouldUpdateTaskFromItem_data() { QTest::addColumn("updatedSummary"); QTest::addColumn("updatedContent"); QTest::addColumn("updatedDone"); QTest::addColumn("updatedDoneDate"); QTest::addColumn("updatedStartDate"); QTest::addColumn("updatedDueDate"); QTest::addColumn("updatedRelated"); QTest::addColumn("updatedRecurs"); QTest::addColumn("updatedAttachmentData"); QTest::addColumn("updatedAttachmentUris"); QTest::addColumn("updatedAttachmentLabels"); QTest::addColumn("updatedAttachmentMimeTypes"); QTest::addColumn("updatedAttachmentIconNames"); QTest::addColumn("updatedRunning"); QTest::newRow("no change") << "summary" << "content" << false << QDate() << QDate(2013, 11, 24) << QDate(2014, 03, 01) << "my-uid" << false << QByteArrayList() << QStringList() << QStringList() << QStringList() << QStringList() << false; QTest::newRow("changed") << "new summary" << "new content" << true << QDate(2013, 11, 28) << QDate(2013, 11, 25) << QDate(2014, 03, 02) << "my-new-uid" << true << QByteArrayList({"foo", "# bar", QByteArray()}) << QStringList({QString(), QString(), "https://www.kde.org"}) << QStringList({"label1", "label2", "label3"}) << QStringList({"text/plain", "text/markdown", "text/html"}) << QStringList({"text-plain", "text-markdown", "text-html"}) << false; QTest::newRow("set_to_running") << "summary" << "content" << false << QDate() << QDate(2013, 11, 24) << QDate(2014, 03, 01) << "my-uid" << false << QByteArrayList() << QStringList() << QStringList() << QStringList() << QStringList() << true; } void shouldUpdateTaskFromItem() { // GIVEN // A todo... KCalCore::Todo::Ptr originalTodo(new KCalCore::Todo); originalTodo->setSummary(QStringLiteral("summary")); originalTodo->setDescription(QStringLiteral("content")); originalTodo->setCompleted(false); setTodoDates(originalTodo, QDate(2013, 11, 24), QDate(2014, 03, 01)); originalTodo->setRelatedTo(QStringLiteral("my-uid")); KCalCore::Attendee::Ptr originalAttendee(new KCalCore::Attendee(QStringLiteral("John Doe"), QStringLiteral("j@d.com"), true, KCalCore::Attendee::Accepted)); originalTodo->addAttendee(originalAttendee); // ... as payload of an item... Akonadi::Item originalItem; originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); originalItem.setPayload(originalTodo); // ... which has a parent collection... Akonadi::Collection originalCollection(43); originalItem.setParentCollection(originalCollection); // ... deserialized as a task Akonadi::Serializer serializer; auto task = serializer.createTaskFromItem(originalItem); // WHEN // Data... QFETCH(QString, updatedSummary); QFETCH(QString, updatedContent); QFETCH(bool, updatedDone); QFETCH(QDate, updatedDoneDate); QFETCH(QDate, updatedStartDate); QFETCH(QDate, updatedDueDate); QFETCH(QString, updatedRelated); QFETCH(bool, updatedRecurs); QFETCH(QByteArrayList, updatedAttachmentData); QFETCH(QStringList, updatedAttachmentUris); QFETCH(QStringList, updatedAttachmentLabels); QFETCH(QStringList, updatedAttachmentMimeTypes); QFETCH(QStringList, updatedAttachmentIconNames); QFETCH(bool, updatedRunning); // ... in a new todo... KCalCore::Todo::Ptr updatedTodo(new KCalCore::Todo); updatedTodo->setSummary(updatedSummary); updatedTodo->setDescription(updatedContent); if (updatedDone) updatedTodo->setCompleted(QDateTime(updatedDoneDate)); else updatedTodo->setCompleted(updatedDone); setTodoDates(updatedTodo, updatedStartDate, updatedDueDate); updatedTodo->setRelatedTo(updatedRelated); if (updatedRecurs) updatedTodo->recurrence()->setDaily(1); for (int i = 0; i < updatedAttachmentData.size(); i++) { KCalCore::Attachment::Ptr attachment(new KCalCore::Attachment(QByteArray())); if (!updatedAttachmentData.at(i).isEmpty()) attachment->setDecodedData(updatedAttachmentData.at(i)); else attachment->setUri(updatedAttachmentUris.at(i)); attachment->setMimeType(updatedAttachmentMimeTypes.at(i)); attachment->setLabel(updatedAttachmentLabels.at(i)); updatedTodo->addAttachment(attachment); } if (updatedRunning) { updatedTodo->setCustomProperty("Zanshin", "Running", "1"); } else { updatedTodo->removeCustomProperty("Zanshin", "Running"); } // ... as payload of a new item Akonadi::Item updatedItem; updatedItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); updatedItem.setPayload(updatedTodo); // ... which has a new parent collection Akonadi::Collection updatedCollection(45); updatedItem.setParentCollection(updatedCollection); serializer.updateTaskFromItem(task, updatedItem); // THEN QCOMPARE(task->title(), updatedSummary); QCOMPARE(task->text(), updatedContent); QCOMPARE(task->isDone(), updatedDone); QCOMPARE(task->doneDate(), updatedDoneDate); QCOMPARE(task->startDate(), updatedStartDate); QCOMPARE(task->dueDate(), updatedDueDate); QCOMPARE(task->property("todoUid").toString(), updatedTodo->uid()); QCOMPARE(task->property("relatedUid").toString(), updatedTodo->relatedTo()); QCOMPARE(task->property("itemId").toLongLong(), updatedItem.id()); QCOMPARE(task->property("parentCollectionId").toLongLong(), updatedCollection.id()); QCOMPARE(task->recurrence(), (updatedRecurs ? Domain::Task::RecursDaily : Domain::Task::NoRecurrence)); QCOMPARE(task->attachments().size(), updatedAttachmentData.size()); for (int i = 0; i < task->attachments().size(); i++) { const auto attachment = task->attachments().at(i); QCOMPARE(attachment.data(), updatedAttachmentData.at(i)); QCOMPARE(attachment.uri(), QUrl(updatedAttachmentUris.at(i))); QCOMPARE(attachment.label(), updatedAttachmentLabels.at(i)); QCOMPARE(attachment.mimeType(), updatedAttachmentMimeTypes.at(i)); QCOMPARE(attachment.iconName(), updatedAttachmentIconNames.at(i)); } QCOMPARE(task->isRunning(), updatedRunning); } void shouldUpdateTaskRecurrenceFromItem_data() { QTest::addColumn("todoRecurrence"); QTest::addColumn("expectedRecurrence"); QTest::newRow("none") << int(KCalCore::Recurrence::rNone) << Domain::Task::NoRecurrence; QTest::newRow("minutely") << int(KCalCore::Recurrence::rMinutely) << Domain::Task::NoRecurrence; QTest::newRow("hourly") << int(KCalCore::Recurrence::rHourly) << Domain::Task::NoRecurrence; QTest::newRow("daily") << int(KCalCore::Recurrence::rDaily) << Domain::Task::RecursDaily; QTest::newRow("weekly") << int(KCalCore::Recurrence::rWeekly) << Domain::Task::RecursWeekly; QTest::newRow("monthly") << int(KCalCore::Recurrence::rMonthlyDay) << Domain::Task::RecursMonthly; } void shouldUpdateTaskRecurrenceFromItem() { // GIVEN // A todo... KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(QStringLiteral("summary")); QFETCH(int, todoRecurrence); switch (todoRecurrence) { case KCalCore::Recurrence::rNone: break; case KCalCore::Recurrence::rMinutely: todo->recurrence()->setMinutely(1); break; case KCalCore::Recurrence::rHourly: todo->recurrence()->setHourly(1); break; case KCalCore::Recurrence::rDaily: todo->recurrence()->setDaily(1); break; case KCalCore::Recurrence::rWeekly: todo->recurrence()->setWeekly(1); break; case KCalCore::Recurrence::rMonthlyDay: todo->recurrence()->setMonthly(1); break; default: qFatal("Shouldn't happen"); } // ... as payload of an item... Akonadi::Item item; item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); // ... which has a parent collection... Akonadi::Collection collection(43); item.setParentCollection(collection); // WHEN // ... deserialized as a task Akonadi::Serializer serializer; auto task = serializer.createTaskFromItem(item); // THEN QFETCH(Domain::Task::Recurrence, expectedRecurrence); QCOMPARE(task->recurrence(), expectedRecurrence); } void shouldNotBreakRecurrenceDuringSerialization() { // GIVEN // Data... const QDate today(QDate::currentDate()); const QDate doneDate(2013, 11, 20); const QDate startDate(2013, 11, 10); // ... stored in a todo... KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(QStringLiteral("summary")); todo->setDtStart(QDateTime(startDate)); todo->recurrence()->setMonthly(1); // ... as payload of an item... Akonadi::Item item; item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); // ... deserialized as a task Akonadi::Serializer serializer; auto task = serializer.createTaskFromItem(item); // WHEN // Task is marked done... task->setDoneDate(doneDate); task->setDone(true); // and goes through serialization and back const auto newItem = serializer.createItemFromTask(task); serializer.updateTaskFromItem(task, newItem); // THEN QCOMPARE(task->recurrence(), Domain::Task::RecursMonthly); QVERIFY(!task->isDone()); const QDate lastOccurrence(QDate(today.year(), today.month(), 10)); if (today.day() >= 10) QCOMPARE(task->startDate(), lastOccurrence.addMonths(1)); else QCOMPARE(task->startDate(), lastOccurrence); } void shouldNotUpdateTaskFromInvalidItem() { // GIVEN // Data... const QString summary = QStringLiteral("summary"); const QString content = QStringLiteral("content"); const bool isDone = true; const QDate doneDate(2013, 11, 30); const QDate startDate(2013, 11, 24); const QDate dueDate(2014, 03, 01); // ... stored in a todo... KCalCore::Todo::Ptr originalTodo(new KCalCore::Todo); originalTodo->setSummary(summary); originalTodo->setDescription(content); if (originalTodo) originalTodo->setCompleted(QDateTime(doneDate)); else originalTodo->setCompleted(isDone); setTodoDates(originalTodo, startDate, dueDate); // ... as payload of an item... Akonadi::Item originalItem; originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); originalItem.setPayload(originalTodo); // ... deserialized as a task Akonadi::Serializer serializer; auto task = serializer.createTaskFromItem(originalItem); // WHEN Akonadi::Item invalidItem; serializer.updateTaskFromItem(task, invalidItem); // THEN QCOMPARE(task->title(), summary); QCOMPARE(task->text(), content); QCOMPARE(task->isDone(), isDone); QCOMPARE(task->doneDate(), doneDate); QCOMPARE(task->startDate(), startDate); QCOMPARE(task->dueDate(), dueDate); QCOMPARE(task->property("itemId").toLongLong(), originalItem.id()); } void shouldNotUpdateTaskFromProjectItem() { // GIVEN // Data... const QString summary = QStringLiteral("summary"); const QString content = QStringLiteral("content"); const bool isDone = true; const QDate doneDate(2013, 11, 30); const QDate startDate(2013, 11, 24); const QDate dueDate(2014, 03, 01); // ... stored in a todo... KCalCore::Todo::Ptr originalTodo(new KCalCore::Todo); originalTodo->setSummary(summary); originalTodo->setDescription(content); if (originalTodo) originalTodo->setCompleted(QDateTime(doneDate)); else originalTodo->setCompleted(isDone); setTodoDates(originalTodo, startDate, dueDate); // ... as payload of an item... Akonadi::Item originalItem; originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); originalItem.setPayload(originalTodo); // ... deserialized as a task Akonadi::Serializer serializer; auto task = serializer.createTaskFromItem(originalItem); // WHEN // A todo with the project flag KCalCore::Todo::Ptr projectTodo(new KCalCore::Todo); projectTodo->setSummary(QStringLiteral("foo")); projectTodo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); // ... as payload of an item Akonadi::Item projectItem; projectItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); projectItem.setPayload(projectTodo); serializer.updateTaskFromItem(task, projectItem); // THEN QCOMPARE(task->title(), summary); QCOMPARE(task->text(), content); QCOMPARE(task->isDone(), isDone); QCOMPARE(task->doneDate(), doneDate); QCOMPARE(task->startDate(), startDate); QCOMPARE(task->dueDate(), dueDate); QCOMPARE(task->property("itemId").toLongLong(), originalItem.id()); } void shouldCreateItemFromTask_data() { QTest::addColumn("summary"); QTest::addColumn("content"); QTest::addColumn("isDone"); QTest::addColumn("doneDate"); QTest::addColumn("startDate"); QTest::addColumn("dueDate"); QTest::addColumn("itemId"); QTest::addColumn("parentCollectionId"); QTest::addColumn("todoUid"); QTest::addColumn("recurrence"); QTest::addColumn("attachments"); QTest::addColumn("running"); Domain::Task::Attachments attachments; Domain::Task::Attachment dataAttachment; dataAttachment.setData("foo"); dataAttachment.setLabel("dataAttachment"); dataAttachment.setMimeType("text/plain"); dataAttachment.setIconName("text-plain"); attachments.append(dataAttachment); Domain::Task::Attachment uriAttachment; uriAttachment.setUri(QUrl("https://www.kde.org")); uriAttachment.setLabel("uriAttachment"); uriAttachment.setMimeType("text/html"); uriAttachment.setIconName("text-html"); attachments.append(uriAttachment); QTest::newRow("nominal case (no id)") << "summary" << "content" << false << QDate() << QDate(2013, 11, 24) << QDate(2014, 03, 01) << qint64(-1) << qint64(-1) << QString() << Domain::Task::NoRecurrence << attachments << false; QTest::newRow("nominal case (daily)") << "summary" << "content" << false << QDate() << QDate(2013, 11, 24) << QDate(2014, 03, 01) << qint64(-1) << qint64(-1) << QString() << Domain::Task::RecursDaily << Domain::Task::Attachments() << false; QTest::newRow("nominal case (weekly)") << "summary" << "content" << false << QDate() << QDate(2013, 11, 24) << QDate(2014, 03, 01) << qint64(-1) << qint64(-1) << QString() << Domain::Task::RecursWeekly << Domain::Task::Attachments() << false; QTest::newRow("nominal case (monthly)") << "summary" << "content" << false << QDate() << QDate(2013, 11, 24) << QDate(2014, 03, 01) << qint64(-1) << qint64(-1) << QString() << Domain::Task::RecursMonthly << Domain::Task::Attachments() << false; QTest::newRow("done case (no id)") << "summary" << "content" << true << QDate(2013, 11, 30) << QDate(2013, 11, 24) << QDate(2014, 03, 01) << qint64(-1) << qint64(-1) << QString() << Domain::Task::NoRecurrence << Domain::Task::Attachments() << false; QTest::newRow("empty case (no id)") << QString() << QString() << false << QDate() << QDate() << QDate() << qint64(-1) << qint64(-1) << QString() << Domain::Task::NoRecurrence << Domain::Task::Attachments() << false; #if 0 // if we ever need time info, then we need a Task::setAllDay(bool) just like KCalCore::Todo has. QTest::newRow("nominal_with_time_info_noid") << "summary" << "content" << true << QDateTime(QDate(2015, 3, 1), QTime(1, 2, 3), Qt::UTC) << QDateTime(QDate(2013, 11, 24), QTime(0, 1, 2), Qt::UTC) << QDateTime(QDate(2016, 3, 1), QTime(4, 5, 6), Qt::UTC) << qint64(-1) << qint64(-1) << QString() << Domain::Task::NoRecurrence << Domain::Task::Attachments() << false; #endif QTest::newRow("nominal case (with id)") << "summary" << "content" << false << QDate() << QDate(2013, 11, 24) << QDate(2014, 03, 01) << qint64(42) << qint64(43) << "my-uid" << Domain::Task::NoRecurrence << Domain::Task::Attachments() << false; QTest::newRow("done case (with id)") << "summary" << "content" << true << QDate(2013, 11, 30) << QDate(2013, 11, 24) << QDate(2014, 03, 01) << qint64(42) << qint64(43) << "my-uid" << Domain::Task::NoRecurrence << Domain::Task::Attachments() << false; QTest::newRow("empty case (with id)") << QString() << QString() << false << QDate() << QDate() << QDate() << qint64(42) << qint64(43) << "my-uid" << Domain::Task::NoRecurrence << Domain::Task::Attachments() << false; QTest::newRow("nominal case (running)") << "running" << QString() << false << QDate() << QDate(2013, 11, 24) << QDate(2014, 03, 01) << qint64(-1) << qint64(-1) << QString() << Domain::Task::NoRecurrence << Domain::Task::Attachments() << true; } void shouldCreateItemFromTask() { // GIVEN // Data... QFETCH(QString, summary); QFETCH(QString, content); QFETCH(bool, isDone); QFETCH(QDate, doneDate); QFETCH(QDate, startDate); QFETCH(QDate, dueDate); QFETCH(qint64, itemId); QFETCH(qint64, parentCollectionId); QFETCH(QString, todoUid); QFETCH(Domain::Task::Recurrence, recurrence); QFETCH(Domain::Task::Attachments, attachments); QFETCH(bool, running); // ... stored in a task auto task = Domain::Task::Ptr::create(); task->setTitle(summary); task->setText(content); task->setDone(isDone); task->setDoneDate(doneDate); task->setStartDate(startDate); task->setDueDate(dueDate); task->setRecurrence(recurrence); task->setAttachments(attachments); task->setRunning(running); if (itemId > 0) task->setProperty("itemId", itemId); if (parentCollectionId > 0) task->setProperty("parentCollectionId", parentCollectionId); if (!todoUid.isEmpty()) task->setProperty("todoUid", todoUid); task->setProperty("relatedUid", "parent-uid"); // WHEN Akonadi::Serializer serializer; auto item = serializer.createItemFromTask(task); // THEN QCOMPARE(item.mimeType(), KCalCore::Todo::todoMimeType()); QCOMPARE(item.isValid(), itemId > 0); if (itemId > 0) { QCOMPARE(item.id(), itemId); } QCOMPARE(item.parentCollection().isValid(), parentCollectionId > 0); if (parentCollectionId > 0) { QCOMPARE(item.parentCollection().id(), parentCollectionId); } auto todo = item.payload(); QCOMPARE(todo->summary(), summary); QCOMPARE(todo->description(), content); QCOMPARE(todo->isCompleted(), isDone); QCOMPARE(todo->completed().toLocalTime().date(), doneDate); QCOMPARE(todo->dtStart().toLocalTime().date(), startDate); QCOMPARE(todo->dtDue().toLocalTime().date(), dueDate); if (todo->dtStart().isValid()) { QCOMPARE(int(todo->dtStart().timeSpec()), int(Qt::LocalTime)); } QVERIFY(todo->allDay()); // this is always true currently... const ushort expectedRecurrence = recurrence == Domain::Task::NoRecurrence ? KCalCore::Recurrence::rNone : recurrence == Domain::Task::RecursDaily ? KCalCore::Recurrence::rDaily : recurrence == Domain::Task::RecursWeekly ? KCalCore::Recurrence::rWeekly : recurrence == Domain::Task::RecursMonthly ? KCalCore::Recurrence::rMonthlyDay : KCalCore::Recurrence::rNone; // Shouldn't happen though QCOMPARE(todo->recurrence()->recurrenceType(), expectedRecurrence); if (recurrence != Domain::Task::NoRecurrence) QCOMPARE(todo->recurrence()->frequency(), 1); QCOMPARE(todo->attachments().size(), attachments.size()); for (int i = 0; i < attachments.size(); i++) { auto attachment = todo->attachments().at(i); QCOMPARE(attachment->isUri(), attachments.at(i).isUri()); QCOMPARE(QUrl(attachment->uri()), attachments.at(i).uri()); QCOMPARE(attachment->decodedData(), attachments.at(i).data()); QCOMPARE(attachment->label(), attachments.at(i).label()); QCOMPARE(attachment->mimeType(), attachments.at(i).mimeType()); } if (!todoUid.isEmpty()) { QCOMPARE(todo->uid(), todoUid); } QCOMPARE(todo->relatedTo(), QStringLiteral("parent-uid")); QCOMPARE(todo->customProperty("Zanshin", "Running"), running ? QStringLiteral("1") : QString()); } void shouldVerifyIfAnItemIsATaskChild_data() { QTest::addColumn("task"); QTest::addColumn("item"); QTest::addColumn("isParent"); // Create task const QString summary = QStringLiteral("summary"); const QString content = QStringLiteral("content"); const bool isDone = true; const QDate doneDate(QDate(2013, 11, 30)); const QDate startDate(QDate(2013, 11, 24)); const QDate dueDate(QDate(2014, 03, 01)); // ... create a task Domain::Task::Ptr task(new Domain::Task); task->setTitle(summary); task->setText(content); task->setDone(isDone); task->setDoneDate(doneDate); task->setStartDate(startDate); task->setDueDate(dueDate); task->setProperty("todoUid", "1"); // Create Child item KCalCore::Todo::Ptr childTodo(new KCalCore::Todo); childTodo->setSummary(summary); childTodo->setDescription(content); if (isDone) childTodo->setCompleted(QDateTime(doneDate)); else childTodo->setCompleted(isDone); setTodoDates(childTodo, startDate, dueDate); Akonadi::Item childItem; childItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); childItem.setPayload(childTodo); QTest::newRow("without parent") << task << childItem << false; // Create Child Item with parent KCalCore::Todo::Ptr childTodo2(new KCalCore::Todo); childTodo2->setSummary(summary); childTodo2->setDescription(content); if (isDone) childTodo2->setCompleted(QDateTime(doneDate)); else childTodo2->setCompleted(isDone); setTodoDates(childTodo2, startDate, dueDate); childTodo2->setRelatedTo(QStringLiteral("1")); Akonadi::Item childItem2; childItem2.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); childItem2.setPayload(childTodo2); QTest::newRow("with parent") << task << childItem2 << true; Domain::Task::Ptr invalidTask(new Domain::Task); QTest::newRow("with invalid task") << invalidTask << childItem << false; Akonadi::Item invalidItem; QTest::newRow("with invalid item") << task << invalidItem << false; } void shouldVerifyIfAnItemIsATaskChild() { // GIVEN QFETCH(Domain::Task::Ptr, task); QFETCH(Akonadi::Item, item); QFETCH(bool, isParent); // WHEN Akonadi::Serializer serializer; bool value = serializer.isTaskChild(task, item); // THEN QCOMPARE(value, isParent); } void shouldRetrieveRelatedUidFromItem_data() { QTest::addColumn("item"); QTest::addColumn("expectedUid"); Akonadi::Item item1; KCalCore::Todo::Ptr todo1(new KCalCore::Todo); item1.setPayload(todo1); Akonadi::Item item2; KCalCore::Todo::Ptr todo2(new KCalCore::Todo); todo2->setRelatedTo(QStringLiteral("1")); item2.setPayload(todo2); QTest::newRow("task without related") << item1 << QString(); QTest::newRow("task with related") << item2 << "1"; } void shouldRetrieveRelatedUidFromItem() { // GIVEN QFETCH(Akonadi::Item, item); QFETCH(QString, expectedUid); // WHEN Akonadi::Serializer serializer; QString uid = serializer.relatedUidFromItem(item); // THEN QCOMPARE(uid, expectedUid); } void shouldCreateNoteFromItem_data() { QTest::addColumn("title"); QTest::addColumn("text"); QTest::addColumn("relatedUid"); QTest::newRow("nominal case (no related)") << "A note title" << "A note content.\nWith two lines." << QString(); QTest::newRow("nominal case (with related)") << "A note title" << "A note content.\nWith two lines." << "parent-uid"; QTest::newRow("trailing new lines") << "A note title" << "Empty lines at the end.\n\n\n" << QString(); QTest::newRow("empty case") << QString() << QString() << QString(); } void shouldCreateProjectFromItem_data() { QTest::addColumn("summary"); QTest::newRow("nominal case") << "summary"; QTest::newRow("empty case") << QString(); } void shouldCreateProjectFromItem() { // GIVEN // Data... QFETCH(QString, summary); // ... stored in a todo... KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(summary); todo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); QVERIFY(!todo->uid().isEmpty()); // ... as payload of an item Akonadi::Item item(42); item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); // which has a prent collection Akonadi::Collection collection(43); item.setParentCollection(collection); // WHEN Akonadi::Serializer serializer; Domain::Project::Ptr project = serializer.createProjectFromItem(item); // THEN QCOMPARE(project->name(), summary); QCOMPARE(project->property("itemId").toLongLong(), item.id()); QCOMPARE(project->property("parentCollectionId").toLongLong(), collection.id()); QCOMPARE(project->property("todoUid").toString(), todo->uid()); } void shouldCreateNullProjectFromInvalidItem() { // GIVEN Akonadi::Item item; // WHEN Akonadi::Serializer serializer; Domain::Project::Ptr project = serializer.createProjectFromItem(item); // THEN QVERIFY(project.isNull()); } void shouldCreateNullProjectFromTaskItem() { // GIVEN // A todo without the project flag KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(QStringLiteral("foo")); // ... as payload of an item Akonadi::Item item; item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); // WHEN Akonadi::Serializer serializer; Domain::Project::Ptr project = serializer.createProjectFromItem(item); // THEN QVERIFY(project.isNull()); } void shouldUpdateProjectFromItem_data() { QTest::addColumn("updatedSummary"); QTest::newRow("no change") << "summary"; QTest::newRow("changed") << "new summary"; } void shouldUpdateProjectFromItem() { // GIVEN // A todo... KCalCore::Todo::Ptr originalTodo(new KCalCore::Todo); originalTodo->setSummary(QStringLiteral("summary")); originalTodo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); // ... as payload of an item... Akonadi::Item originalItem(42); originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); originalItem.setPayload(originalTodo); // ... which has a parent collection... Akonadi::Collection originalCollection(43); originalItem.setParentCollection(originalCollection); // ... deserialized as a project Akonadi::Serializer serializer; auto project = serializer.createProjectFromItem(originalItem); // WHEN // Data... QFETCH(QString, updatedSummary); // ... in a new todo... KCalCore::Todo::Ptr updatedTodo(new KCalCore::Todo); updatedTodo->setSummary(updatedSummary); updatedTodo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); QVERIFY(!updatedTodo->uid().isEmpty()); // ... as payload of a new item Akonadi::Item updatedItem(44); updatedItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); updatedItem.setPayload(updatedTodo); // ... which has a new parent collection Akonadi::Collection updatedCollection(45); updatedItem.setParentCollection(updatedCollection); serializer.updateProjectFromItem(project, updatedItem); // THEN QCOMPARE(project->name(), updatedSummary); QCOMPARE(project->property("itemId").toLongLong(), updatedItem.id()); QCOMPARE(project->property("parentCollectionId").toLongLong(), updatedCollection.id()); QCOMPARE(project->property("todoUid").toString(), updatedTodo->uid()); } void shouldNotUpdateProjectFromInvalidItem() { // GIVEN // Data... const QString summary = QStringLiteral("summary"); // ... stored in a todo... KCalCore::Todo::Ptr originalTodo(new KCalCore::Todo); originalTodo->setSummary(summary); originalTodo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); // ... as payload of an item... Akonadi::Item originalItem; originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); originalItem.setPayload(originalTodo); // ... deserialized as a project Akonadi::Serializer serializer; auto project = serializer.createProjectFromItem(originalItem); // WHEN Akonadi::Item invalidItem; serializer.updateProjectFromItem(project, invalidItem); // THEN QCOMPARE(project->name(), summary); } void shouldNotUpdateProjectFromTaskItem() { // GIVEN // Data... const QString summary = QStringLiteral("summary"); // ... stored in a todo... KCalCore::Todo::Ptr originalTodo(new KCalCore::Todo); originalTodo->setSummary(summary); originalTodo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); // ... as payload of an item... Akonadi::Item originalItem; originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); originalItem.setPayload(originalTodo); // ... deserialized as a project Akonadi::Serializer serializer; auto project = serializer.createProjectFromItem(originalItem); // WHEN // A todo without the project flag KCalCore::Todo::Ptr projectTodo(new KCalCore::Todo); projectTodo->setSummary(QStringLiteral("foo")); // ... as payload of an item Akonadi::Item projectItem; projectItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); projectItem.setPayload(projectTodo); serializer.updateProjectFromItem(project, projectItem); // THEN QCOMPARE(project->name(), summary); } void shouldCreateItemFromProject_data() { QTest::addColumn("summary"); QTest::addColumn("itemId"); QTest::addColumn("parentCollectionId"); QTest::newRow("nominal case (no id)") << "summary" << qint64(-1) << qint64(-1); QTest::newRow("empty case (no id)") << QString() << qint64(-1) << qint64(-1); QTest::newRow("nominal case (with id)") << "summary" << qint64(42) << qint64(43); QTest::newRow("empty case (with id)") << QString() << qint64(42) << qint64(43); } void shouldCreateItemFromProject() { // GIVEN // Data... QFETCH(QString, summary); QFETCH(qint64, itemId); QFETCH(qint64, parentCollectionId); const QString todoUid = QStringLiteral("test-uid"); // ... stored in a project auto project = Domain::Project::Ptr::create(); project->setName(summary); project->setProperty("todoUid", todoUid); if (itemId > 0) project->setProperty("itemId", itemId); if (parentCollectionId > 0) project->setProperty("parentCollectionId", parentCollectionId); // WHEN Akonadi::Serializer serializer; auto item = serializer.createItemFromProject(project); // THEN QCOMPARE(item.mimeType(), KCalCore::Todo::todoMimeType()); QCOMPARE(item.isValid(), itemId > 0); if (itemId > 0) { QCOMPARE(item.id(), itemId); } QCOMPARE(item.parentCollection().isValid(), parentCollectionId > 0); if (parentCollectionId > 0) { QCOMPARE(item.parentCollection().id(), parentCollectionId); } auto todo = item.payload(); QCOMPARE(todo->summary(), summary); QCOMPARE(todo->uid(), todoUid); QVERIFY(!todo->customProperty("Zanshin", "Project").isEmpty()); } void shouldVerifyIfAnItemIsAProjectChild_data() { QTest::addColumn("project"); QTest::addColumn("item"); QTest::addColumn("isParent"); // Create project auto project = Domain::Project::Ptr::create(); project->setName(QStringLiteral("project")); project->setProperty("todoUid", "1"); // Create unrelated todo auto unrelatedTodo = KCalCore::Todo::Ptr::create(); unrelatedTodo->setSummary(QStringLiteral("summary")); Akonadi::Item unrelatedTodoItem; unrelatedTodoItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); unrelatedTodoItem.setPayload(unrelatedTodo); QTest::newRow("unrelated todo") << project << unrelatedTodoItem << false; // Create child todo auto childTodo = KCalCore::Todo::Ptr::create(); childTodo->setSummary(QStringLiteral("summary")); childTodo->setRelatedTo(QStringLiteral("1")); Akonadi::Item childTodoItem; childTodoItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); childTodoItem.setPayload(childTodo); QTest::newRow("child todo") << project << childTodoItem << true; auto invalidProject = Domain::Project::Ptr::create(); QTest::newRow("invalid project") << invalidProject << unrelatedTodoItem << false; Akonadi::Item invalidItem; QTest::newRow("invalid item") << project << invalidItem << false; } void shouldVerifyIfAnItemIsAProjectChild() { // GIVEN QFETCH(Domain::Project::Ptr, project); QFETCH(Akonadi::Item, item); QFETCH(bool, isParent); // WHEN Akonadi::Serializer serializer; bool value = serializer.isProjectChild(project, item); // THEN QCOMPARE(value, isParent); } void shouldUpdateItemParent_data() { QTest::addColumn("item"); QTest::addColumn("parent"); QTest::addColumn("expectedRelatedToUid"); Akonadi::Item item1; KCalCore::Todo::Ptr todo1(new KCalCore::Todo); item1.setPayload(todo1); Domain::Task::Ptr parent(new Domain::Task); parent->setProperty("todoUid", "1"); QTest::newRow("nominal case") << item1 << parent << "1"; Akonadi::Item item2; QTest::newRow("update item without payload") << item2 << parent << QString(); Domain::Task::Ptr parent2(new Domain::Task); QTest::newRow("update item with a empty parent uid") << item1 << parent2 << QString(); } void shouldUpdateItemParent() { // GIVEN QFETCH(Akonadi::Item, item); QFETCH(Domain::Task::Ptr, parent); QFETCH(QString, expectedRelatedToUid); // WHEN Akonadi::Serializer serializer; serializer.updateItemParent(item, parent); // THEN if (item.hasPayload()) { auto todo = item.payload(); QString relatedUid = todo->relatedTo(); QCOMPARE(relatedUid, expectedRelatedToUid); } } void shouldUpdateItemProject_data() { QTest::addColumn("item"); QTest::addColumn("parent"); QTest::addColumn("expectedRelatedToUid"); Akonadi::Item todoItem; KCalCore::Todo::Ptr todo(new KCalCore::Todo); todoItem.setPayload(todo); auto parent = Domain::Project::Ptr::create(); parent->setProperty("todoUid", "1"); QTest::newRow("nominal todo case") << todoItem << parent << "1"; auto invalidParent = Domain::Project::Ptr::create(); QTest::newRow("update todo item with a empty parent uid") << todoItem << invalidParent << QString(); Akonadi::Item invalidItem; QTest::newRow("update item without payload") << invalidItem << parent << QString(); } void shouldUpdateItemProject() { // GIVEN QFETCH(Akonadi::Item, item); QFETCH(Domain::Project::Ptr, parent); QFETCH(QString, expectedRelatedToUid); // WHEN Akonadi::Serializer serializer; serializer.updateItemProject(item, parent); // THEN if (item.hasPayload()) { auto todo = item.payload(); const QString relatedUid = todo->relatedTo(); QCOMPARE(relatedUid, expectedRelatedToUid); - } else if (item.hasPayload()) { - auto note = item.payload(); - const auto relatedHeader = note->headerByType("X-Zanshin-RelatedProjectUid"); - const QString relatedUid = relatedHeader ? relatedHeader->asUnicodeString() : QString(); - QCOMPARE(relatedUid, expectedRelatedToUid); - if (!expectedRelatedToUid.isEmpty()) - QVERIFY(note->encodedContent().contains(QStringLiteral("X-Zanshin-RelatedProjectUid: %1").arg(expectedRelatedToUid).toUtf8())); - else - QVERIFY(!note->encodedContent().contains("X-Zanshin-RelatedProjectUid:")); } } void shouldFilterChildrenItem_data() { QTest::addColumn("item"); QTest::addColumn("items"); QTest::addColumn("size"); Akonadi::Item item(12); KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setUid(QStringLiteral("1")); item.setPayload(todo); Akonadi::Item::List items; QTest::newRow("empty list") << item << items << 0; Akonadi::Item item2(13); KCalCore::Todo::Ptr todo2(new KCalCore::Todo); item2.setPayload(todo2); Akonadi::Item::List items2; items2 << item2; QTest::newRow("list without child") << item << items2 << 0; Akonadi::Item item3(14); KCalCore::Todo::Ptr todo3(new KCalCore::Todo); todo3->setUid(QStringLiteral("3")); todo3->setRelatedTo(QStringLiteral("1")); item3.setPayload(todo3); Akonadi::Item::List items3; items3 << item2 << item3; QTest::newRow("list with child") << item << items3 << 1; Akonadi::Item item4(15); KCalCore::Todo::Ptr todo4(new KCalCore::Todo); todo4->setRelatedTo(QStringLiteral("3")); item4.setPayload(todo4); Akonadi::Item::List items4; items4 << item2 << item3 << item4; QTest::newRow("list with child with a child") << item << items4 << 2; Akonadi::Item::List items5; items5 << item << item2 << item3 << item4; QTest::newRow("list with filter in list") << item << items5 << 2; } void shouldFilterChildrenItem() { // GIVEN QFETCH(Akonadi::Item, item); QFETCH(Akonadi::Item::List, items); QFETCH(int, size); // WHEN Akonadi::Serializer serializer; Akonadi::Item::List list = serializer.filterDescendantItems(items, item); // THEN QCOMPARE(list.size(), size); } void shouldRemoveItemParent_data() { QTest::addColumn("item"); Akonadi::Item item(15); KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setRelatedTo(QStringLiteral("3")); item.setPayload(todo); QTest::newRow("nominal case") << item; Akonadi::Item item2(16); QTest::newRow("parent invalid") << item2; } void shouldRemoveItemParent() { // GIVEN QFETCH(Akonadi::Item, item); // WHEN Akonadi::Serializer serializer; serializer.removeItemParent(item); // THEN if (item.hasPayload()) QCOMPARE(item.payload()->relatedTo(), QString()); } void shouldPromoteItemToProject_data() { QTest::addColumn("item"); auto item = Akonadi::Item(15); auto todo = KCalCore::Todo::Ptr::create(); todo->setRelatedTo(QStringLiteral("3")); item.setPayload(todo); QTest::newRow("nominal case") << item; QTest::newRow("invalid item") << Akonadi::Item(16); } void shouldPromoteItemToProject() { // GIVEN QFETCH(Akonadi::Item, item); // WHEN Akonadi::Serializer serializer; serializer.promoteItemToProject(item); // THEN if (item.hasPayload()) { auto todo = item.payload(); QCOMPARE(todo->relatedTo(), QString()); QVERIFY(!todo->customProperty("Zanshin", "Project").isEmpty()); } } void shouldClearItem_data() { QTest::addColumn("item"); Akonadi::Item *itemWithContent = new Akonadi::Item(15); KCalCore::Todo::Ptr todo(new KCalCore::Todo); // context Akonadi::Tag context(QStringLiteral("42")); context.setType( Akonadi::SerializerInterface::contextTagType() ); // tag Akonadi::Tag tag(QStringLiteral("43")); tag.setType( Akonadi::Tag::PLAIN ); Akonadi::Tag::List tagsList = Akonadi::Tag::List() << tag << context; itemWithContent->setTags(tagsList); itemWithContent->setPayload(todo); QTest::newRow("nominal case") << itemWithContent; Akonadi::Item *item2 = new Akonadi::Item(16); QTest::newRow("parent invalid") << item2; } void shouldClearItem() { // GIVEN QFETCH(Akonadi::Item*, item); // WHEN Akonadi::Serializer serializer; serializer.clearItem(item); // THEN QCOMPARE(item->tags().size(), 0); delete item; } void shouldCreateContextFromTag_data() { QTest::addColumn("type"); QTest::addColumn("name"); QTest::addColumn("tagId"); const QByteArray rightTagType = Akonadi::Serializer::contextTagType() ; QTest::newRow("nominal case") << rightTagType << "Context42" << Akonadi::Tag::Id(42); QTest::newRow("empty name case") << rightTagType << "" << Akonadi::Tag::Id(43); } void shouldCreateContextFromTag() { // GIVEN // Data... QFETCH(QByteArray, type); QFETCH(QString, name); QFETCH(Akonadi::Tag::Id, tagId); // ... stored as an Akonadi Tag Akonadi::Tag tag(name); tag.setType(type); tag.setId(tagId); // WHEN Akonadi::Serializer serializer; Domain::Context::Ptr context = serializer.createContextFromTag(tag); // THEN QCOMPARE(context->name(), tag.name()); QCOMPARE(context->property("tagId").toLongLong(), tag.id()); } void shouldNotCreateContextFromWrongTagType() { // GIVEN // Data stored as an Akonadi Tag Akonadi::Tag tag(QStringLiteral("context42")); tag.setType(QByteArray("wrongTagType")); // WHEN Akonadi::Serializer serializer; Domain::Context::Ptr context = serializer.createContextFromTag(tag); // THEN QVERIFY(!context); } void shouldUpdateContextFromTag_data() { shouldCreateContextFromTag_data(); } void shouldUpdateContextFromTag() { // GIVEN // Data... QFETCH(QByteArray, type); QFETCH(QString, name); QFETCH(Akonadi::Tag::Id, tagId); // ... stored as an Akonadi Tag Akonadi::Tag tag(name); tag.setType(type); tag.setId(tagId); // WHEN Akonadi::Serializer serializer; Domain::Context::Ptr context(new Domain::Context); serializer.updateContextFromTag(context, tag); // THEN QCOMPARE(context->name(), tag.name()); QCOMPARE(context->property("tagId").toLongLong(), tag.id()); } void shouldNotUpdateContextFromWrongTagType() { // GIVEN Akonadi::Tag originalTag(QStringLiteral("Context42")); originalTag.setType(Akonadi::Serializer::contextTagType()); originalTag.setId(42); Akonadi::Serializer serializer; Domain::Context::Ptr context = serializer.createContextFromTag(originalTag); // WHEN Akonadi::Tag wrongTag(QStringLiteral("WrongContext42")); wrongTag.setType(QByteArray("wrongTypeTag")); serializer.updateContextFromTag(context, wrongTag); // THEN QCOMPARE(context->name(), originalTag.name()); QCOMPARE(context->property("tagId").toLongLong(), originalTag.id()); } void shouldVerifyIfAnItemIsAContextChild_data() { QTest::addColumn("context"); QTest::addColumn("item"); QTest::addColumn("isChild"); // Create a context auto context = Domain::Context::Ptr::create(); context->setProperty("tagId", qint64(43)); Akonadi::Tag tag(Akonadi::Tag::Id(43)); Akonadi::Item unrelatedItem; QTest::newRow("Unrelated item") << context << unrelatedItem << false; Akonadi::Item relatedItem; relatedItem.setTag(tag); QTest::newRow("Related item") << context << relatedItem << true; auto invalidContext = Domain::Context::Ptr::create(); QTest::newRow("Invalid context") << invalidContext << relatedItem << false; Akonadi::Item invalidItem; QTest::newRow("Invalid Item") << context << invalidItem << false; } void shouldVerifyIfAnItemIsAContextChild() { // GIVEN QFETCH(Domain::Context::Ptr, context); QFETCH(Akonadi::Item, item); QFETCH(bool, isChild); // WHEN Akonadi::Serializer serializer; bool value = serializer.isContextChild(context, item); // THEN QCOMPARE(value, isChild); } void shouldCheckIfAnItemHasContexts_data() { QTest::addColumn("item"); QTest::addColumn("contextsExpected"); Akonadi::Tag unrelatedTag(QStringLiteral("Foo")); unrelatedTag.setType("unrelated"); Akonadi::Tag contextTag(QStringLiteral("Bar")); contextTag.setType(Akonadi::Serializer::contextTagType()); Akonadi::Tag akonadiTag(QStringLiteral("Baz")); akonadiTag.setType(Akonadi::Tag::PLAIN); Akonadi::Item item; QTest::newRow("no tags") << item << false; item.setTags({ unrelatedTag }); QTest::newRow("unrelated tags") << item << false; item.setTags({ unrelatedTag, contextTag }); QTest::newRow("has contexts") << item << true; item.setTags({ unrelatedTag, akonadiTag }); QTest::newRow("has tags") << item << false; item.setTags({ unrelatedTag, contextTag, akonadiTag }); QTest::newRow("has both") << item << true; } void shouldCheckIfAnItemHasContexts() { // GIVEN QFETCH(Akonadi::Item, item); QFETCH(bool, contextsExpected); Akonadi::Serializer serializer; // WHEN const bool hasContexts = serializer.hasContextTags(item); // THEN QCOMPARE(hasContexts, contextsExpected); } void shouldCreateTagFromContext_data() { QTest::addColumn("name"); QTest::addColumn("tagId"); QTest::addColumn("tagGid"); QString nameInternet = QStringLiteral("Internet"); QTest::newRow("nominal case") << QString(nameInternet) << qint64(42) << nameInternet.toLatin1(); QTest::newRow("null name case") << QString() << qint64(42) << QByteArray(); QTest::newRow("null tagId case") << QString(nameInternet)<< qint64(-1) << nameInternet.toLatin1(); QTest::newRow("totally null context case") << QString() << qint64(-1) << QByteArray(); } void shouldCreateTagFromContext() { // GIVEN QFETCH(QString, name); QFETCH(qint64, tagId); QFETCH(QByteArray, tagGid); // WHEN auto context = Domain::Context::Ptr::create(); context->setProperty("tagId", tagId); context->setName(name); Akonadi::Serializer serializer; Akonadi::Tag tag = serializer.createTagFromContext(context); // THEN QCOMPARE(tag.name(), name); QCOMPARE(tag.isValid(), tagId > 0); if (tagId > 0) { QCOMPARE(tag.id(), tagId); QCOMPARE(tag.gid(), tagGid); QCOMPARE(tag.type(), Akonadi::SerializerInterface::contextTagType()); } } // Investigation into how to differentiate all-day events from events with time, // using QDateTime only. Doesn't seem to be possible. void noWayToHaveQDateTimeWithoutTime() { // GIVEN a QDateTime without time information QDateTime dateOnly(QDate(2016, 6, 12), QTime(-1, -1, -1)); // THEN we can't detect that there was no time information, i.e. all day event QVERIFY(dateOnly.time().isValid()); // I wish this was "!" QVERIFY(!dateOnly.time().isNull()); // got converted to midnight localtime by QDateTime // This doesn't help, QDateTime converts "null time" to midnight. dateOnly.setTime(QTime()); QVERIFY(dateOnly.time().isValid()); // same as above QVERIFY(!dateOnly.time().isNull()); // same as above // GIVEN a QDateTime at midnight QDateTime atMidnight(QDate(2016, 6, 12), QTime(0, 0, 0)); // THEN we can detect that a time information was present QVERIFY(atMidnight.time().isValid()); QVERIFY(!atMidnight.time().isNull()); #if 0 // GIVEN a KDateTime without time information KDateTime kdOnly(QDate(2016, 6, 12)); // THEN we can detect that there was no time information, i.e. all day event QVERIFY(kdOnly.isDateOnly()); #endif } }; ZANSHIN_TEST_MAIN(AkonadiSerializerTest) #include "akonadiserializertest.moc"